إضافة قواعد الاحتفاظ

على مستوى عالٍ، تحدّد قاعدة الحفاظ على الرموز البرمجية فئة (أو فئة فرعية أو تنفيذًا)، ثم تحدّد العناصر، أي الطرق أو الدوال الإنشائية أو الحقول، التي يجب الحفاظ عليها داخل هذه الفئة.

بناء الجملة العام لقاعدة الاحتفاظ هو كما يلي، ولكن بعض خيارات الاحتفاظ لا تقبل keep_option_modfier الاختيارية.


-<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 هو الجزء الأول من قاعدة keep. ويحدّد الجوانب التي يجب الاحتفاظ بها في الفئة. تتوفّر ستة خيارات مختلفة للاحتفاظ بالبيانات، وهي keep وkeepclassmembers وkeepclasseswithmembers وkeepnames وkeepclassmembernames وkeepclasseswithmembernames.

يوضّح الجدول التالي خيارات الاحتفاظ هذه:

خيار "الاحتفاظ" الوصف
keepclassmembers يحتفظ هذا الخيار بالأعضاء المحدّدين فقط إذا كان الصف موجودًا بعد التحسين.
keep تحتفظ هذه القاعدة بالفئات المحدّدة والعناصر المحدّدة (الحقول والطرق)، ما يمنع تحسينها.

ملاحظة: يجب بشكل عام استخدام keep مع معدِّلات خيار الاحتفاظ فقط لأنّ keep بمفرده يمنع إجراء أي تحسينات من أي نوع على الفئات المطابقة.
keepclasseswithmembers تحتفظ بفئة وأعضائها المحدّدين فقط إذا كانت الفئة تضم جميع الأعضاء من مواصفات الفئة.
keepclassmembernames يمنع هذا الخيار إعادة تسمية أعضاء الصف المحددين، ولكنّه لا يمنع إزالة الصف أو أعضائه.

ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار -keepclassmembers,allowshrinking المكافئ بدلاً منه.
keepnames يمنع هذا الخيار إعادة تسمية الصفوف وأعضائها، ولكنّه لا يمنع إزالتها بالكامل إذا تم اعتبارها غير مستخدَمة.

ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار -keep,allowshrinking المكافئ بدلاً منه.
keepclasseswithmembernames يمنع إعادة تسمية الفئات وأعضائها المحدّدين، ولكن فقط إذا كان الأعضاء متوفّرين في الرمز النهائي. ولا يمنع إزالة الرمز.

ملاحظة: غالبًا ما يُساء فهم معنى هذا الخيار، لذا ننصحك باستخدام الخيار -keepclasseswithmembers,allowshrinking المكافئ بدلاً منه.

اختيار خيار الاحتفاظ المناسب

يُعد اختيار خيار الحفاظ المناسب أمرًا بالغ الأهمية لتحديد التحسين المناسب لتطبيقك، إذ تعمل بعض خيارات الحفاظ على تقليل حجم الرمز البرمجي، وهي عملية تتم من خلال إزالة الرمز البرمجي غير المُشار إليه، بينما تعمل خيارات أخرى على تشويش الرمز البرمجي أو إعادة تسميته. يوضّح الجدول التالي إجراءات خيارات الاحتفاظ المختلفة:

خيار "الاحتفاظ" تقليل حجم الصفوف إخفاء هوية الفئات تقليص عدد الأعضاء إخفاء هوية الأعضاء
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

Keep option modifier

يُستخدَم معدِّل خيار الاحتفاظ بالبيانات للتحكّم في نطاق قاعدة الاحتفاظ بالبيانات وسلوكها. يمكنك إضافة 0 أو أكثر من معدّلات خيار الاحتفاظ إلى قاعدة الاحتفاظ.

يوضّح الجدول التالي القيم المحتملة لمعدِّل خيار الاحتفاظ:

القيمة الوصف
allowoptimization يسمح بتحسين العناصر المحدّدة. ومع ذلك، لا تتم إعادة تسمية العناصر المحدّدة أو إزالتها.
allowobfucastion تتيح إعادة تسمية العناصر المحدّدة. ومع ذلك، لا يمكن إزالة العناصر أو تحسينها بأي شكل آخر.
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

تحدّد مواصفات الفئة أيضًا الأعضاء ضمن الفئة الذين يجب الاحتفاظ بهم. تحتفظ القاعدة التالية بالفئة MaterialButton وجميع عناصرها:

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

الفئات الفرعية وعمليات التنفيذ

لاستهداف فئة فرعية أو فئة تنفّذ واجهة، استخدِم extend وimplements على التوالي.

على سبيل المثال، إذا كان لديك الفئة Bar مع الفئة الفرعية Foo على النحو التالي:

class Foo : Bar()

تحتفظ قاعدة Keep التالية بجميع الفئات الفرعية من Bar. يُرجى العِلم أنّ قاعدة keep لا تتضمّن الفئة الرئيسية 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 f: Int
      internal fun m() {}
    }
    

    تكون الفئات والطرق والحقول internal public في ملفات .class التي ينتجها برنامج الترجمة البرمجية Kotlin، لذا يجب استخدام الكلمة الرئيسية 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);
    

    لكتابة قاعدة الاحتفاظ بالبيانات لهذه الدالة، يجب أن تتطابق مع هذه التوقيعات المجمّعة أو استخدام ....

    في ما يلي مثال على استخدام التوقيع المجمَّع:

    -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 والطريقة العامة 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 *;
}

الطرق

في ما يلي بنية تحديد طريقة في مواصفات العضو لقاعدة الاحتفاظ:

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

على سبيل المثال، تحتفظ قاعدة الإبقاء التالية بطريقة عامة باسم setLabel() تعرض قيمة فارغة وتأخذ String.

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

يمكنك استخدام <methods> كاختصار لمطابقة جميع الطرق في فئة كما يلي:

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

لمزيد من المعلومات حول كيفية تحديد أنواع القيم المعروضة وأنواع المَعلمات، يُرجى الاطّلاع على الأنواع.

الشركات المصنِّعة

لتحديد دالة إنشائية، استخدِم <init>. بناء الجملة لتحديد دالة إنشاء في مواصفات العنصر لقاعدة الاحتفاظ هو كما يلي:

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

الحقول

في مواصفات العنصر لقاعدة الحفاظ، تكون بنية تحديد حقل على النحو التالي:

[<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 الذي تم إنشاؤه للفئة التي أضافها برنامج ترجمة 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("@")
}

لكتابة قاعدة keep للدالة isEmailValid، يجب أن يستهدف تحديد الفئة الفئة التي تم إنشاؤها MyClassKt:

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

الأنواع

يوضّح هذا القسم كيفية تحديد أنواع الإرجاع وأنواع المَعلمات وأنواع الحقول في مواصفات عناصر قاعدة الاحتفاظ. تذكَّر استخدام أسماء 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 معلومات النوع العام، لذا عند كتابة قواعد keep التي تتضمّن أنواعًا عامة، عليك استهداف التمثيل المترجم للرمز وليس رمز المصدر الأصلي. لمزيد من المعلومات حول كيفية تغيير الأنواع العامة، راجِع إزالة النوع.

على سبيل المثال، إذا كان لديك الرمز التالي مع نوع عام غير محدود محدّد في 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();
}

أحرف البدل

يوضّح الجدول التالي كيفية استخدام أحرف البدل لتطبيق قواعد الإبقاء على فئات أو عناصر متعددة تتطابق مع نمط معيّن.

حرف البدل تنطبق على الصفوف أو الأعضاء الوصف
** كلاهما الأكثر استخدامًا تتطابق مع أي اسم نوع، بما في ذلك أي عدد من فواصل الحزم. ويكون ذلك مفيدًا لمطابقة جميع الفئات ضِمن حزمة وحِزمها الفرعية.
* كلاهما بالنسبة إلى مواصفات الفئات، تتم مطابقة أي جزء من اسم نوع لا يحتوي على فواصل الحزم (.)
أما بالنسبة إلى مواصفات الأعضاء، فتتم مطابقة أي اسم طريقة أو حقل. عند استخدامه بمفرده، يكون أيضًا اسمًا بديلًا لـ **.
؟ كلاهما تتطابق مع أي حرف مفرد في اسم فئة أو عضو.
*** الأعضاء تطابق أي نوع، بما في ذلك الأنواع الأساسية (مثل 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[];) }
    

فحص أسماء Java التي تم إنشاؤها

عند كتابة قواعد الاحتفاظ، يجب تحديد الفئات وأنواع المراجع الأخرى باستخدام أسمائها بعد تجميعها في رمز بايت Java (راجِع مواصفات الفئة والأنواع للاطّلاع على أمثلة). للتحقّق من أسماء Java التي تم إنشاؤها للرمز البرمجي، استخدِم إحدى الأداتَين التاليتَين في &quot;استوديو Android&quot;:

  • أداة تحليل ملفات APK
  • بعد فتح ملف مصدر Kotlin، افحص الرمز الثانوي من خلال الانتقال إلى الأدوات > Kotlin > عرض الرمز الثانوي لـ Kotlin > تفكيك.