افزودن قوانین نگهداری

در سطح بالا، یک قانون نگهداری یک کلاس (یا زیرکلاس یا پیاده‌سازی) را مشخص می‌کند، و سپس اعضا—روش‌ها، سازنده‌ها، یا فیلدها—درون آن کلاس برای حفظ کردن.

نحو کلی برای قانون حفظ به‌صورت زیر است:


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

در زیر نمونه‌ای از قانون نگهداری آورده شده است که از keepclassmembers به‌عنوان گزینه نگهداری، allowoptimization به‌عنوان اصلاح‌گر، و someSpecificMethod() از com.example.MyClass را نگه می‌دارد:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

گزینه «نگه‌داشتن»

گزینه «نگه‌داشتن» اولین بخش از قانون نگه‌داشتن شما است. این مورد مشخص می‌کند که چه جنبه‌هایی از کلاس باید حفظ شود. شش گزینه مختلف برای نگهداری وجود دارد، یعنی keep، keepclassmembers، keepclasseswithmembers، keepnames، keepclassmembernames، و keepclasseswithmembernames.

جدول زیر این گزینه‌های نگهداری را شرح می‌دهد:

گزینه «نگه داشتن» توضیحات
keepclassmembers اعضای مشخص‌شده را فقط درصورتی حفظ می‌کند که R8 کلاسی را که حاوی آن‌ها است حذف نکند.
keep کلاس‌های مشخص‌شده و اعضای مشخص‌شده (فیلدها و روش‌ها) را حفظ می‌کند و مانع بهینه‌سازی آن‌ها می‌شود.

توجه: keep معمولاً باید فقط با اصلاح‌کننده‌های گزینه keep استفاده شود زیرا keep به‌خودی‌خود از انجام هرگونه بهینه‌سازی در کلاس‌های منطبق جلوگیری می‌کند.
keepclasseswithmembers کلاس و اعضای مشخص‌شده آن را فقط درصورتی حفظ می‌کند که کلاس همه اعضای مشخص‌شده در کلاس را داشته باشد.
keepclassmembernames از تغییر نام اعضای کلاس مشخص‌شده جلوگیری می‌کند، اما از برداشته شدن کلاس یا اعضای آن جلوگیری نمی‌کند.

توجه: معنی این گزینه اغلب اشتباه درک می‌شود؛ به‌جای آن از معادل -keepclassmembers,allowshrinking استفاده کنید.
keepnames از تغییر نام کلاس‌ها و اعضای آن‌ها جلوگیری می‌کند، اما اگر کلاس‌ها و اعضا استفاده‌نشده تلقی شوند، از حذف کامل آن‌ها جلوگیری نمی‌کند.

توجه: معنی این گزینه اغلب اشتباه فهمیده می‌شود؛ به‌جای آن از معادل -keep,allowshrinking استفاده کنید.
keepclasseswithmembernames از تغییر نام کلاس‌ها و اعضای مشخص‌شده آن‌ها جلوگیری می‌کند، اما فقط درصورتی‌که اعضا در کد نهایی وجود داشته باشند. این گزینه از برداشتن کد جلوگیری نمی‌کند.

توجه: معنای این گزینه اغلب اشتباه درک می‌شود؛ به‌جای آن از -keepclasseswithmembers,allowshrinking معادل استفاده کنید.

گزینه نگهداری مناسب را انتخاب کنید

انتخاب گزینه نگهداری مناسب در تعیین بهینه‌سازی مناسب برای برنامه‌تان بسیار مهم است. برخی‌از گزینه‌های نگهداری کد را کوچک می‌کنند، فرایندی که طی آن کد بدون ارجاع برداشته می‌شود، درحالی‌که برخی دیگر کد را مبهم می‌کنند یا نام آن را تغییر می‌دهند. جدول زیر کنش‌های گزینه‌های مختلف نگهداری را نشان می‌دهد:

گزینه «حفظ کردن» کلاس‌ها را کوچک می‌کند کلاس‌ها را مبهم می‌کند اعضا را کوچک می‌کند اعضا را مبهم می‌کند
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

اصلاح‌کننده گزینه نگهداری

از اصلاح‌کننده گزینه نگهداری برای کنترل دامنه و رفتار قانون نگهداری استفاده می‌شود. می‌توانید ۰ یا چند اصلاح‌کننده گزینه حفظ به قانون حفظتان اضافه کنید.

مقادیر ممکن برای اصلاح‌کننده گزینه نگهداری در جدول زیر شرح داده شده است:

مقدار شرح
allowoptimization بهینه‌سازی عناصر مشخص‌شده را مجاز می‌کند. بااین‌حال، عناصر مشخص‌شده تغییر نام نمی‌دهند یا برداشته نمی‌شوند.
allowobfuscation امکان تغییر نام عناصر مشخص‌شده را فراهم می‌کند. اما عناصر برداشته نمی‌شوند یا بهینه‌سازی دیگری روی آن‌ها انجام نمی‌شود.
allowshrinking اگر R8 ارجاعی به عناصر مشخص‌شده پیدا نکند، اجازه می‌دهد آن‌ها برداشته شوند. اما عناصر تغییر نام نمی‌دهند یا به روش دیگری بهینه‌سازی نمی‌شوند.
includedescriptorclasses به R8 دستور می‌دهد همه کلاس‌هایی را که در توصیف‌کننده‌های روش‌ها (انواع پارامتر و انواع پاسخ) و فیلدها (انواع فیلد) ظاهر می‌شوند حفظ کند.
allowaccessmodification به R8 اجازه می‌دهد تا اصلاح‌کننده‌های دسترسی (public، private، protected) کلاس‌ها، روش‌ها، و فیلدها را درطول فرایند بهینه‌سازی تغییر دهد (معمولاً گسترش دهد).
allowrepackage به R8 اجازه می‌دهد کلاس‌ها را به بسته‌های مختلف، ازجمله بسته پیش‌فرض (ریشه)، منتقل کند.

مشخصات کلاس

باید کلاسی (شامل کلاس‌های رابط، شمارشی، و شرح) را در هر قانون نگهداری مشخص کنید. می‌توانید به‌طور اختیاری قانون را براساس حاشیه‌نویسی‌ها، با مشخص کردن یک سوپرکلاس یا رابط پیاده‌سازی‌شده، یا با مشخص کردن اصلاح‌کننده دسترسی برای کلاس محدود کنید. همه کلاس‌ها، ازجمله کلاس‌های فضای نام java.lang مانند java.lang.String، باید بااستفاده از نام کامل Java آن‌ها مشخص شوند. برای درک نام‌هایی که باید استفاده شوند، بایت‌کد را بااستفاده از ابزارهای شرح‌داده‌شده در بازرسی نام‌های Java تولیدشده بازرسی کنید.

مثال زیر نشان می‌دهد که چگونه باید کلاس MaterialButton را مشخص کنید:

  • درست: com.google.android.material.button.MaterialButton
  • نادرست: MaterialButton

مشخصات کلاس همچنین اعضای درون کلاسی را که باید حفظ شوند مشخص می‌کند. برای مثال، قانون زیر کلاس MyClass و روش someSpecificMethod() را حفظ می‌کند:

-keep class com.example.MyClass {
  void someSpecificMethod();
}

مشخص کردن کلاس‌ها براساس حاشیه‌نویسی‌ها

برای مشخص کردن کلاس‌ها براساس گزارمان‌های آن‌ها، نام Java کاملاً واجدشرایط گزارمان را با نماد @ پیشوندگذاری کنید. برای مثال:

-keep class @com.example.MyAnnotation com.example.MyClass

اگر قانون نگهداری بیش‌از یک حاشیه‌نویسی داشته باشد، کلاس‌هایی را که همه حاشیه‌نویسی‌های فهرست‌شده را دارند نگه می‌دارد. می‌توانید چند حاشیه‌نویسی را فهرست کنید، اما این قانون فقط درصورتی اعمال می‌شود که کلاس همه حاشیه‌نویسی‌های فهرست‌شده را داشته باشد. برای مثال، قانون زیر همه کلاس‌های حاشیه‌نویسی‌شده توسط Annotation1 و Annotation2 را نگه می‌دارد.

-keep class @com.example.Annotation1 @com.example.Annotation2 *

زیرکلاس‌ها و پیاده‌سازی‌ها را مشخص کنید

برای هدف‌یابی زیرکلاس یا کلاسی که رابط را پیاده‌سازی می‌کند، به‌ترتیب از extend و implements استفاده کنید.

برای مثال، اگر کلاس Bar با زیرکلاس Foo به‌صورت زیر دارید:

class Foo : Bar()

قانون حفظ زیر همه زیرکلاس‌های Bar را حفظ می‌کند. توجه داشته باشید که قانون نگه‌داشتن شامل خود ابرکلاس Bar نمی‌شود.

-keep class * extends Bar

اگر کلاس Foo را دارید که میانای Bar را پیاده‌سازی می‌کند:

class Foo : Bar

قانون نگهداری زیر همه کلاس‌هایی را که Bar را پیاده‌سازی می‌کنند حفظ می‌کند. توجه داشته باشید که قانون نگهداری شامل خود رابط Bar نمی‌شود.

-keep class * implements Bar

مشخص کردن کلاس‌ها براساس اصلاح‌کننده‌های دسترسی

می‌توانید اصلاح‌کننده‌های دسترسی مانند public، private، static، و final را مشخص کنید تا قوانین نگهداری‌تان دقیق‌تر شود.

برای مثال، قانون زیر همه کلاس‌های public را در بسته api و زیربسته‌های آن، و همه اعضای عمومی و محافظت‌شده را در این کلاس‌ها نگه می‌دارد.

-keep public class com.example.api.** { public protected *; }

همچنین می‌توانید از اصلاح‌کننده‌ها برای اعضای درون کلاس استفاده کنید. برای مثال، قانون زیر فقط روش‌های public static کلاس Utils را حفظ می‌کند:

-keep class com.example.Utils {
  public static void *(...);
}

اصلاح‌گرهای مختص Kotlin

‫R8 از اصلاح‌کننده‌های مختص Kotlin مثل internal و suspend پشتیبانی نمی‌کند. از دستورالعمل‌های زیر برای حفظ چنین فیلدهایی استفاده کنید.

  • برای حفظ کلاس، روش، یا فیلد internal، آن را به‌عنوان عمومی درنظر بگیرید. برای مثال، منبع Kotlin زیر را درنظر بگیرید:

    package com.example
    internal class ImportantInternalClass {
      internal val f: Int
      internal fun m() {}
    }
    

    کلاس‌ها، روش‌ها، و فیلدهای internal در .class فایل‌های تولیدشده توسط کامپایلر Kotlin public هستند، بنابراین باید از کلیدواژه public همان‌طور که در مثال زیر نشان داده شده است استفاده کنید:

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • وقتی عضو suspend کامپایل می‌شود، امضای بایت‌کد کامپایل‌شده آن را در قانون حفظ مطابقت دهید.

    برای مثال، کلاس مخزن واقعی و ملموس زیر را که در Kotlin تعریف شده است درنظر بگیرید:

    package com.example.repository
    
    import com.example.model.User
    
    class UserRepository {
        suspend fun fetchUser(id: String): User {
            // Implementation details...
        }
    }
    

    وقتی کامپایلر Kotlin این کلاس را به بایت‌کد کامپایل می‌کند، توابع suspend تحت تبدیل «سبک انتقال ادامه» (CPS) قرار می‌گیرند. تغییردهنده suspend برداشته می‌شود، نوع پاسخ به java.lang.Object تغییر می‌کند، و پارامتر kotlin.coroutines.Continuation به امضای روش اضافه می‌شود تا ماشین حالت غیرهم‌زمان را مدیریت کند.

    امضای روش کامپایل‌شده در بایت‌کد به‌صورت زیر ظاهر می‌شود:

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    برای نوشتن قانون نگهداری برای این تابع، نمی‌توانید از نحو Kotlin suspend استفاده کنید. درعوض، باید امضای بایت‌کد کامپایل‌شده را مطابقت دهید (به‌طور خاص با ارجاع به kotlin.coroutines.Continuation)، یا از ... استفاده کنید.

    نمونه‌ای از مطابقت با امضای دقیق بایت‌کد کامپایل‌شده به‌شرح زیر است:

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation);
    }
    

    نمونه‌ای از استفاده از ... به‌صورت زیر است:

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(...);
    }
    

مشخصات عضو

مشخصات کلاس به‌صورت اختیاری شامل اعضای کلاسی است که باید حفظ شوند. اگر یک یا چند عضو را برای کلاسی مشخص کنید، قانون برای سایر اعضا اعمال نمی‌شود.

مشخص کردن اعضا براساس حاشیه‌نویسی‌ها

می‌توانید اعضا را براساس حاشیه‌نویسی‌هایشان مشخص کنید. مشابه کلاس‌ها، نام Java کاملاً واجدشرایط حاشیه‌نویسی را با @ پیشوند می‌کنید. این به شما امکان می‌دهد فقط آن دسته از اعضای کلاس را نگه دارید که با حاشیه‌نویسی‌های خاصی علامت‌گذاری شده‌اند. برای مثال، برای حفظ روش‌ها و فیلدهای حاشیه‌نویسی‌شده با @com.example.MyAnnotation:

-keep class com.example.MyClass {
  @com.example.MyAnnotation <methods>;
  @com.example.MyAnnotation <fields>;
}

می‌توانید این را با مطابقت دادن شرح در سطح کلاس برای قوانین قدرتمند و هدفمند ترکیب کنید:

-keep class @com.example.ClassAnnotation * {
  @com.example.MethodAnnotation <methods>;
  @com.example.FieldAnnotation <fields>;
}

این کار کلاس‌های حاشیه‌نویسی‌شده با @ClassAnnotation را حفظ می‌کند و در آن کلاس‌ها، روش‌های حاشیه‌نویسی‌شده با @MethodAnnotation و فیلدهای حاشیه‌نویسی‌شده با @FieldAnnotation را حفظ می‌کند.

درصورت امکان، از قوانین نگهداری مبتنی بر حاشیه‌نویسی استفاده کنید. این رویکرد پیوندی صریح بین کد شما و قوانین نگهداری‌تان ایجاد می‌کند و اغلب به پیکربندی‌های قوی‌تری منجر می‌شود. کتابخانه گزارمان androidx.annotation، برای مثال، از این سازوکار استفاده می‌کند.

برای بازتاب در کد خودتان، می‌توانید از Jetpack Keep نیز استفاده کنید. به‌جای نوشتن قوانین نگهداری که حاشیه‌نویسی‌ها را هدف قرار می‌دهند، کد خود را حاشیه‌نویسی می‌کنید، و یک افزونه Gradle قوانین نگهداری را برای شما تولید می‌کند.

روش‌ها

نحو برای مشخص کردن روش در مشخصات عضو برای قانون نگهداری به‌صورت زیر است:

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

برای مثال، قانون نگهداری زیر روش عمومی به‌نام getUserId() را که String برمی‌گرداند نگه می‌دارد.

-keep class com.example.model.UserData {
    public java.lang.String getUserId();
}

می‌توانید از <methods> به‌عنوان میان‌بری برای مطابقت دادن همه روش‌ها در کلاس به‌صورت زیر استفاده کنید:

-keep class com.example.model.UserData {
    <methods>;
}

برای کسب اطلاعات بیشتر درباره نحوه مشخص کردن انواع برای انواع برگشتی و انواع پارامتر، به انواع مراجعه کنید.

سازندگان

برای مشخص کردن سازنده، از <init> استفاده کنید. نحو مشخص کردن سازنده در مشخصات عضو برای قانون حفظ به‌صورت زیر است:

[<access_modifier>] <init>(parameter_types);

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

-keep class com.example.ui.state.UserViewModel {
    public <init>(com.example.repository.UserDataRepository);
}

برای حفظ همه سازنده‌های عمومی، از مثال زیر به‌عنوان مرجع استفاده کنید:

-keep class com.example.ui.state.UserViewModel {
    public <init>(...);
}

دشت

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

[<access_modifier>...] [<type>] <field_name>;

برای مثال، قانون حفظ زیر یک فیلد رشته خصوصی به‌نام userId و یک فیلد عدد صحیح ثابت عمومی به‌نام STATUS_ACTIVE را حفظ می‌کند:

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

می‌توانید از <fields> به‌عنوان میان‌بر برای مطابقت دادن همه فیلدهای کلاس به‌صورت زیر استفاده کنید:

-keep class com.example.models.User {
    <fields>;
}

انواع

این بخش نحوه مشخص کردن انواع پاسخ، انواع پارامتر، و انواع فیلد را در مشخصات عضو قانون حفظ کردن شرح می‌دهد. به‌یاد داشته باشید که اگر انواع با کد منبع Kotlin متفاوت است، از نام‌های Java تولیدشده برای مشخص کردن انواع استفاده کنید.

انواع ابتدایی

برای مشخص کردن نوع اولیه، از کلیدواژه Java آن استفاده کنید. R8 انواع اولیه زیر را تشخیص می‌دهد: boolean، byte، short، char، int، long، float، double.

نمونه‌ای از قانون با نوع اولیه به‌صورت زیر است:

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

انواع عمومی

درطول کامپایل، کامپایلر Kotlin/Java اطلاعات نوع عمومی را پاک می‌کند، بنابراین وقتی قوانین نگهداری را می‌نویسید که شامل انواع عمومی است، باید نمایش کامپایل‌شده کدتان را هدف قرار دهید، نه کد منبع اصلی را. برای آشنایی بیشتر با نحوه تغییر انواع عمومی، محو کردن نوع را ببینید.

برای مثال، اگر کد زیر را با نوع عمومی نامحدود تعریف‌شده در Box.kt دارید:

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

پس‌از پاک کردن نوع، T با Object جایگزین می‌شود. برای حفظ سازنده و روش کلاس، قانون شما باید به‌جای عمومی T از java.lang.Object استفاده کند.

نمونه‌ای از قانون نگهداری به‌صورت زیر است:

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

اگر کد زیر را با نوع عمومی محدودشده در NumberBox.kt دارید:

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

در این مورد، پاک کردن نوع T را با کران آن، java.lang.Number، جایگزین می‌کند.

نمونه‌ای از قانون نگهداری به‌صورت زیر است:

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

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

برای مثال، برای کد زیر:

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

می‌توانید از قانون نگهداری با includedescriptorclasses برای حفظ کردن هم کلاس UnpackOptions و هم روش کلاس Box با یک قانون به‌صورت زیر استفاده کنید:

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

برای حفظ تابع خاصی که فهرستی از اشیاء را پردازش می‌کند، باید قاعده‌ای بنویسید که دقیقاً با امضای تابع مطابقت داشته باشد. توجه داشته باشید که چون انواع عمومی پاک می‌شوند، پارامتری مثل List<Product> به‌صورت java.util.List دیده می‌شود.

برای مثال، اگر کلاس سودمندی با تابعی دارید که فهرستی از اشیاء Product را به‌صورت زیر پردازش می‌کند:

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

می‌توانید از قانون حفظ زیر برای محافظت از فقط تابع processProducts استفاده کنید:

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

انواع آرایه

نوع آرایه را با افزودن [] به نوع مؤلفه برای هر بُعد آرایه مشخص کنید. این امر هم برای انواع کلاس و هم برای انواع ابتدایی اعمال می‌شود.

  • آرایه کلاس یک‌بعدی: java.lang.String[]
  • آرایه اولیه دوبعدی: int[][]

برای مثال، اگر کد زیر را داشته باشید:

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

می‌توانید از قانون نگهداری زیر استفاده کنید:

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

مثال‌ها

برای مثال، برای حفظ یک کلاس خاص و همه اعضای آن، از این استفاده کنید:

-keep class com.myapp.MyClass { *; }

برای حفظ کردن فقط کلاس، همراه با سازنده پیش‌فرض آن، اما نه اعضای دیگر، از موارد زیر استفاده کنید:

-keep class com.myapp.MyClass

توصیه می‌شود که همیشه چند عضو را مشخص کنید. برای مثال، نمونه زیر فیلد عمومی text و روش عمومی updateText() را در کلاس MyClass نگه می‌دارد.

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

برای حفظ همه فیلدهای عمومی و روش‌های عمومی، مثال زیر را ببینید:

-keep public class com.example.api.ApiClient {
    public *;
}

مشخصات عضو را حذف کنید

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

برای مثال، اگر -keep class com.example.MyClass یا -keep class com.example.MyClass {} را بنویسید، R8 با آن‌ها طوری رفتار می‌کند که انگار موارد زیر را نوشته‌اید:

-keep class com.example.MyClass{
  void <init>();
}

لغو کردن الگوهای نام عضو

از «افزایه Android Gradle»‏ (AGP) نسخه ۹.۲.۰، می‌توانید الگوهای نام عضو را در قوانین نگهداری‌تان نفی کنید. این کار به شما امکان می‌دهد اعضایی را برای نگه‌داشتن یا مستثنی کردن براساس الگوها مشخص کنید. برای نفی کردن الگو، پیشوند الگوی نام عضو را با علامت تعجب (!) مشخص کنید.

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

مثال:

برای نگه داشتن همه روش‌های عمومی در com.example.MyClass به‌جز روش‌هایی که با «ForTesting» تمام می‌شوند، از قانون زیر استفاده کنید:

-keepclassmembers class com.example.MyClass {
    public *** !*ForTesting(...);
}

توابع سطح بسته

برای ارجاع دادن به تابع Kotlin که خارج از کلاس تعریف شده است (که معمولاً توابع سطح بالا نامیده می‌شود)، حتماً از نام جاوا تولیدشده برای کلاسی که به‌طور ضمنی توسط کامپایلر Kotlin اضافه شده است استفاده کنید. نام کلاس همان نام فایل Kotlin است که Kt به آن اضافه شده است. برای مثال، اگر فایل Kotlin به‌نام MyClass.kt دارید که به‌صورت زیر تعریف شده است:

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

برای نوشتن قانون نگهداری برای تابع isEmailValid، مشخصات کلاس باید کلاس تولیدشده MyClassKt را هدف قرار دهد:

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

نویسه‌های عام

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

کارت جوکر برای کلاس‌ها یا اعضا اعمال می‌شود شرح
** هر دو پراستفاده‌ترین. با هر نام نوعی، ازجمله هر تعداد جداکننده بسته، مطابقت دارد. این برای مطابقت دادن همه کلاس‌های درون یک بسته و بسته‌های فرعی آن مفید است.
* هر دو برای مشخصات کلاس، با هر بخشی از نام نوع که حاوی جداکننده‌های بسته (.) نباشد مطابقت دارد
برای مشخصات عضو، با هر روش یا نام فیلدی مطابقت دارد. وقتی به‌تنهایی استفاده شود، نام مستعاری برای ** نیز هست.
؟ هر دو با هر تک‌حرفی در نام کلاس یا عضو مطابقت دارد.
*** اعضا با هر نوعی، ازجمله انواع اولیه (مثل int)، انواع کلاس (مثل java.lang.String)، و انواع آرایه با هر ابعادی (مثل byte[][]) مطابقت دارد.
… اعضا با هر فهرستی از پارامترها برای یک روش مطابقت دارد.
٪ اعضا با هر نوع اولیه (مانند int، float، boolean، یا موارد دیگر) مطابقت دارد.

در اینجا چند مثال از نحوه استفاده از نویسه‌های عام ویژه آورده شده است:

  • اگر چند روش با نام یکسان دارید که انواع اولیه مختلفی را به‌عنوان ورودی می‌گیرند، می‌توانید از % برای نوشتن قانون حفظی استفاده کنید که همه آن‌ها را حفظ می‌کند. برای مثال، این کلاس DataStore چندین روش setValue دارد:

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    قانون نگهداری زیر همه روش‌ها را نگه می‌دارد:

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • اگر چندین کلاس دارید که نام‌هایشان با یک نویسه متفاوت است، از ? برای نوشتن قانون نگهداری استفاده کنید که همه آن‌ها را نگه می‌دارد. برای مثال، اگر کلاس‌های زیر را داشته باشید:

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    قانون نگهداری زیر همه کلاس‌ها را نگه می‌دارد:

    -keep class com.example.models.UserV?
    
  • برای مطابقت دادن کلاس‌های Example و AnotherExample (اگر کلاس‌های سطح ریشه باشند)، اما نه com.foo.Example، از قانون حفظ زیر استفاده کنید:

    -keep class *Example
    
  • اگر از * به‌تنهایی استفاده کنید، به‌عنوان نام مستعار برای ** عمل می‌کند. برای مثال، قوانین نگهداری زیر معادل هستند:

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

قوانین نگهداری مشروط

علاوه‌بر قوانین نگهداری استاندارد، می‌توانید از قوانین نگهداری شرطی استفاده کنید که فقط درصورت برآورده شدن شرایط خاصی اعمال می‌شوند. بااستفاده از پرچم -if می‌توانید قوانین شرطی را مشخص کنید. قانون حفظی که پس‌از پرچم -if می‌آید فقط درصورتی فعال است که مشخصات کلاس در پرچم -if مطابقت داشته باشد.

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

نحو کلی برای قانون نگهداری شرطی به‌صورت زیر است:

-if <class_specification_if> <keep_rule>

اگر مشخصات کلاس در شرط -if حاوی نویسه‌های عام (مانند * یا **) باشد، توالی نویسه‌هایی که با نویسه عام مطابقت دارند ضبط می‌شود. می‌توانید بااستفاده از ارجاع‌های برگشتی به این رشته‌های ضبط‌شده در قانون نگهداری بعدی ارجاع دهید: <1> به رشته ضبط‌شده توسط حرف عام اول ارجاع می‌دهد، <2> به رشته ضبط‌شده توسط حرف عام دوم ارجاع می‌دهد، و به همین ترتیب.

برای مثال، عنصر «پیمایش Jetpack» NavArgs کلاس برای انتقال آرگومان نوع امن بین مقصدها تولید می‌کند. هنگام استفاده از نماینده NavArgsLazy، از بازتاب برای یافتن و فراخوانی روش fromBundle ثابت در کلاس NavArgs تولیدشده برای واگرداندن استدلال‌ها به حالت اولیه استفاده می‌کند. اگر برنامه‌تان از NavArgs استفاده می‌کند، فقط باید روش fromBundle را برای کلاس‌هایی که میانای NavArgs را پیاده‌سازی می‌کنند نگه دارید.

می‌توانید از قانون حفظ شرطی استفاده کنید تا مشخص کنید که اگر کلاسی androidx.navigation.NavArgs را پیاده‌سازی کند، R8 باید روش fromBundle را برای آن کلاس خاص حفظ کند:

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

در این مثال، ** اولین و تنها نویسه عام در شرط -if است. با نام کلاس هر کلاسی که androidx.navigation.NavArgs را پیاده‌سازی می‌کند مطابقت دارد. رشته‌ای که ** مطابقت دارد (در این مورد، نام کلاس) ضبط می‌شود و می‌توانید بااستفاده از <1> در قانون بعدی به آن ارجاع دهید. بنابراین قانون -keepclassmembers برای هر کلاسی که androidx.navigation.NavArgs را پیاده‌سازی می‌کند و با شرط -if مطابقت دارد اعمال می‌شود. اگر R8 کلاسی را پیدا نکند که NavArgs را پیاده‌سازی کند، این قانون نگه‌داشتن را نادیده می‌گیرد.

مورد استفاده رایج دیگر، کتابخانه‌های سریال‌سازی JSON مانند Gson است. اگر کلاس‌های مدل داده شما از حاشیه‌نویسی @SerializedName Gson در هر فیلدی استفاده می‌کنند، می‌توانید از قانون شرطی برای محافظت از هر کلاس و اعضای آن که Gson برای بازتاب نیاز دارد استفاده کنید:

# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
    @com.google.gson.annotations.SerializedName <fields>;
    <init>(...);
}

ارجاع‌های برگشتی رشته‌ها را ضبط می‌کنند، که اگر نویسه عام فقط با بخشی از نام مطابقت داشته باشد، می‌تواند رشته‌های فرعی نام کلاس باشد. برای مثال، اگر از -if class com.example.*X* استفاده کنید، R8 زیررشته قبل‌از X را به‌عنوان <1> و زیررشته بعداز X را به‌عنوان <2> ضبط می‌کند. قانون زیر از این برای یافتن هر نام کلاس حاوی X و حفظ کلاس مربوطه که در آن X با Y جایگزین شده است استفاده می‌کند:

# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>

قوانین نگهداری مشروط برای بازتاب

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

کتابخانه Jetpack Navigation از انعکاس بااستفاده از نماینده NavArgsLazy برای فراخوانی روش fromBundle ایستا در کلاس‌های NavArgs تولیدشده برای انتقال آرگومان نوع ایمن استفاده می‌کند. برای اطمینان از اینکه این روش فقط برای پیاده‌سازی‌های NavArgs حفظ می‌شود و نه برای هر کلاس، Jetpack Navigation شامل قانون حفظ شرطی زیر است:

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

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

بررسی نام‌های تولیدشده جاوا

هنگام نوشتن قوانین نگهداری، باید کلاس‌ها و دیگر انواع مرجع را بااستفاده از نام آن‌ها پس‌از کامپایل شدن به بایت‌کد Java مشخص کنید (برای مثال، به مشخصات کلاس و انواع مراجعه کنید). برای بررسی اینکه نام‌های Java تولیدشده برای کد شما چیست، از یکی از ابزارهای زیر در Android Studio استفاده کنید:

  • «تحلیل‌گر APK»
  • درحالی‌که فایل منبع Kotlin باز است، با رفتن به ابزارها > Kotlin > نمایش کد بایت Kotlin > تبدیل کد، کد بایت را بررسی کنید.