در سطح بالا، یک قانون 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
تولید شده توسط کامپایلر Kotlinpublic
هستند، بنابراین باید از کلمه کلیدی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 بررسی کنید.