على مستوى عالٍ، تحدّد قاعدة الاحتفاظ فئة (أو فئة فرعية أو تنفيذًا)، ثم الأعضاء، أي الطرق أو الدوال الإنشائية أو الحقول، ضمن تلك الفئة التي يجب الاحتفاظ بها.
بناء الجملة العام لقاعدة الاحتفاظ هو كما يلي:
-<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 يمنع بمفرده إجراء أي نوع من التحسينات على الفئات المطابقة. |
keepclasseswithmembers |
تحتفظ هذه القاعدة بفئة وأعضائها المحدّدين فقط إذا كانت الفئة تتضمّن جميع الأعضاء من مواصفات الفئة. |
keepclassmembernames |
يمنع إعادة تسمية أعضاء فئة معيّنين، ولكنّه لا يمنع إزالة الفئة أو أعضائها. ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار المكافئ -keepclassmembers,allowshrinking بدلاً منه. |
keepnames |
يمنع إعادة تسمية الفئات وأعضائها، ولكنه لا يمنع إزالتها بالكامل إذا تم اعتبارها غير مستخدمة. ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار -keep,allowshrinking المكافئ بدلاً منه. |
keepclasseswithmembernames |
يمنع إعادة تسمية الفئات وأعضائها المحدّدين، ولكن فقط إذا كان الأعضاء متوفّرين في الرمز النهائي. ولا يمنع إزالة الرمز. ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار المكافئ -keepclasseswithmembers,allowshrinking بدلاً منه. |
اختيار خيار الاحتفاظ المناسب
يُعدّ تحديد خيار الاحتفاظ المناسب أمرًا بالغ الأهمية في تحديد التحسين المناسب لتطبيقك. تعمل بعض خيارات الاحتفاظ على تقليص الرمز، وهي عملية تتم من خلالها إزالة الرمز الذي لم تتم الإشارة إليه، بينما تعمل خيارات أخرى على تشويش الرمز أو إعادة تسميته. يوضّح الجدول التالي إجراءات خيارات الاحتفاظ المختلفة:
| خيار الاحتفاظ | تقليل حجم الصفوف | إخفاء الفئات | تقليص عدد الأعضاء | إخفاء الأعضاء |
|---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
الاحتفاظ بمعدِّل الخيار
يُستخدَم معدِّل خيار الاحتفاظ للتحكّم في نطاق قاعدة الاحتفاظ وسلوكها. يمكنك إضافة 0 أو أكثر من معدّلات خيار الاحتفاظ إلى قاعدة الاحتفاظ.
في ما يلي القيم المحتملة لمعدِّل خيار الاحتفاظ:
| القيمة | الوصف |
|---|---|
allowoptimization |
يتيح هذا الإذن تحسين العناصر المحدّدة. ومع ذلك، لا تتم إعادة تسمية العناصر المحدّدة أو إزالتها. |
allowobfuscation |
يسمح بإعادة تسمية العناصر المحدّدة. ومع ذلك، لا تتم إزالة العناصر أو تحسينها بطريقة أخرى. |
allowshrinking |
تسمح بإزالة العناصر المحدّدة إذا لم يعثر R8 على أي مراجع لها. ومع ذلك، لا تتم إعادة تسمية العناصر أو تحسينها بأي شكل آخر. |
includedescriptorclasses |
توجّه هذه السمة أداة R8 إلى الاحتفاظ بجميع الفئات التي تظهر في واصفات الطرق (أنواع المَعلمات وأنواع القيمة التي تم إرجاعها) والحقول (أنواع الحقول) التي يتم الاحتفاظ بها. |
allowaccessmodification |
تسمح أداة R8 بتغيير (توسيع عادةً) معدّلات الوصول (public وprivate وprotected) للفئات والطرق والحقول أثناء عملية التحسين. |
allowrepackage |
يسمح هذا الإعداد لأداة R8 بنقل الفئات إلى حزم مختلفة، بما في ذلك الحزمة التلقائية (الجذر). |
مواصفات الفئة
يجب تحديد فئة (بما في ذلك فئات الواجهات والتعدادات والتعليقات التوضيحية) في كل قاعدة keep. يمكنك بشكل اختياري حصر القاعدة استنادًا إلى التعليقات التوضيحية، وذلك من خلال تحديد فئة فائقة أو واجهة تم تنفيذها، أو من خلال تحديد معدِّل الوصول للفئة. يجب تحديد جميع الفئات، بما في ذلك الفئات من مساحة الاسم 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وطرقها وحقولهاpublicفي ملفات.classالتي ينتجها برنامج تجميع Kotlin، لذا يجب استخدام الكلمة الرئيسية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>;
}
الأنواع
يوضّح هذا القسم كيفية تحديد أنواع القيمة التي تم إرجاعها وأنواع المَعلمات وأنواع الحقول في مواصفات عناصر قاعدة الاحتفاظ. تذكَّر استخدام أسماء Java التي تم إنشاؤها لتحديد الأنواع إذا كانت مختلفة عن الرمز المصدر للغة Kotlin.
الأنواع الأساسية
لتحديد نوع أساسي، استخدِم كلمة 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. للاحتفاظ بدالة الإنشاء والطريقة الخاصة بالفئة، يجب أن تستخدم القاعدة 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) {
}
يمكنك استخدام قاعدة احتفاظ مع 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>();
}
استبعاد أنماط أسماء الأعضاء
اعتبارًا من الإصدار 9.2.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android، يمكنك إلغاء أنماط أسماء الأعضاء ضمن قواعد الاحتفاظ. يتيح لك ذلك تحديد الأعضاء الذين تريد الاحتفاظ بهم أو استبعادهم استنادًا إلى أنماط. لنفي نمط، أضِف علامة تعجّب (!) قبل نمط اسم العنصر.
يمكنك استخدام هذه الميزة في الحالات التي تريد فيها الاحتفاظ بمعظم الأعضاء الذين يطابقون نمطًا أوسع ولكن يستبعدون أعضاء محددين، مثل الطرق المخصصة فقط للاختبار.
مثال:
للاحتفاظ بجميع الطرق العامة في com.example.MyClass باستثناء تلك التي تنتهي بـ "ForTesting"، استخدِم القاعدة التالية:
-keepclassmembers class com.example.MyClass {
public *** !*ForTesting(...);
}
الدوال على مستوى الحزمة
للإشارة إلى دالة Kotlin تم تعريفها خارج فئة (يُطلق عليها عادةً اسم دوال المستوى الأعلى)، احرص على استخدام اسم Java الذي تم إنشاؤه للفئة التي أضافها برنامج ترجمة 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 التالية بجميع الطرق:
-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 Navigation فئات NavArgs لمنع أخطاء الكتابة عند تمرير الوسيطات بين الوجهات. عند استخدام NavArgsLazy
delegate، يتم استخدام الانعكاس للعثور على طريقة ثابتة 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. إذا كانت فئات نموذج البيانات تستخدم تعليق Gson التوضيحي @SerializedName في أي حقل، يمكنك استخدام قاعدة شرطية لحماية أي فئة من هذه الفئات وأعضائها التي يحتاجها 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 (راجِع تحديد الفئة والأنواع للاطّلاع على أمثلة). لمعرفة أسماء Java التي تم إنشاؤها للرمز البرمجي، استخدِم إحدى الأدوات التالية في "استوديو Android":
- أداة تحليل ملفات APK
- بعد فتح ملف مصدر Kotlin، افحص الرمز الثانوي من خلال الانتقال إلى الأدوات > Kotlin > عرض رمز Kotlin الثانوي > فك التشفير.