در سطح بالا، یک قانون keep، یک کلاس (یا زیرکلاس یا پیادهسازی) و سپس اعضایی - متدها، سازندهها یا فیلدها - درون آن کلاس را برای حفظ مشخص میکند.
سینتکس کلی برای دستور keep به صورت زیر است:
-<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 وجود دارد، یعنی keep ، keepclassmembers ، keepclasseswithmembers ، keepnames ، keepclassmembernames ، keepclasseswithmembernames .
جدول زیر این گزینههای نگهداری را شرح میدهد:
| گزینه نگه دارید | توضیحات |
|---|---|
keepclassmembers | اعضای مشخص شده را فقط در صورتی که کلاس پس از بهینهسازی وجود داشته باشد، حفظ میکند. |
keep | کلاسهای مشخص شده و اعضای مشخص شده (فیلدها و متدها) را حفظ میکند و از بهینهسازی آنها جلوگیری میکند. نکته : keep معمولاً فقط باید با اصلاحکنندههای گزینه keep استفاده شود، زیرا keep به خودی خود از هرگونه بهینهسازی روی کلاسهای منطبق جلوگیری میکند. |
keepclasseswithmembers | فقط در صورتی که کلاس تمام اعضای مشخص شده در مشخصات کلاس را داشته باشد، یک کلاس و اعضای مشخص شده آن را حفظ میکند. |
keepclassmembernames | از تغییر نام اعضای کلاس مشخص شده جلوگیری میکند، اما مانع از حذف کلاس یا اعضای آن نمیشود. نکته: معنی این گزینه اغلب اشتباه فهمیده میشود؛ به جای آن، استفاده از معادل آن -keepclassmembers,allowshrinking را در نظر بگیرید. |
keepnames | از تغییر نام کلاسها و اعضای آنها جلوگیری میکند، اما در صورت بلااستفاده بودن، مانع از حذف کامل آنها نمیشود. نکته: معنی این گزینه اغلب اشتباه فهمیده میشود؛ به جای آن از معادل آن یعنی -keep,allowshrinking استفاده کنید. |
keepclasseswithmembernames | از تغییر نام کلاسها و اعضای مشخصشدهی آنها جلوگیری میکند، اما فقط در صورتی که اعضا در کد نهایی وجود داشته باشند. از حذف کد جلوگیری نمیکند. نکته: معنی این گزینه اغلب اشتباه فهمیده میشود؛ به جای آن، استفاده از معادل آن -keepclasseswithmembers,allowshrinking در نظر بگیرید. |
گزینه نگهداری مناسب را انتخاب کنید
انتخاب گزینه مناسب برای Keep در تعیین بهینهسازی مناسب برای برنامه شما بسیار مهم است. برخی از گزینههای Keep، کد را کوچک میکنند، فرآیندی که طی آن کد بدون ارجاع حذف میشود، در حالی که برخی دیگر، کد را مبهمسازی یا تغییر نام میدهند. جدول زیر عملکرد گزینههای مختلف Keep را نشان میدهد:
| گزینه نگه دارید | کلاسهای کوچکسازی | کلاسها را مبهم میکند | اعضا را کوچک میکند | اعضا را مبهم میکند |
|---|---|---|---|---|
keep | ||||
keepclassmembers | ||||
keepclasseswithmembers | ||||
keepnames | ||||
keepclassmembernames | ||||
keepclasseswithmembernames |
نگه داشتن اصلاحکنندهی گزینه
یک اصلاحکنندهی گزینهی keep برای کنترل دامنه و رفتار یک قانون keep استفاده میشود. میتوانید 0 یا چند اصلاحکنندهی گزینهی keep به قانون keep خود اضافه کنید.
مقادیر ممکن برای اصلاحکنندهی گزینهی keep در جدول زیر شرح داده شده است:
| ارزش | توضیحات |
|---|---|
allowoptimization | امکان بهینهسازی عناصر مشخصشده را فراهم میکند. با این حال، عناصر مشخصشده تغییر نام نمیدهند یا حذف نمیشوند. |
allowobfucastion | اجازه تغییر نام عناصر مشخص شده را میدهد. با این حال، عناصر حذف یا بهینهسازی نمیشوند. |
allowshrinking | اگر R8 هیچ ارجاعی به عناصر مشخصشده پیدا نکند، اجازه حذف آنها را میدهد. با این حال، عناصر تغییر نام نمیدهند یا بهینهسازی دیگری نمیشوند. |
includedescriptorclasses | به R8 دستور میدهد که تمام کلاسهایی که در توصیفگرهای متدها (انواع پارامتر و انواع بازگشتی) و فیلدها (انواع فیلد) ظاهر میشوند را نگه دارد. |
allowaccessmodification | به R8 اجازه میدهد تا در طول فرآیند بهینهسازی، اصلاحکنندههای دسترسی ( public ، private ، protected ) کلاسها، متدها و فیلدها را تغییر دهد (معمولاً گسترش دهد). |
allowrepackage | به R8 اجازه میدهد کلاسها را به بستههای مختلف، از جمله بسته پیشفرض (ریشه)، منتقل کند. |
مشخصات کلاس
شما باید در هر قانون keep، یک کلاس (شامل کلاسهای interface، enum و annotation) مشخص کنید. میتوانید به صورت اختیاری، قانون را بر اساس annotationها یا با مشخص کردن یک superclass یا رابط پیادهسازی شده محدود کنید. همه کلاسها، از جمله کلاسهای فضای نام java.lang مانند java.lang.String ، باید با استفاده از نام جاوای کاملاً واجد شرایط خود مشخص شوند. برای فهمیدن نامهایی که باید استفاده شوند، بایتکد را با استفاده از ابزارهای شرح داده شده در Inspect generated Java names بررسی کنید.
مثال زیر نحوهی تعیین کلاس MaterialButton را نشان میدهد:
- صحیح:
com.google.android.material.button.MaterialButton - نادرست:
MaterialButton
مشخصات کلاس همچنین اعضای درون یک کلاس را که باید نگه داشته شوند، مشخص میکند . قانون زیر کلاس MaterialButton و تمام اعضای آن را نگه میدارد:
-keep class com.google.android.material.button.MaterialButton { *; }
کلاسها را بر اساس حاشیهنویسیها مشخص کنید
برای مشخص کردن کلاسها بر اساس حاشیهنویسیهایشان، نام کامل جاوای حاشیهنویسی را با علامت @ شروع کنید. برای مثال:
-keep class @com.example.MyAnnotation com.example.MyClass
اگر یک قانون keep بیش از یک حاشیهنویسی داشته باشد، کلاسهایی را نگه میدارد که همه حاشیهنویسیهای فهرستشده را دارند. میتوانید چندین حاشیهنویسی را فهرست کنید، اما این قانون فقط در صورتی اعمال میشود که کلاس همه حاشیهنویسیهای فهرستشده را داشته باشد. برای مثال، قانون زیر همه کلاسهایی را که توسط Annotation1 و Annotation2 حاشیهنویسی شدهاند، نگه میدارد.
-keep class @com.example.Annotation1 @com.example.Annotation2 *
زیرکلاسها و پیادهسازیها را مشخص کنید
برای هدف قرار دادن یک زیرکلاس یا کلاسی که یک رابط را پیادهسازی میکند، به ترتیب extend و implements استفاده کنید.
برای مثال، اگر کلاس Bar با زیرکلاس Foo به صورت زیر داشته باشید:
class Foo : Bar()
قانون keep زیر تمام زیرکلاسهای Bar را حفظ میکند. توجه داشته باشید که قانون keep شامل خود کلاس بالادست Bar نمیشود.
-keep class * extends Bar
اگر کلاس Foo دارید که رابط Bar را پیادهسازی میکند:
class Foo : Bar
قانون keep زیر تمام کلاسهایی را که Bar را پیادهسازی میکنند، حفظ میکند. توجه داشته باشید که قانون keep شامل خود رابط Bar نمیشود.
-keep class * implements Bar
اصلاحکننده دسترسی
شما میتوانید اصلاحکنندههای دسترسی مانند public ، private ، static و final را مشخص کنید تا قوانین keep خود را دقیقتر کنید.
برای مثال، قانون زیر تمام کلاسهای public درون بسته api و زیربستههای آن و تمام اعضای عمومی و محافظتشده در این کلاسها را نگه میدارد.
-keep public class com.example.api.** { public protected *; }
شما همچنین میتوانید از اصلاحکنندهها برای اعضای درون یک کلاس استفاده کنید. برای مثال، قانون زیر فقط متدهای public static یک کلاس Utils را نگه میدارد:
-keep class com.example.Utils {
public static void *(...);
}
اصلاحکنندههای مخصوص کاتلین
R8 از اصلاحکنندههای مخصوص کاتلین مانند internal و suspend پشتیبانی نمیکند. برای نگهداشتن چنین فیلدهایی از دستورالعملهای زیر استفاده کنید.
برای نگه داشتن یک کلاس، متد یا فیلد
internal، آن را به صورت عمومی در نظر بگیرید. برای مثال، کد منبع کاتلین زیر را در نظر بگیرید:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }کلاسها، متدها و فیلدهای
internalدر فایلهای.classکه توسط کامپایلر کاتلین تولید میشوند،publicهستند، بنابراین باید از کلمه کلیدیpublicهمانطور که در مثال زیر نشان داده شده است استفاده کنید:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }وقتی یک عضو
suspendکامپایل میشود، امضای کامپایلشدهی آن را در قانون keep مطابقت بده.برای مثال، اگر تابع
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(...); }
مشخصات اعضا
مشخصات کلاس به صورت اختیاری شامل اعضای کلاسی است که باید حفظ شوند. اگر یک یا چند عضو برای یک کلاس مشخص کنید، این قانون فقط برای آن اعضا اعمال میشود.
همچنین میتوانید اعضا را بر اساس حاشیهنویسیهایشان مشخص کنید. مشابه کلاسها، نام کامل جاوای حاشیهنویسی را با @ پیشوند میآورید. این به شما امکان میدهد فقط اعضایی را در یک کلاس نگه دارید که با حاشیهنویسیهای خاص علامتگذاری شدهاند. به عنوان مثال، برای نگه داشتن متدها و فیلدهای حاشیهنویسی شده با @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 حاشیهنویسی شوند.
در صورت امکان، استفاده از قوانین keep مبتنی بر حاشیهنویسی را در نظر بگیرید. این رویکرد، پیوند صریحی بین کد شما و قوانین keep شما ایجاد میکند و اغلب منجر به پیکربندیهای قویتری میشود. برای مثال، کتابخانه حاشیهنویسی androidx.annotation از این مکانیسم استفاده میکند.
مثالها
برای مثال، برای ذخیره یک کلاس خاص و تمام اعضای آن، از کد زیر استفاده کنید:
-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 *;
}
روشها
نحو تعیین یک متد در مشخصات عضو برای یک قانون 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);
برای مثال، قانون keep زیر یک سازندهی 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>;
برای مثال، قانون keep زیر یک فیلد رشتهای خصوصی به نام 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>;
}
توابع سطح بسته
برای ارجاع به یک تابع کاتلین که خارج از یک کلاس تعریف شده است (که معمولاً توابع سطح بالا نامیده میشوند)، مطمئن شوید که از نام جاوای تولید شده برای کلاسی که به طور ضمنی توسط کامپایلر کاتلین اضافه شده است، استفاده میکنید. نام کلاس، نام فایل کاتلین با پسوند Kt است. برای مثال، اگر یک فایل کاتلین به نام MyClass.kt به صورت زیر تعریف شده است:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
برای نوشتن یک قانون keep برای تابع isEmailValid ، مشخصات کلاس باید کلاس تولید شده MyClassKt را هدف قرار دهد:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
انواع
این بخش نحوهی تعیین انواع مقادیر برگشتی، انواع پارامترها و انواع فیلدها را در مشخصات اعضای قانون keep شرح میدهد. به یاد داشته باشید که اگر نامهای تولید شدهی جاوا با کد منبع کاتلین متفاوت هستند، از آنها برای تعیین نوعها استفاده کنید.
انواع اولیه
برای مشخص کردن یک نوع داده اولیه، از کلمه کلیدی جاوای آن استفاده کنید. 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 اطلاعات نوع عمومی را پاک میکند، بنابراین وقتی قوانین keep را مینویسید که شامل انواع عمومی هستند، باید نمایش کامپایل شده کد خود را هدف قرار دهید و نه کد منبع اصلی. برای کسب اطلاعات بیشتر در مورد نحوه تغییر انواع عمومی، به Type eraser مراجعه کنید.
برای مثال، اگر کد زیر را با یک نوع ژنریک نامحدود تعریف شده در Box.kt داشته باشید:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
پس از پاک کردن نوع داده، T با Object جایگزین میشود. برای حفظ سازنده کلاس و متد، قانون شما باید به جای T ژنریک از java.lang.Object استفاده کند.
یک مثال از قانون keep به شرح زیر است:
# 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)
در این حالت، نوع eraser، T با حد آن، java.lang.Number ، جایگزین میکند.
یک مثال از قانون keep به شرح زیر است:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
هنگام استفاده از انواع ژنریک مختص برنامه به عنوان کلاس پایه، لازم است که قوانین keep را برای کلاسهای پایه نیز لحاظ کنید.
مثلاً برای کد زیر:
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)
شما میتوانید از قانون keep زیر برای محافظت از فقط تابع 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
}
}
میتوانید از قانون keep زیر استفاده کنید:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
وایلدکاردها
جدول زیر نحوه استفاده از wildcardها را برای اعمال قوانین keep به چندین کلاس یا عضو که با یک الگوی خاص مطابقت دارند، نشان میدهد.
| وایلدکارت | برای کلاسها یا اعضا اعمال میشود | توضیحات |
|---|---|---|
| ** | هر دو | پرکاربردترین. با هر نام نوعی، شامل هر تعداد جداکنندهی بسته، مطابقت دارد. این برای تطبیق تمام کلاسهای درون یک بسته و زیربستههای آن مفید است. |
| * | هر دو | برای مشخصات کلاس، با هر بخشی از نام نوع که حاوی جداکنندههای بسته ( . ) نباشد، مطابقت دارد.برای مشخصات اعضا، با هر نام متد یا فیلدی مطابقت دارد. وقتی به تنهایی استفاده شود، یک نام مستعار برای ** نیز هست. |
| ? | هر دو | با هر کاراکتر واحدی در نام کلاس یا عضو مطابقت دارد. |
| *** | اعضا | با هر نوعی، شامل انواع اولیه (مانند int )، انواع کلاس (مانند java.lang.String ) و انواع آرایه با هر بعدی (مانند byte[][] ) مطابقت دارد. |
| ... | اعضا | با هر لیستی از پارامترها برای یک متد مطابقت دارد. |
| % | اعضا | با هر نوع دادهی اولیه (مانند `int`، `float`، `boolean` یا موارد دیگر) مطابقت دارد. |
در اینجا چند مثال از نحوه استفاده از کاراکترهای ویژه wildcards آورده شده است:
اگر چندین متد با نام یکسان دارید که انواع اولیهی مختلفی را به عنوان ورودی میگیرند، میتوانید
%برای نوشتن یک قانون keep که همه آنها را نگه میدارد، استفاده کنید. برای مثال، این کلاسDataStoreچندین متدsetValueدارد:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }قانون keep زیر همه متدها را نگه میدارد:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }اگر چندین کلاس با نامهایی دارید که در یک کاراکتر با هم متفاوت هستند، از
?برای نوشتن یک قانون keep که همه آنها را نگه میدارد استفاده کنید. به عنوان مثال، اگر کلاسهای زیر را دارید:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}قانون keep زیر همه کلاسها را نگه میدارد:
-keep class com.example.models.UserV?برای تطبیق کلاسهای
ExampleوAnotherExample(اگر کلاسهای سطح ریشه باشند)، اما نهcom.foo.Example، از قانون keep زیر استفاده کنید:-keep class *Exampleاگر از * به تنهایی استفاده کنید، به عنوان یک نام مستعار برای ** عمل میکند. برای مثال، قوانین keep زیر معادل هستند:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
بررسی نامهای تولید شده جاوا
هنگام نوشتن قوانین keep، باید کلاسها و سایر انواع ارجاع را با استفاده از نامهایشان پس از کامپایل شدن به بایتکد جاوا مشخص کنید (برای مثالها به مشخصات کلاس و انواع مراجعه کنید). برای بررسی نامهای جاوای تولید شده برای کد خود، از یکی از ابزارهای زیر در اندروید استودیو استفاده کنید:
- تحلیلگر APK
- با باز کردن فایل منبع کاتلین، با رفتن به Tools > Kotlin > Show Kotlin Bytecode > Decompile، بایتکد را بررسی کنید.