قوانین حفظ را اضافه کنید

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

نحو کلی برای یک قانون keep به شرح زیر است، با این حال برخی از گزینه های keep گزینه اختیاری keep_option_modfier نمی پذیرند.


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

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

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

گزینه Keep

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

جدول زیر این گزینه های نگهداری را توضیح می دهد:

گزینه Keep توضیحات
keepclassmembers اعضای مشخص شده را فقط در صورتی حفظ می کند که کلاس پس از بهینه سازی وجود داشته باشد .
keep کلاس های مشخص شده و اعضای مشخص شده (فیلدها و روش ها) را حفظ می کند و از بهینه شدن آنها جلوگیری می کند.

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

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

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

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

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

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

گزینه Keep کلاس ها را کوچک می کند کلاس ها را مبهم می کند اعضا را کوچک می کند اعضا را مخدوش می کند
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

اصلاح کننده گزینه Keep

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

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

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

مشخصات کلاس

شما باید یک کلاس، سوپرکلاس یا رابط پیاده سازی شده را به عنوان بخشی از یک قانون حفظ مشخص کنید. همه کلاس‌ها، از جمله کلاس‌های فضای نام java.lang مانند java.lang.String ، باید با استفاده از نام جاوا کاملاً واجد شرایط خود مشخص شوند. برای درک نام هایی که باید استفاده شوند، بایت کد را با استفاده از ابزارهای توضیح داده شده در دریافت نام های جاوا تولید شده بررسی کنید.

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

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

مشخصات کلاس همچنین اعضای یک کلاس را مشخص می کند که باید نگه داشته شوند. قانون زیر کلاس MaterialButton و تمام اعضای آن را نگه می دارد:

-keep class com.google.android.material.button.MaterialButton { *; }

زیر کلاس ها و پیاده سازی ها

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

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

class Foo : Bar()

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

-keep class * extends Bar

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

class Foo : Bar

قانون keep زیر تمام کلاس هایی را که Bar را پیاده سازی می کنند حفظ می کند. توجه داشته باشید که قانون keep شامل خود 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 *(...);
}

اصلاح کننده های مخصوص کاتلین

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

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

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

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

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

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

    suspend fun fetchUser(id: String): User
    

    هنگام کامپایل، امضای آن در بایت کد به شکل زیر است:

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

    برای نوشتن یک قانون keep برای این تابع، باید این امضای کامپایل شده را مطابقت دهید یا از ... استفاده کنید.

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

    -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(...);
    }
    

مشخصات اعضا

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

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

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

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

-keep class com.myapp.MyClass

بیشتر اوقات، شما می خواهید چند عضو را مشخص کنید. به عنوان مثال، مثال زیر text فیلد عمومی و متد public 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 *;
}

روش ها

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

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

برای مثال، قانون keep زیر یک متد عمومی به نام setLabel() را نگه می‌دارد که void را برمی‌گرداند و یک String می‌گیرد.

-keep class com.example.MyView {
    public void setLabel(java.lang.String);
}

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

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

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

سازندگان

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

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

به عنوان مثال، قانون حفظ زیر یک سازنده View سفارشی را نگه می دارد که یک Context و یک AttributeSet را می گیرد.

-keep class com.example.ui.MyCustomView {
    public <init>(android.content.Context, android.util.AttributeSet);
}

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

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

فیلدها

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

[<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 که خارج از یک کلاس تعریف شده است (که معمولاً توابع سطح بالا نامیده می شود)، مطمئن شوید که از نام جاوای تولید شده برای کلاسی که به طور ضمنی توسط کامپایلر 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);
}

انواع

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

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

برای تعیین یک نوع اولیه، از کلمه کلیدی جاوا آن استفاده کنید. 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 جایگزین می شود. برای حفظ سازنده کلاس و متد، قانون شما باید از java.lang.Object به جای T عمومی استفاده کند.

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

# 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) {
}

می‌توانید از یک قانون keep با 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();
}

عجایب

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

عجایب برای کلاس ها یا اعضا اعمال می شود توضیحات
** هر دو بیشتر مورد استفاده قرار می گیرد. با هر نام نوع، از جمله هر تعداد جداکننده بسته مطابقت دارد. این برای تطبیق تمام کلاس‌ها در یک بسته و بسته‌های فرعی آن مفید است.
* هر دو برای مشخصات کلاس، هر بخشی از نام نوع را که حاوی جداکننده بسته ( . ) نباشد مطابقت می دهد.
برای مشخصات اعضا، با هر روش یا نام فیلد مطابقت دارد. هنگامی که به خودی خود استفاده می شود، همچنین نام مستعار برای ** است.
? هر دو با هر کاراکتری در یک کلاس یا نام عضو مطابقت دارد.
*** اعضا با هر نوع، از جمله انواع اولیه (مانند 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 زیر استفاده کنید:

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

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

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

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

  • تحلیلگر APK
  • با باز بودن فایل منبع Kotlin، بایت کد را با رفتن به Tools > Kotlin > Show Kotlin Bytecode > Decompile بررسی کنید.