مثالهای زیر بر اساس سناریوهای رایجی هستند که در آن از R8 برای بهینهسازی استفاده میکنید، اما برای پیشنویس قوانین حفظ نیاز به راهنمایی پیشرفته دارید.
انعکاس
به طور کلی، برای عملکرد بهینه، استفاده از بازتاب توصیه نمی شود. با این حال، در سناریوهای خاص، ممکن است اجتناب ناپذیر باشد. مثالهای زیر راهنمایی برای حفظ قوانین در سناریوهای رایجی که از بازتاب استفاده میکنند، ارائه میکند.
بازتاب با کلاس های بارگذاری شده با نام
کتابخانه ها اغلب کلاس ها را به صورت پویا با استفاده از نام کلاس به عنوان String
بارگیری می کنند. با این حال، R8 نمیتواند کلاسهایی را که به این روش بارگذاری میشوند شناسایی کند و ممکن است کلاسهایی را که استفاده نشده در نظر میگیرد حذف کند.
به عنوان مثال، سناریوی زیر را در نظر بگیرید که در آن شما یک کتابخانه و یک برنامه دارید که از کتابخانه استفاده می کند - کد یک بارگیری کتابخانه را نشان می دهد که یک رابط MyWorker
را که توسط یک برنامه پیاده سازی شده است، نشان می دهد.
کد کتابخانه به شرح زیر است:
// The interface for a task that runs once.
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(className: String) {
// R8 won't retain classes specified by this string value at runtime
val taskClass = Class.forName(className)
val task = taskClass.getDeclaredConstructor().newInstance() as StartupTask
task.run()
}
}
برنامه ای که از کتابخانه استفاده می کند دارای کد زیر است:
// The app's task to pre-cache data.
// R8 will remove this class because it's only referenced by a string.
class PreCacheTask : StartupTask {
override fun run() {
// This log will never appear if the class is removed by R8.
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is told to run the app's task by its name.
TaskRunner.execute("com.example.app.PreCacheTask")
}
در این سناریو، کتابخانه شما باید شامل یک فایل قوانین نگهداری مصرف کننده با قوانین نگهداری زیر باشد:
-keep class * implements com.example.library.StartupTask {
<init>();
}
بدون این قانون، R8 PreCacheTask
از برنامه حذف می کند زیرا برنامه مستقیماً از کلاس استفاده نمی کند و یکپارچگی را به هم می زند. این قانون کلاسهایی را پیدا میکند که رابط StartupTask
کتابخانه شما را پیادهسازی میکنند و آنها را به همراه سازنده بدون آرگومانشان حفظ میکند و به کتابخانه اجازه میدهد تا PreCacheTask
با موفقیت نمونهسازی و اجرا کند.
انعکاس با ::class.java
کتابخانهها میتوانند کلاسها را با ارسال مستقیم شی Class
توسط برنامه بارگیری کنند، که روشی قویتر از بارگیری کلاسها با نام است. این یک مرجع قوی به کلاس ایجاد می کند که R8 می تواند تشخیص دهد. با این حال، در حالی که این امر مانع از حذف کلاس R8 می شود، شما همچنان باید از یک قانون keep استفاده کنید تا اعلام کنید که کلاس به صورت انعکاسی نمونه سازی شده است و از اعضایی که به صورت انعکاسی به آنها دسترسی پیدا می کنند، مانند سازنده محافظت کنید.
به عنوان مثال، سناریوی زیر را در نظر بگیرید که در آن شما یک کتابخانه و یک برنامه دارید که از کتابخانه استفاده می کند - بارگذار کتابخانه با ارسال مستقیم مرجع کلاس، یک رابط StartupTask
نمونه سازی می کند.
کد کتابخانه به شرح زیر است:
// The interface for a task that runs once.
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(taskClass: Class<out StartupTask>) {
// The class isn't removed, but its constructor might be.
val task = taskClass.getDeclaredConstructor().newInstance()
task.run()
}
}
برنامه ای که از کتابخانه استفاده می کند دارای کد زیر است:
// The app's task is to pre-cache data.
class PreCacheTask : StartupTask {
override fun run() {
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is given a direct reference to the app's task class.
TaskRunner.execute(PreCacheTask::class.java)
}
در این سناریو، کتابخانه شما باید شامل یک فایل قوانین نگهداری مصرف کننده با قوانین نگهداری زیر باشد:
# Allow any implementation of StartupTask to be removed if unused.
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask
# Keep the default constructor, which is called via reflection.
-keepclassmembers class * implements com.example.library.StartupTask {
<init>();
}
این قوانین به گونهای طراحی شدهاند که با این نوع انعکاس کاملاً کار میکنند، و در عین حال از عملکرد صحیح کد، حداکثر بهینهسازی را ممکن میسازند. قوانین به R8 اجازه می دهد نام کلاس را مبهم کند و در صورتی که برنامه هرگز از آن استفاده نکند، پیاده سازی کلاس StartupTask
را کوچک یا حذف کند. با این حال، برای هر پیاده سازی، مانند PrecacheTask
که در مثال استفاده می شود، سازنده پیش فرض ( <init>()
) را که کتابخانه شما باید فراخوانی کند، حفظ می کند.
-
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask
: این قانون هر کلاسی را هدف قرار می دهد که رابطStartupTask
شما را پیاده سازی کند.-
-keep class * implements com.example.library.StartupTask
: این کلاس هر کلاس (*
) را که رابط شما را پیاده سازی می کند حفظ می کند. -
,allowobfuscation
: به R8 دستور می دهد که با وجود حفظ کلاس، می تواند آن را تغییر نام داده یا مبهم کند. این امن است زیرا کتابخانه شما به نام کلاس متکی نیست. مستقیماً شیClass
را دریافت می کند. -
,allowshrinking
: این اصلاح کننده به R8 دستور می دهد که در صورت استفاده نشدن کلاس می تواند آن را حذف کند. این به R8 کمک می کند تا با خیال راحت پیاده سازیStartupTask
را که هرگز بهTaskRunner.execute()
ارسال نمی شود حذف کند. به طور خلاصه، این قانون به موارد زیر اشاره دارد: اگر یک برنامه از کلاسی استفاده می کند کهStartupTask
پیاده سازی می کند، R8 کلاس را نگه می دارد. R8 می تواند نام کلاس را تغییر دهد تا اندازه آن کاهش یابد و اگر برنامه از آن استفاده نکرد می تواند آن را حذف کند.
-
-
-keepclassmembers class * implements com.example.library.StartupTask { <init>(); }
: این قانون اعضای خاصی از کلاس هایی را هدف قرار می دهد که در قانون اول شناسایی شده اند - در این مورد، سازنده.-
-keepclassmembers class * implements com.example.library.StartupTask
: این کار اعضای خاص (روش ها، فیلدها) کلاسی را که رابطStartupTask
را پیاده سازی می کند حفظ می کند، اما فقط در صورتی که خود کلاس پیاده سازی شده نگه داشته شود. -
{ <init>(); }
: این انتخاب کننده عضو است.<init>
نام داخلی ویژه سازنده در بایت کد جاوا است. این بخش به طور خاص سازنده پیش فرض و بدون آرگومان را هدف قرار می دهد. - این قانون بسیار مهم است زیرا کد شما
getDeclaredConstructor().newInstance()
بدون هیچ آرگومان فراخوانی می کند که به طور بازتابی سازنده پیش فرض را فراخوانی می کند. بدون این قانون، R8 می بیند که هیچ کدی مستقیماًnew PreCacheTask()
را فراخوانی نمی کند، فرض می کند که سازنده استفاده نشده است و آن را حذف می کند. این باعث می شود برنامه شما در زمان اجرا با یکInstantiationException
از کار بیفتد.
-
انعکاس بر اساس حاشیه نویسی روش
کتابخانه ها اغلب حاشیه نویسی را تعریف می کنند که توسعه دهندگان از آنها برای برچسب گذاری روش ها یا فیلدها استفاده می کنند. سپس کتابخانه از بازتاب برای یافتن این اعضای مشروح در زمان اجرا استفاده می کند. به عنوان مثال، حاشیه نویسی @OnLifecycleEvent
برای یافتن روش های مورد نیاز در زمان اجرا استفاده می شود.
به عنوان مثال، سناریوی زیر را در نظر بگیرید که در آن شما یک کتابخانه و یک برنامه دارید که از کتابخانه استفاده می کند - مثال یک اتوبوس رویداد را نشان می دهد که روش های حاشیه نویسی شده با @OnEvent
را پیدا کرده و فراخوانی می کند.
کد کتابخانه به شرح زیر است:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class OnEvent
class EventBus {
fun dispatch(listener: Any) {
// Find all methods annotated with @OnEvent and invoke them
listener::class.java.declaredMethods.forEach { method ->
if (method.isAnnotationPresent(OnEvent::class.java)) {
try {
method.invoke(listener)
} catch (e: Exception) { /* ... */ }
}
}
}
}
برنامه ای که از کتابخانه استفاده می کند دارای کد زیر است:
class MyEventListener {
@OnEvent
fun onSomethingHappened() {
// This method will be removed by R8 without a keep rule
Log.d(TAG, "Event received!")
}
}
fun onCreate() {
// Instantiate the listener and the event bus
val listener = MyEventListener()
val eventBus = EventBus()
// Dispatch the listener to the event bus
eventBus.dispatch(listener)
}
کتابخانه باید شامل یک فایل قوانین نگهداری مصرف کننده باشد که به طور خودکار هر روشی را با استفاده از حاشیه نویسی حفظ کند:
-keepattributes RuntimeVisibleAnnotations
-keep @interface com.example.library.OnEvent;
-keepclassmembers class * {
@com.example.library.OnEvent <methods>;
}
-
-keepattributes RuntimeVisibleAnnotations
: این قانون حاشیه نویسی هایی را که قرار است در زمان اجرا خوانده شوند حفظ می کند. -
-keep @interface com.example.library.OnEvent
: این قانون خود کلاس حاشیه نویسیOnEvent
حفظ می کند. -
-keepclassmembers class * {@com.example.library.OnEvent <methods>;}
: این قانون یک کلاس و اعضای خاص را فقط در صورتی حفظ می کند که کلاس در حال استفاده باشد و کلاس شامل آن اعضا باشد.-
-keepclassmembers
: این قانون یک کلاس و اعضای خاص را فقط در صورتی حفظ می کند که کلاس در حال استفاده باشد و کلاس شامل آن اعضا باشد. -
class *
: این قانون برای هر کلاسی اعمال می شود. -
@com.example.library.OnEvent <methods>;
: این کار هر کلاسی را که دارای یک یا چند متد (<methods>
) است که با@com.example.library.OnEvent
حاشیه نویسی شده است حفظ می کند و همچنین خود متدهای حاشیه نویسی را نیز حفظ می کند.
-
انعکاس بر اساس حاشیه نویسی کلاس
کتابخانه ها می توانند از بازتاب برای اسکن کلاس هایی که حاشیه نویسی خاصی دارند استفاده کنند. در این حالت کلاس task runner تمام کلاس های حاشیه نویسی شده با ReflectiveExecutor
را با استفاده از بازتاب پیدا کرده و متد execute
اجرا می کند.
به عنوان مثال، سناریوی زیر را در نظر بگیرید که در آن شما یک کتابخانه و یک برنامه دارید که از کتابخانه استفاده می کند.
این کتابخانه دارای کد زیر است:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class ReflectiveExecutor
class TaskRunner {
fun process(task: Any) {
val taskClass = task::class.java
if (taskClass.isAnnotationPresent(ReflectiveExecutor::class.java)) {
val methodToCall = taskClass.getMethod("execute")
methodToCall.invoke(task)
}
}
}
برنامه ای که از کتابخانه استفاده می کند دارای کد زیر است:
// In consumer app
@ReflectiveExecutor
class ImportantBackgroundTask {
fun execute() {
// This class will be removed by R8 without a keep rule
Log.e("ImportantBackgroundTask", "Executing the important background task...")
}
}
// Usage of ImportantBackgroundTask
fun onCreate(){
val task = ImportantBackgroundTask()
val runner = TaskRunner()
runner.process(task)
}
از آنجا که کتابخانه به طور انعکاسی از بازتاب برای دریافت کلاسهای خاص استفاده میکند، کتابخانه باید شامل یک فایل قوانین حفظ مصرفکننده با قوانین حفظ زیر باشد:
# Retain annotation metadata for runtime reflection.
-keepattributes RuntimeVisibleAnnotations
# Keep the annotation interface itself.
-keep @interface com.example.library.ReflectiveExecutor
# Keep the execute method in the classes which are being used
-keepclassmembers @com.example.library.ReflectiveExecutor class * {
public void execute();
}
این پیکربندی بسیار کارآمد است زیرا به R8 میگوید دقیقاً چه چیزی را حفظ کند.
بازتاب برای پشتیبانی از وابستگی های اختیاری
یک مورد رایج برای بازتاب ایجاد یک وابستگی نرم بین یک کتابخانه اصلی و یک کتابخانه اضافی اختیاری است. کتابخانه هسته می تواند بررسی کند که آیا افزونه در برنامه گنجانده شده است یا خیر و در صورت وجود، می تواند ویژگی های اضافی را فعال کند. این به شما امکان می دهد ماژول های الحاقی را بدون وادار کردن کتابخانه هسته به وابستگی مستقیم به آنها ارسال کنید.
کتابخانه هسته از بازتاب ( Class.forName
) برای جستجوی یک کلاس خاص با نام خود استفاده می کند. اگر کلاس پیدا شود، ویژگی فعال می شود. در غیر این صورت، به خوبی شکست می خورد.
به عنوان مثال، کد زیر را در نظر بگیرید که در آن یک AnalyticsManager
اصلی یک کلاس VideoEventTracker
اختیاری را بررسی می کند تا تجزیه و تحلیل ویدیو را فعال کند.
کتابخانه هسته دارای کد زیر است:
object AnalyticsManager {
private const val VIDEO_TRACKER_CLASS = "com.example.analytics.video.VideoEventTracker"
fun initialize() {
try {
// Attempt to load the optional module's class using reflection
Class.forName(VIDEO_TRACKER_CLASS).getDeclaredConstructor().newInstance()
Log.d(TAG, "Video tracking enabled.")
} catch (e: ClassNotFoundException) {
Log.d(TAG,"Video tracking module not found. Skipping.")
} catch (e: Exception) {
Log.e(TAG, e.printStackTrace())
}
}
}
کتابخانه ویدیویی اختیاری دارای کد زیر است:
package com.example.analytics.video
class VideoEventTracker {
// This constructor must be kept for the reflection call to succeed.
init { /* ... */ }
}
توسعه دهنده کتابخانه اختیاری مسئول ارائه قانون نگهداری مصرف کننده لازم است. این قانون حفظ اطمینان می دهد که هر برنامه ای که از کتابخانه اختیاری استفاده می کند، کدی را که کتابخانه اصلی باید پیدا کند، حفظ می کند.
# In the video library's consumer keep rules file
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
بدون این قانون، R8 احتمالا VideoEventTracker
از کتابخانه اختیاری حذف می کند زیرا هیچ چیز در آن ماژول مستقیماً از آن استفاده نمی کند. قانون keep کلاس و سازنده آن را حفظ میکند و به کتابخانه هسته اجازه میدهد با موفقیت آن را نمونهسازی کند.
بازتاب برای دسترسی به اعضای خصوصی
استفاده از بازتاب برای دسترسی به کد خصوصی یا محافظت شده که بخشی از API عمومی کتابخانه نیست، می تواند مشکلات مهمی را ایجاد کند. چنین کدی بدون اطلاع قبلی ممکن است تغییر کند، که می تواند منجر به رفتار غیرمنتظره یا خرابی در برنامه شما شود.
وقتی برای APIهای غیر عمومی به بازتاب متکی هستید، ممکن است با مشکلات زیر مواجه شوید:
- به روز رسانی های مسدود شده: تغییرات در کد خصوصی یا محافظت شده می تواند شما را از به روز رسانی به نسخه های بالاتر کتابخانه جلوگیری کند.
- مزایای از دست رفته: ممکن است عملکردهای جدید، رفع خرابی های مهم یا به روز رسانی های امنیتی ضروری را از دست بدهید.
بهینه سازی و بازتاب R8
اگر باید به کد خصوصی یا محافظت شده یک کتابخانه فکر کنید، به بهینه سازی های R8 توجه زیادی داشته باشید. اگر هیچ ارجاع مستقیمی به این اعضا وجود نداشته باشد، R8 ممکن است آنها را بلااستفاده فرض کند و متعاقباً آنها را حذف یا تغییر نام دهد. این می تواند منجر به خرابی زمان اجرا شود، اغلب با پیام های خطای گمراه کننده مانند NoSuchMethodException
یا NoSuchFieldException
.
به عنوان مثال، سناریوی زیر را در نظر بگیرید که نشان می دهد چگونه می توانید به یک فیلد خصوصی از کلاس کتابخانه دسترسی پیدا کنید.
کتابخانه ای که شما مالک آن نیستید دارای کد زیر است:
class LibraryClass {
private val secretMessage = "R8 will remove me"
}
برنامه شما دارای کد زیر است:
fun accessSecretMessage(instance: LibraryClass) {
// Use Java reflection from Kotlin to access the private field
val secretField = instance::class.java.getDeclaredField("secretMessage")
secretField.isAccessible = true
// This will crash at runtime with R8 enabled
val message = secretField.get(instance) as String
}
برای جلوگیری از حذف فیلد خصوصی R8، یک قانون -keep
را در برنامه خود اضافه کنید:
-keepclassmembers class com.example.LibraryClass {
private java.lang.String secretMessage;
}
-keepclassmembers
: فقط در صورتی اعضای خاصی از یک کلاس حفظ شود که خود کلاس حفظ شود.-
class com.example.LibraryClass
: این کلاس دقیقاً حاوی فیلد را هدف قرار می دهد. -
private java.lang.String secretMessage;
: این فیلد خصوصی خاص را با نام و نوع آن مشخص می کند.
رابط بومی جاوا (JNI)
بهینهسازیهای R8 میتوانند هنگام کار با تماسهای جدید از بومی (کد C/C++) به جاوا یا کاتلین مشکل داشته باشند. در حالی که عکس آن نیز صادق است - کاهش تماسها از جاوا یا کاتلین به کد بومی میتواند مشکلاتی داشته باشد، فایل پیشفرض proguard-android-optimize.txt
شامل قانون زیر برای کارکردن تماسهای پایین است. این قانون از روشهای بومی که کوتاه میشوند محافظت میکند.
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
تعامل با کد بومی از طریق رابط بومی جاوا (JNI)
وقتی برنامه شما از JNI برای برقراری تماس از کد بومی (C/C++) به جاوا یا کاتلین استفاده میکند، R8 نمیتواند ببیند کدام روشها از کد بومی شما فراخوانی میشوند. اگر هیچ مرجع مستقیمی به این روش ها در برنامه شما وجود نداشته باشد، R8 به اشتباه فرض می کند که این روش ها استفاده نشده اند و آنها را حذف می کند و باعث از کار افتادن برنامه شما می شود.
مثال زیر یک کلاس Kotlin را با متدی نشان می دهد که قرار است از یک کتابخانه بومی فراخوانی شود. کتابخانه بومی یک نوع برنامه کاربردی را نمونه سازی می کند و داده ها را از کد بومی به کد Kotlin ارسال می کند.
package com.example.models
// This class is used in the JNI bridge method signature
data class NativeData(val id: Int, val payload: String)
package com.example.app
// In package com.example.app
class JniBridge {
/**
* This method is called from the native side.
* R8 will remove it if it's not kept.
*/
fun onNativeEvent(data: NativeData) {
Log.d(TAG, "Received event from native code: $data")
}
// Use 'external' to declare a native method
external fun startNativeProcess()
companion object {
init {
// Load the native library
System.loadLibrary("my-native-lib")
}
}
}
در این صورت باید R8 را اطلاع دهید تا از بهینه سازی نوع اپلیکیشن جلوگیری شود. بهعلاوه، اگر روشهایی که از کد بومی فراخوانی میشوند، از کلاسهای خود در امضای خود بهعنوان پارامتر یا نوع بازگشتی استفاده میکنند، باید تأیید کنید که آن کلاسها تغییر نام نداده باشند.
قوانین حفظ زیر را به برنامه خود اضافه کنید:
-keepclassmembers,includedescriptorclasses class com.example.JniBridge {
public void onNativeEvent(com.example.model.NativeData);
}
-keep class NativeData{
<init>(java.lang.Integer, java.lang.String);
}
این قوانین مانع از حذف یا تغییر نام روش onNativeEvent
و نوع پارامتر آن توسط R8 می شود.
-
-keepclassmembers,includedescriptorclasses class com.example.JniBridge{ public void onNativeEvent(com.example.model.NativeData);}
: اعضای خاص یک کلاس را فقط در صورتی حفظ می کند که کلاس ابتدا در کد Kotlin یا جاوا نمونه سازی شده باشد—به R8 می گوید که برنامه از کلاس خاصی استفاده می کند و از کلاس استفاده می کند.-
-keepclassmembers
: فقط در صورتی اعضای خاص یک کلاس را حفظ می کند که کلاس ابتدا در کد Kotlin یا جاوا نمونه سازی شود—به R8 می گوید که برنامه از کلاس استفاده می کند و باید اعضای خاصی از کلاس را حفظ کند. -
class com.example.JniBridge
: این کلاس دقیقاً حاوی فیلد را هدف قرار می دهد. -
includedescriptorclasses
: این اصلاح کننده همچنین هر کلاسی را که در امضای متد یا توصیفگر یافت می شود حفظ می کند. در این حالت، R8 را از تغییر نام یا حذف کلاسcom.example.models.NativeData
که به عنوان پارامتر استفاده می شود، جلوگیری می کند. اگرNativeData
تغییر نام داده شود (مثلاً بهaa
)، امضای متد دیگر با آنچه کد بومی انتظار دارد مطابقت ندارد و باعث خرابی می شود. -
public void onNativeEvent(com.example.models.NativeData);
: این علامت دقیق جاوای متد را برای حفظ مشخص می کند.
-
-
-keep class NativeData{<init>(java.lang.Integer, java.lang.String);}
: در حالی کهincludedescriptorclasses
اطمینان حاصل می کند که خود کلاسNativeData
حفظ می شود، هر عضو (فیلد یا روش) درNativeData
که مستقیماً از کد JNI اصلی شما قابل دسترسی است نیاز به قوانین نگه داشتن خود دارد.-
-keep class NativeData
: کلاسی به نامNativeData
را هدف قرار می دهد و بلوک مشخص می کند که کدام اعضای کلاسNativeData
را نگه دارد. -
<init>(java.lang.Integer, java.lang.String)
: این امضای سازنده است. به طور منحصربهفرد سازندهای را که دو پارامتر میگیرد شناسایی میکند: اولی یکInteger
و دومی یکString
است.
-
تماس های پلت فرم غیر مستقیم
انتقال داده ها با پیاده سازی Parcelable
چارچوب Android از بازتاب برای ایجاد نمونه هایی از اشیاء Parcelable
شما استفاده می کند. در توسعه مدرن Kotlin، شما باید از پلاگین kotlin-parcelize
استفاده کنید، که به طور خودکار پیاده سازی Parcelable
لازم را ایجاد می کند، از جمله فیلد CREATOR
و روش هایی که چارچوب به آن نیاز دارد.
به عنوان مثال، مثال زیر را در نظر بگیرید که در آن از افزونه kotlin-parcelize
برای ایجاد یک کلاس Parcelable
استفاده می شود:
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
// Add the @Parcelize annotation to your data class
@Parcelize
data class UserData(
val name: String,
val age: Int
) : Parcelable
در این سناریو، یک قانون حفظ توصیه شده وجود ندارد. پلاگین kotlin-parcelize
Gradle به طور خودکار قوانین حفظ مورد نیاز را برای کلاس هایی که با @Parcelize
حاشیه نویسی می کنید ایجاد می کند. این پیچیدگی را برای شما کنترل میکند و مطمئن میشود که CREATOR
و سازندههای ایجاد شده برای فراخوانهای بازتابی چارچوب Android حفظ میشوند.
اگر یک کلاس Parcelable
به صورت دستی در Kotlin بدون استفاده از @Parcelize
بنویسید، شما مسئول حفظ فیلد CREATOR
و سازنده ای هستید که یک Parcel
می پذیرد. فراموشی انجام این کار باعث میشود برنامه شما زمانی که سیستم سعی میکند شیء شما را از حالت سریال خارج کند، از کار بیفتد. استفاده از @Parcelize
روش استاندارد و ایمن تر است.
هنگام استفاده از افزونه kotlin-parcelize
، به موارد زیر توجه داشته باشید:
- این افزونه به طور خودکار فیلدهای
CREATOR
را در حین کامپایل ایجاد می کند. - فایل
proguard-android-optimize.txt
حاوی قوانینkeep
لازم برای حفظ این فیلدها برای عملکرد مناسب است. - توسعهدهندگان برنامه باید تأیید کنند که همه قوانین
keep
الزامی وجود دارد، بهویژه برای اجرای سفارشی یا وابستگیهای شخص ثالث.