موارد و مثال های استفاده از قوانین را حفظ کنید

مثال‌های زیر بر اساس سناریوهای رایجی هستند که در آن از 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 الزامی وجود دارد، به‌ویژه برای اجرای سفارشی یا وابستگی‌های شخص ثالث.