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

مثال‌های زیر بر اساس سناریوهای رایجی هستند که در آن‌ها از R8 برای بهینه‌سازی استفاده می‌کنید، اما برای تهیه‌ی قوانین Keep به راهنمایی پیشرفته نیاز دارید.

بازتاب

به طور کلی، برای عملکرد بهینه، استفاده از reflection توصیه نمی‌شود. با این حال، در سناریوهای خاص، ممکن است اجتناب‌ناپذیر باشد. مثال‌های زیر راهنمایی‌هایی برای keep rules در سناریوهای رایجی که از reflection استفاده می‌کنند، ارائه می‌دهند.

بازتاب با کلاس‌های بارگذاری شده بر اساس نام

کتابخانه‌ها اغلب کلاس‌ها را به صورت پویا با استفاده از نام کلاس به عنوان یک String بارگذاری می‌کنند. با این حال، R8 نمی‌تواند کلاس‌هایی را که به این روش بارگذاری می‌شوند تشخیص دهد و ممکن است کلاس‌هایی را که استفاده نشده می‌دانند حذف کند.

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

بازتاب مبتنی بر حاشیه‌نویسی متد

کتابخانه‌ها اغلب حاشیه‌نویسی‌هایی را تعریف می‌کنند که توسعه‌دهندگان برای برچسب‌گذاری متدها یا فیلدها از آنها استفاده می‌کنند. سپس کتابخانه از reflection برای یافتن این اعضای حاشیه‌نویسی‌شده در زمان اجرا استفاده می‌کند. برای مثال، حاشیه‌نویسی @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 : این قانون ، annotationهایی را که قرار است در زمان اجرا خوانده شوند، حفظ می‌کند.
  • -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 ‎ حاشیه‌نویسی شده داشته باشد، و همچنین خود متدهای حاشیه‌نویسی شده را حفظ می‌کند.

بازتاب بر اساس حاشیه‌نویسی‌های کلاس

کتابخانه‌ها می‌توانند از reflection برای اسکن کلاس‌هایی که حاشیه‌نویسی خاصی دارند استفاده کنند. در این حالت، کلاس اجراکننده‌ی وظیفه با استفاده از reflection تمام کلاس‌هایی که با 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)
}

از آنجا که کتابخانه به صورت انعکاسی از reflection برای دریافت کلاس‌های خاص استفاده می‌کند، کتابخانه باید یک فایل قوانین مصرف‌کننده keep با قوانین keep زیر را در خود جای دهد:

# 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 می‌گوید چه چیزی را حفظ کند.

بازتاب برای پشتیبانی از وابستگی‌های اختیاری

یک مورد استفاده رایج برای reflection، ایجاد یک وابستگی نرم بین یک کتابخانه اصلی و یک کتابخانه افزونه اختیاری است. کتابخانه اصلی می‌تواند بررسی کند که آیا افزونه در برنامه گنجانده شده است یا خیر و در صورت وجود، می‌تواند ویژگی‌های اضافی را فعال کند. این به شما امکان می‌دهد ماژول‌های افزونه را بدون مجبور کردن کتابخانه اصلی به وابستگی مستقیم به آنها ارسال کنید.

کتابخانه اصلی از reflection ( 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 { /* ... */ }
}

توسعه‌دهنده‌ی کتابخانه‌ی اختیاری مسئول ارائه‌ی قانون لازم برای مصرف‌کننده به نام keep است. این قانون باعث می‌شود هر برنامه‌ای که از کتابخانه‌ی اختیاری استفاده می‌کند، کدی را که کتابخانه‌ی اصلی باید پیدا کند، حفظ کند.

# In the video library's consumer keep rules file
-keep class com.example.analytics.video.VideoEventTracker {
    <init>();
}

بدون این قانون، R8 احتمالاً VideoEventTracker از کتابخانه اختیاری حذف می‌کند، زیرا هیچ چیز در آن ماژول مستقیماً از آن استفاده نمی‌کند. قانون keep کلاس و سازنده آن را حفظ می‌کند و به کتابخانه اصلی اجازه می‌دهد تا با موفقیت آن را نمونه‌سازی کند.

بازتاب برای دسترسی به اعضای خصوصی

استفاده از reflection برای دسترسی به کد خصوصی یا محافظت‌شده‌ای که بخشی از API عمومی کتابخانه نیست، می‌تواند مشکلات قابل توجهی ایجاد کند. چنین کدی بدون اطلاع قبلی قابل تغییر است که می‌تواند منجر به رفتار غیرمنتظره یا خرابی در برنامه شما شود.

وقتی برای API های غیر عمومی به reflection تکیه می‌کنید، ممکن است با مشکلات زیر روبرو شوید:

  • به‌روزرسانی‌های مسدود شده: تغییرات در کد خصوصی یا محافظت‌شده می‌تواند مانع از به‌روزرسانی شما به نسخه‌های بالاتر کتابخانه شود.
  • مزایای از دست رفته: ممکن است قابلیت‌های جدید، رفع اشکالات مهم یا به‌روزرسانی‌های امنیتی ضروری را از دست بدهید.

بهینه‌سازی‌ها و بازتاب‌های 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
}

برای جلوگیری از حذف فیلد private توسط R8، یک قانون -keep به برنامه خود اضافه کنید:

-keepclassmembers class com.example.LibraryClass {
    private java.lang.String secretMessage;
}
  • -keepclassmembers : این فقط در صورتی اعضای خاص یک کلاس را حفظ می‌کند که خود کلاس حفظ شده باشد.
  • class com.example.LibraryClass : این دقیقاً کلاسی را که حاوی فیلد است، هدف قرار می‌دهد.
  • private java.lang.String secretMessage; : این فیلد private خاص را با نام و نوع آن مشخص می‌کند.

رابط بومی جاوا (JNI)

بهینه‌سازی‌های R8 می‌تواند هنگام کار با فراخوانی‌های بالا از کد بومی (کد C/C++) به جاوا یا کاتلین با مشکلاتی مواجه شود. در حالی که عکس این قضیه نیز صادق است - فراخوانی‌های پایین از جاوا یا کاتلین به کد بومی می‌تواند با مشکلاتی مواجه شود - فایل پیش‌فرض proguard-android-optimize.txt شامل قانون زیر است تا فراخوانی‌های پایین را فعال نگه دارد. این قانون از حذف شدن متدهای بومی جلوگیری می‌کند.

-keepclasseswithmembernames,includedescriptorclasses class * {
  native <methods>;
}

تعامل با کد بومی از طریق رابط بومی جاوا (JNI)

وقتی برنامه شما از JNI برای فراخوانی‌های آپ‌کال از کد نیتیو (C/C++) به جاوا یا کاتلین استفاده می‌کند، R8 نمی‌تواند ببیند کدام متدها از کد نیتیو شما فراخوانی می‌شوند. اگر هیچ ارجاع مستقیمی به این متدها در برنامه شما وجود نداشته باشد، R8 به اشتباه فرض می‌کند که این متدها بلااستفاده هستند و آنها را حذف می‌کند که باعث خرابی برنامه شما می‌شود.

مثال زیر یک کلاس کاتلین را با متدی که قرار است از یک کتابخانه بومی فراخوانی شود، نشان می‌دهد. کتابخانه بومی یک نوع برنامه را نمونه‌سازی می‌کند و داده‌ها را از کد بومی به کد کاتلین منتقل می‌کند.

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 اطلاع دهید تا از بهینه‌سازی نوع برنامه جلوگیری کند. علاوه بر این، اگر متدهایی که از کد بومی فراخوانی می‌شوند از کلاس‌های خودتان در امضاهایشان به عنوان پارامتر یا نوع بازگشتی استفاده می‌کنند، باید تأیید کنید که آن کلاس‌ها تغییر نام نداده باشند.

قوانین keep زیر را به برنامه خود اضافه کنید:

-keepclassmembers,includedescriptorclasses class com.example.JniBridge {
    public void onNativeEvent(com.example.model.NativeData);
}

-keep class NativeData{
        <init>(java.lang.Integer, java.lang.String);
}

این قوانین مانع از آن می‌شوند که R8 متد onNativeEvent و مهم‌تر از همه نوع پارامتر آن را حذف یا تغییر نام دهد.

  • -keepclassmembers,includedescriptorclasses class com.example.JniBridge{ public void onNativeEvent(com.example.model.NativeData);} ‎: این فقط در صورتی اعضای خاصی از یک کلاس را حفظ می‌کند که کلاس ابتدا در کد کاتلین یا جاوا نمونه‌سازی شده باشد - این به R8 می‌گوید که برنامه از کلاس استفاده می‌کند و باید اعضای خاصی از کلاس را حفظ کند.
    • -keepclassmembers : این فقط اعضای خاصی از یک کلاس را در صورتی حفظ می‌کند که کلاس ابتدا در کد کاتلین یا جاوا نمونه‌سازی شده باشد - این به R8 می‌گوید که برنامه از کلاس استفاده می‌کند و باید اعضای خاصی از کلاس را حفظ کند.
    • class com.example.JniBridge : این دقیقاً کلاسی را که حاوی فیلد است، هدف قرار می‌دهد.
    • includedescriptorclasses : این اصلاح‌کننده همچنین هر کلاسی را که در امضای متد یا توصیف‌کننده آن یافت می‌شود، حفظ می‌کند. در این حالت، از تغییر نام یا حذف کلاس com.example.models.NativeData که به عنوان پارامتر استفاده می‌شود توسط R8 جلوگیری می‌کند. اگر NativeData تغییر نام داده شود (مثلاً به aa )، امضای متد دیگر با آنچه کد native انتظار دارد مطابقت نخواهد داشت و باعث خرابی می‌شود.
    • public void onNativeEvent(com.example.models.NativeData); : این مقدار امضای دقیق جاوای متدی که قرار است حفظ شود را مشخص می‌کند.
  • -keep class NativeData{<init>(java.lang.Integer, java.lang.String);} : در حالی که includedescriptorclasses تضمین می‌کند که خود کلاس NativeData حفظ شود، هر عضوی (فیلد یا متد) درون NativeData که مستقیماً از کد JNI بومی شما قابل دسترسی باشد، به قوانین keep مخصوص به خود نیاز دارد.
    • -keep class NativeData : این کلاسی با نام NativeData را هدف قرار می‌دهد و بلوک مشخص می‌کند که کدام اعضای داخل کلاس NativeData باید نگه داشته شوند.
    • <init>(java.lang.Integer, java.lang.String) : این امضای سازنده است. این به طور منحصر به فرد سازنده‌ای را مشخص می‌کند که دو پارامتر می‌گیرد: اولی یک Integer و دومی یک رشته ( String ).

تماس‌های غیرمستقیم پلتفرم

انتقال داده با پیاده‌سازی Parcelable

چارچوب اندروید از reflection برای ایجاد نمونه‌هایی از اشیاء 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

در این سناریو، هیچ قانون keep توصیه‌شده‌ای وجود ندارد. افزونه‌ی kotlin-parcelize Gradle به‌طور خودکار قوانین keep مورد نیاز را برای کلاس‌هایی که با @Parcelize حاشیه‌نویسی می‌کنید، تولید می‌کند. این افزونه پیچیدگی را برای شما مدیریت می‌کند و اطمینان حاصل می‌کند که CREATOR و سازنده‌های تولید شده برای فراخوانی‌های reflection چارچوب اندروید حفظ می‌شوند.

اگر یک کلاس Parcelable را به صورت دستی در کاتلین و بدون استفاده از @Parcelize بنویسید، مسئول نگه داشتن فیلد CREATOR و سازنده‌ای که یک Parcel را می‌پذیرد، هستید. فراموش کردن این کار باعث می‌شود برنامه شما هنگام تلاش سیستم برای deserialize کردن شیء شما، از کار بیفتد. استفاده از @Parcelize روش استاندارد و ایمن‌تری است.

هنگام استفاده از افزونه kotlin-parcelize ، به موارد زیر توجه داشته باشید:

  • این افزونه به طور خودکار فیلدهای CREATOR را در حین کامپایل ایجاد می‌کند.
  • فایل proguard-android-optimize.txt شامل قوانین لازم برای keep این فیلدها و عملکرد صحیح آن‌ها است.
  • توسعه‌دهندگان برنامه باید تأیید کنند که تمام قوانین مورد نیاز keep ، به ویژه برای هرگونه پیاده‌سازی سفارشی یا وابستگی‌های شخص ثالث، وجود دارند.