يشغّل نظام تصميم نظام Android الأساسي (Soong) برنامج تجميع R8 على الاستهدافات التي تجمع رمز بايت DEX، بما في ذلك تطبيقات Android (android_app) والاختبارات (android_test) ومكتبات Java القابلة للتثبيت مع compile_dex: true (مثل services.jar)، وذلك لتقليل حجمها وتحسينها وإزالة الرموز والموارد غير المستخدَمة. في ما يتعلق بالمكتبات الثابتة (android_library والثابتة java_library)، لا يشغّل Soong أداة R8 مباشرةً، بل يستخدم كتلة السمة optimize لإرفاق قواعد الاحتفاظ بالمستهلكين (export_proguard_flags_files: true) ونشرها إلى أهداف DEX النهائية التي تربطها بشكل ثابت.
بالنسبة إلى مهندسي المنصات الذين ينشئون صور نظام أو حِزم مورّدين، فإنّ إلغاء قواعد الحفاظ الواسعة النطاق بشكل مفرط يوفّر فوائد مباشرة لصحة النظام:
- تقليل حجم مساحة قسم النظام: يزيل R8 الفئات والطُرق والموارد غير المستخدَمة قبل تجميع حِزم APK وملفات JAR في
/systemو/system_extو/productو/vendor. - عناصر أصغر تم تجميعها: يعني عدد أقل من طرق DEX ملفات
.odexو.vdexأصغر حجمًا يتم إنشاؤها بواسطةdex2oatأثناء وقت الإنشاء أو التجميع على الجهاز فقط. - انخفاض ضغط الذاكرة أثناء وقت التشغيل: بما أنّ نظام التشغيل Android يربط
.odexو.vdexوجداول موارد حِزم APK بذاكرة العمليات باستخدام طلباتmmapالتي يتم تقسيمها إلى صفحات عند الطلب، فإنّ الملفات الثنائية الأصغر حجمًا تقلّل من أخطاء الصفحات الرئيسية أثناء بدء تشغيل التطبيق، كما تقلّل من ذاكرة الرموز والموارد المقيمة في جميع العمليات. لمزيد من المعلومات حول تأثير رمز التطبيق المجمَّع على ذاكرة الجهاز، يُرجى الاطّلاع على رمز التطبيق هو الذاكرة. - تحسين أكثر فعالية للبرنامج بأكمله: تتيح قيود الاحتفاظ الدقيق لبرنامج R8 تضمين الطرق، وإزالة التجريد من الواجهات والمكالمات الافتراضية، وإزالة الحقول غير المستخدَمة، ونشر الثوابت على مستوى الفئات.
ضبط تحسين R8 في Soong
في ملفات Android.bp، اضبط تحسين R8 باستخدام حظر السمة optimize على أهداف android_app وjava_library القابلة للتثبيت (أو على وحدات android_library وjava_library الثابتة لتصدير قواعد الاحتفاظ بالبيانات الخاصة بالمستهلك):
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
يلخّص الجدول التالي سمات optimize الأكثر شيوعًا في Soong
(المحدّدة في build/soong/java/dex.go):
| الخاصية | الوصف |
|---|---|
enabled |
تتحكّم هذه السمة في ما إذا كان سيتم تشغيل R8 على الهدف. القيمة التلقائية هي true
لجميع أهداف android_app.
|
shrink |
تتحكّم هذه السمة في عملية إزالة الأجزاء غير المستخدَمة من الرمز البرمجي لإزالة الفئات والحقول والطرق التي لا يمكن الوصول إليها. يتم ضبط القيمة التلقائية على true لأهداف android_app (false لوحدات الاختبار وjava_library المستقلة التي تمرِّر -dontshrink ما لم يتم ضبطها بشكل صريح).
|
optimize |
تتحكّم هذه السمة في تحسينات الرمز الثانوي، مثل تضمين الدوال البرمجية، ودمج الفئات،
ونشر الثوابت، وإزالة الفروع غير النشطة. القيمة التلقائية هي
true بالنسبة إلى أهداف android_app (يتم التحكّم فيها من خلال
علامة بنية الإصدار RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
تتحكّم هذه السياسة في تصغير المعرّفات وإعادة تسميتها. القيمة التلقائية هي false لتحقيق التوافق مع الإصدارات القديمة، ولكن يجب ضبطها على true كلما أمكن ذلك لتقليل حجم ملف DEX وإتاحة تحسينات أكثر تفصيلاً (راجِع تفعيل التشويش كلما أمكن ذلك).
|
shrink_resources |
تزيل هذه السمة الموارد غير المستخدَمة (res/ إدخالات) من حِزمة APK بعد تخفيض حجم الرموز. القيمة التلقائية هي false.
|
optimized_shrink_resources |
تشغيل مسار أداة تقليص حجم الرموز والموارد المدمجة R8، ما يتيح لأداة R8 تتبُّع مراجع الرموز والموارد بشكل مشترك في عملية واحدة عند ضبط shrink_resources: true، يتم تلقائيًا ضبط RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true في إصدارات المنصة العادية).
|
proguard_flags_files |
تعرض هذه السمة ملفات .flags أو .pro خاصة بالوحدة النمطية
وتحتوي على قواعد مخصّصة للاحتفاظ بالبيانات. تجنَّب إضافة ملفات مخصّصة عندما تكون التعليقات التوضيحية أو الإعدادات التلقائية العادية كافية.
|
export_proguard_flags_files |
تنقل هذه السمة قيمة proguard_flags_files الخاصة بالمكتبة إلى الوحدات التابعة التي تعتمد عليها بشكل ثابت.
|
trace_references_from |
تعرض هذه السمة قوائم بأهداف مصاحبة لمكتبة Java، وتتتبّع تلقائيًا R8 وتحتفظ بها عندما تشير رموزها البايتية إلى هذا الهدف. |
تفعيل ميزة "إخفاء مفاتيح فك التشفير" حيثما أمكن
في Soong، يتم ضبط obfuscate تلقائيًا على false لتحقيق التوافق مع الإصدارات القديمة، ولكن عليك ضبط obfuscate: true بشكل صريح على android_app واستهدافات DEX المستقلة كلما أمكن ذلك:
- حجم أصغر لملف DEX و
.vdex: يؤدي تغيير أسماء الحِزم والفئات والحقول والطُرق إلى معرّفات قصيرة (aوb) إلى تقليل حجم مجموعة السلاسل في ملف DEX (string_idsوstring_data_item) ومواصفات الأنواع. بما أنّ نظام التشغيل Android يربط ملفات.vdexو.odexبذاكرة العملية، فإنّ جداول الرموز الأصغر حجمًا تقلّل بشكل مباشر من حجم قسم النظام ومن استهلاك الذاكرة أثناء وقت التشغيل. - تحسينات أعمق على البرنامج بأكمله: يتيح R8 إعادة تسمية المعرّفات، ما يؤدي إلى إتاحة دمج الفئات وتسوية الحِزم وإزالة تكرار الفئات الاصطناعية في الحِزم التي يجب أن يتجاهلها R8 لتجنُّب تعارض الأسماء.
- تحويل رموز تتبُّع تسلسل استدعاء الدوال البرمجية الكامل: لا يؤدي تفعيل التعتيم في Soong إلى تقليل إمكانية تصحيح الأخطاء. لكل هدف تم تجميعه باستخدام R8، ينشئ Soong ملف ربط
proguard_dictionaryفي الدليل الوسيط للوحدة، ويجمّع كل قواميس الوحدات في العنصرproguard-dict.zipمن عملية الإنشاء، ويضمّن تجزئة ربط (--map-id-template) في عنوان DEX، ويعيد كتابة سمات الفئةSourceFile(--source-file-template) كي يتمكّنretraceمن تحويل تتبُّع تسلسل استدعاء الدوال البرمجية إلى رموز تلقائيًا.
يجب الاحتفاظ بالرمز obfuscate: false (أو الحفاظ على أسماء واجهات برمجة التطبيقات العامة بشكل صريح) فقط في الحالات التالية:
- المكتبات المشترَكة في bootclasspath أو
system_serverclasspath: يجب أن تحتفظ الوحدات (java_libraryأوjava_sdk_library) التي تعرض مساحة خارجية لواجهة برمجة التطبيقات (API) مرتبطة بشكل ديناميكي بوحدات أخرى في وقت التشغيل (مثل أهدافframework.jarأوservices.jarأو<uses-library>) إما بـobfuscate: falseأو تحافظ صراحةً على مساحة واجهة برمجة التطبيقات (API) باستخدام قواعد@KeepForApiأو-keep(بالإضافة إلىprotect_api_surface: trueلأهداف bootclasspath) حتى تتمكّن البرامج التي تم تجميعها مقابل نماذجها من حلّ أسماء الفئات والأعضاء في وقت التشغيل.
قبل تفعيل obfuscate: true على أحد التطبيقات، تحقّق من المتطلبات الأساسية التالية لنقل البيانات:
- التبعيات في حِزم APK للاختبار الخارجي: إذا كانت مجموعة حِزم APK خارجية
android_testتتضمّنinstrumentation_for: "MyApp"وتستدعي مباشرةً فئات أو طرقًا داخلية منMyApp، سيؤدي تغيير أسماء هذه الرموز إلى حدوثNoSuchMethodErrorأوNoClassDefFoundErrorأثناء وقت تشغيل الاختبار. ننصحك بتنظيم الاختبار على شكل حِزمة APK ذاتية التجهيز يتم ربطهاMyApp.implبشكل ثابت، أو إضافة تعليقات توضيحية إلى نقاط ربط الاختبار باستخدام@VisibleForTesting، أو ضبطtrace_references_from(راجِع الخطوة 2: نقل قواعد الاحتفاظ المستندة إلى الاختبار) حتى يتم الاحتفاظ بالرموز الداخلية التي تحتاج إليها الاختبارات مع تشويش بقية التطبيق. - الاسترجاع المستند إلى السلسلة وواجهة JNI: إذا كان أحد الوحدات يبحث عن الفئات أو الطرق أو الحقول من خلال أسماء السلاسل الحرفية (
Class.forNameأوgetDeclaredMethodأو واجهة JNIFindClassوGetMethodID) بدون قواعد أو تعليقات توضيحية خاصة بالاحتفاظ، فإنّ عملية التشويش تعيد تسمية هذه العناصر المستهدَفة وتؤدي إلى تعطيل عمليات البحث في وقت التشغيل. (يُرجى العِلم أنّ الانعكاس غير المشروح يتعذّر أيضًا في ظلshrink: trueوoptimize: true). أضِف شرحًا إلى نقاط الدخول هذه باستخدام دليل الحفاظ على التعليقات التوضيحية (@UsesReflectionأو@UsedByReflectionأو@UsedByNative) لكي يحتفظ R8 بأسمائها ويشوّش بقية الوحدة.
اتّباع مبدأ عدم استخدام أي علامات مخصّصة
لا تحتوي وحدة نظام Android الأساسي المثالية على ملفات proguard.flags أو keep.xml مخصّصة. في إصدار نظام Android الأساسي، تكون الغالبية العظمى من قواعد الاحتفاظ المخصّصة غير ضرورية:
- خطوط الأساس للمنصة وAAPT2: يتم الاحتفاظ بمعظم نقاط الدخول تلقائيًا من خلال خطوط الأساس العامة للمنصة (مثل
@Keepوطُرق JNInativeو@VisibleForTesting) أو يتم إنشاؤها بواسطة AAPT2 منAndroidManifest.xmlوموارد التنسيق (راجِع التعرّف على قواعد الاحتفاظ التلقائية بالمنصة). - البدائل المستهدَفة: في الحالات التي يجب فيها الحفاظ على نقاط الدخول، يُفضّل استخدام تعليقات توضيحية خاصة بالموقع الإلكتروني الذي تم فيه التصريح (
keepannoو@VisibleForTesting) أو قواعد المكتبة التي تم تصديرها (export_proguard_flags_files: true) أو الربط الثابت للاختبار بدلاً من ملفات.flagsالمنفصلة (راجِع مقالة التدقيق في قواعد الاحتفاظ الحالية ونقلها).
قبل إضافة ملف proguard.flags أو keep.xml مخصّص أو الاحتفاظ به، تحقَّق مما إذا كانت القاعدة معالَجة تلقائيًا من خلال خطوط الأساس التلقائية أو ما إذا كان يمكن نقلها إلى تعليقات توضيحية للرمز.
التعرّف على قواعد الاحتفاظ التلقائية في المنصة
تُمرِّر أداة Soong تلقائيًا قواعد خط الأساس العامة التالية إلى كل عملية استدعاء لأداة R8 على الاستهدافات التي يتم فيها تجميع DEX (android_app وandroid_test ووحدات java_library القابلة للتثبيت)، والتي تم ضبطها في build/soong/java/dex.go:
-
build/make/core/proguard.flags: تحافظ على الفئات والعناصر التي تمّت إضافة التعليق التوضيحي@com.android.internal.annotations.VisibleForTestingإليها في جميع الحِزم، وعلى تلك التي تمّت إضافة التعليق التوضيحي@VisibleForTestingإليها (androidx.annotation.VisibleForTestingأوcom.google.common.annotations.VisibleForTesting) في الحِزمandroid.**وcom.android.**وcom.google.android.**. ويحتفظ أيضًا@TestApiو@Keep(androidx.annotationوandroid.support.annotationوcom.android.internal.annotations) و@KeepForWeakReferenceو@WeaklyReferencedCallbackو@dalvik.annotation.optimization.**. -
build/make/core/proguard_basic_keeps.flags: تحتفظ هذه السمة بسماتSourceFile، مثل سمات تتبُّع تسلسل استدعاء الدوال البرمجية، والتعليقات التوضيحية الخاصة بإمكانية رصد وقت التشغيل (RuntimeVisible*Annotations)، وسماتExceptionsوAnnotationDefault، وطُرقnative، وعناصرSerializable، وطُرق@JavascriptInterface، ومنشئاتThrowable(String)، وحقولParcelable$CREATOR، وحقولMessageLiteفي بروتوكول Buffer. -
build/make/core/proguard/kotlin.flags: يتم تجاهل التحذيرات غير الضارة لبعض التعليقات التوضيحية الوصفية في Kotlin (kotlin.Metadataوkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target})، كما تتم إزالة التعليقات التوضيحيةDebugMetadataفي Kotlin من إصدارات الإنشاء. build/make/core/proguard/checknotnull.flags: يستبدل عمليات الاستدعاء الشائعة للمساعد في التحقّق من القيمة الفارغة (com.google.common.base.Preconditions.checkNotNullوdagger.internal.Preconditions.checkNotNull*) بعمليات تحقّق موجزة من القيمة الفارغة في رمز البايت. يحذف هذا الملفObjects.requireNonNullعمدًا للحفاظ على رسائل الاستثناءات الواضحة على مستوى حدود واجهة برمجة التطبيقات الخاصة بالإطار.build/make/core/proguard/enumvalues.flags: تحتفظ بالطريقتَينvaluesوvalueOfلأنواعenumما لم يتم إيقافهما من خلال إعدادات التصميم.- قواعد AAPT2 التي يتم إنشاؤها تلقائيًا: تفحص أداة AAPT2 ملفات
AndroidManifest.xmlالمدمجة وملفات XML الخاصة بالتصميم وملفات XML الخاصة بالإعدادات المفضّلة لإنشاء قواعد إبقاء دقيقة لكلActivityوServiceوBroadcastReceiverوContentProviderوBackupAgentوApplicationوViewوPreferenceوandroid:onClickمسجّلة.
تدقيق قواعد الاحتفاظ الحالية ونقلها
عند تدقيق ملفات proguard.flags أو keep.xml الحالية في مستودع
منصة، قيِّم كل قاعدة بالترتيب وفقًا للتسلسل الهرمي التالي المكوّن من أربع خطوات:
الخطوة 1: حذف القواعد الزائدة أو القديمة
احذف القواعد التي تغطيها خطوط الأساس العامة أو بيان AAPT2 وقواعد التنسيق أو الإعدادات التلقائية في R8، بالإضافة إلى القواعد التي تشير إلى فئات أو حِزم لم تعُد متوفرة.
إذا كانت جميع القواعد في ملف .flags مكرّرة، احذف الملف وأزِل proguard_flags_files من Android.bp. إذا كان القسم المتبقي optimize يعيد فقط ذكر الإعدادات التلقائية، أزِل القسم المكرّر ونسِّق ملف الإنشاء باستخدام bpfmt -w Android.bp. عند تنظيف حزمة، تحقَّق مما إذا كانت الوحدات المصاحبة في الأدلة الفرعية (مثل صيغ استهداف Kotlin) تشير إلى ملف العلامات نفسه وعدِّلها معًا.
الخطوة 2: نقل قواعد الاحتفاظ المستندة إلى الاختبار
نقل قواعد الاحتفاظ المستندة إلى الاختبارات من ملفات الإنتاج المخصّصة .flags باستخدام أحد الأنماط التالية:
ربط مكتبة التنفيذ بالاختبارات (
static_libs): عندما تستخدمandroid_testتفاصيل التنفيذ الخاصة بالحزمة أو الداخلية في أحد التطبيقات، تكون بنية النظام الأساسي المقترَحة هي وضع ملفات مصدر التطبيق فيandroid_library(MyApp.impl) وربطMyApp.implبشكل ثابت بكل منMyAppوMyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }يتيح نمط الوحدات الثلاث هذا تشغيل
MyAppTestsكاختبار لقياس حالة التطبيق مزوَّد بأدوات ذاتية مع إمكانية الوصول الكامل إلى الفئات الداخلية، كما يتيحMyAppتفعيلobfuscate: trueبحرية بدون إيقاف الاختبارات، ويتجنّب إرسال نقاط دخول خاصة بالاختبار فقط في حِزمة APK النهائية، ويزيل الحاجة إلى إعادة إنشاءMyAppعند تغيير رمز الاختبار.إضافة تعليقات توضيحية إلى نقاط ربط الاختبار باستخدام
@VisibleForTesting: إذا كان ملف APK خارجي للاختبار يستدعي عددًا صغيرًا من الطرق أو الدوال الإنشائية الداخلية علىandroid_appمستهدف وكان من غير العملي إعادة هيكلة الملف إلى مكتبة.impl، أضِف تعليقات توضيحية إلى تلك التعريفات باستخدام@VisibleForTesting. تحتفظ قاعدة بياناتbuild/make/core/proguard.flagsالعالمية@VisibleForTestingبالعناصر في حِزمandroid.**وcom.android.**وcom.google.android.**تلقائيًا بدون ملفات.flagsمخصّصة. (بالنسبة إلى وحدات المورّد خارج مساحات الأسماء هذه، استخدِم@UsedByReflectionأو قواعد المكتبة التي تم تصديرها).استخدام
trace_references_fromعلى مستوى حدود المكتبة بين الأنظمة الأساسية: عندما يتعذّر على الاختبارات الربط بشكل ثابت بالتنفيذ المستهدف (على سبيل المثال، الاختبارات التي تستخدم خدمات خادم النظام أو ملفات JAR الخاصة بالنظام الأساسي، مثلframework-connectivity)، اضبطtrace_references_fromعلى الوحدة النمطية المستهدَفة التي تشير إلى وحدة نمطية مصاحبة لمكتبة Java تحتوي على مصادر الاختبار. يتتبّع R8 جميع الفئات والعناصر التي يشير إليها الرمز الثانوي ويحتفظ بها.
الخطوة 3: تصدير القواعد من المكتبة المالكة
إذا كان java_library أو android_library مشتركًا يتطلّب قواعد الاحتفاظ بالبيانات من أجل
الاستخدام الداخلي أو عمليات معاودة الاتصال عبر JNI، عليك تحديد القواعد في هدف المكتبة
وضبط export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
تتضمّن جميع أهداف android_app وأهداف المكتبة التي يتم ربطها بشكل ثابت
my-shared-library هذه القواعد تلقائيًا، وبالتالي لا تحتاج التطبيقات التابعة إلى تكرارها. عندما تحتاج وحدات متعدّدة في أدلة مختلفة إلى مشاركة مجموعة قواعد غير مرتبطة بمكتبة رموز برمجية واحدة، يمكنك نشر ملف .flags من خلال filegroup صريح في Android.bp حتى تتمكّن الوحدات من الإشارة إلى :my-shared-flags بشكل واضح على مستوى حدود الحزمة. للحصول على إرشادات عامة حول إنشاء قواعد الاحتفاظ بالمستهلكين في المكتبة بدون فرض قيود على تحسين التطبيقات التابعة، يُرجى الاطّلاع على التحسين لمؤلفي المكتبات.
الخطوة 4: إضافة تعليقات توضيحية إلى التصريحات في الرمز المصدر
عند الوصول إلى فئة أو طريقة أو أداة إنشاء أو حقل من خلال الانعكاس أو JNI ولم يتم تضمينها في AAPT2 أو خطوط الأساس العامة، استبدِل قواعد -keep المنفصلة في ملفات .flags بالتعليقات التوضيحية للمصدر من دليل التعليقات التوضيحية الخاصة بالاحتفاظ (راجِع مرجع Javadoc الخاص بـ keepanno):
-
@UsesReflection: ننصحك باستخدام هذه التعليقات التوضيحية عندما تتحكّم في الرمز البرمجي الذي ينفّذ الانعكاس. ضَعها في الموقع الإلكتروني الذي يتم فيه استدعاء الانعكاس لتحديد الفئات أو الطرق أو الحقول المستهدَفة التي يتم الوصول إليها بشكل ديناميكي. بما أنّ@UsesReflectionيشفّر تلقائيًا شرطًا مسبقًا بأنّ موقع الاتصال الذي تمت إضافة التعليقات التوضيحية إليه يمكن الوصول إليه، يزيل R8 كلاً من المتصل والهدف الانعكاسي إذا لم يتم استخدام موقع الاتصال. @UsedByReflection: ضعها على الفئات أو الطرق أو الحقول أو الدوال الإنشائية التي يتم إنشاء مثيل لها أو استدعاؤها بشكل انعكاسي بواسطة رمز أو مكتبات خارجية، مثل الفئات التي يتم تحميلها باستخدامClass.forNameمنBundleأوSettings، أو فئات المكوّنات الإضافية التي يتم تحميلها عبر أدوات تحميل الفئات الديناميكية. حدِّدkind(مثلKeepItemKind.CLASS_AND_METHODS) وpreconditions(@KeepCondition) وقيود المَعلمات لكي يحتفظ R8 بالعقد الانعكاسي الدقيق فقط ويظل بإمكانه تحسين أو إزالة الأعضاء غير المستخدَمين من الفئة. لا تُضِف@UsedByReflectionإلى المكوّنات المسجّلة في البيان، مثلJobServiceأوBroadcastReceiver، لأنّ أداة AAPT2 تحتفظ بها تلقائيًا.@UsedByNative: يتم وضعها على الطرق أو الحقول التي يتم الوصول إليها من رمز JNI للغة C أو C++ باستخدامGetMethodIDأوGetStaticMethodIDأوGetFieldID.- استبدِل
@KeepForApi: ضعها على فئات أو عناصر واجهة برمجة التطبيقات في المكتبة التي يجب أن تظل سليمة عند تصغير المكتبة نفسها قبل توزيعها. -
@Keep(androidx.annotation.Keep): تُستخدَم كبديل عندما لا يكونkeepannoمناسبًا. يؤدي تطبيق@Keepعلى فئة إلى الاحتفاظ بالفئة وجميع عناصرها بدون شروط. يؤدي تطبيق@Keepعلى طريقة أو حقل إلى إنشاء نقطة دخول غير مشروطة تحتفظ بالعنصر والفئة التي يتضمّنها حتى إذا لم يتم إنشاء مثيل للفئة مطلقًا، بينما يعبّرkeepannoعن إمكانية الوصول الشرطية.
لاستخدام تعليقات R8 التوضيحية keepanno في وحدة Soong، اتّبِع الخطوات التالية:
أضِف
"keepanno-annotations"إلىlibsفيAndroid.bp:libs: [ "keepanno-annotations", ],استورِد
com.android.tools.r8.keepanno.annotations.*وأضِف تعليقًا توضيحيًا إلى تعريف أو موقع استدعاء في رمز المصدر Java أو Kotlin:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }يحوّل R8 تعليقات
keepannoالتوضيحية مباشرةً إلى نموذج قاعدة الاحتفاظ الداخلية، ويزيل التعليقات التوضيحية من ناتج DEX النهائي، ما يؤدي إلى عدم إضافة أي تكلفة إضافية لوقت تشغيل الرمز الثانوي.
تجنُّب الأخطاء الشائعة
توضّح الأقسام التالية المشاكل الشائعة في proguard.flags وkeep.xml ضمن رمز المنصة وكيفية حلّها.
قواعد الاحتفاظ بالمكوّنات الواسعة
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- سبب التأثير السلبي في الأداء: هذه القاعدة مكرّرة تمامًا مع AAPT2.
ونظرًا لأنّه يستخدم حرف بدل بدون التحقّق مما إذا كان المكوّن مسجّلاً في
AndroidManifest.xmlالنهائي الذي تم دمجه، فإنّه يجبر R8 على الاحتفاظ بكل فئة فرعية منActivityأوServiceأوBroadcastReceiverتم العثور عليها في أي مكان في مسار الفئة، بما في ذلك مكوّنات المكتبة غير المستخدَمة وأنشطة تصحيح الأخطاء غير المفعَّلة. - الحلّ المقترَح: احذف القاعدة. يفحص AAPT2 ملف البيان المدمج وينشئ قواعد
-keepدقيقة للعناصر المسجّلة.
قواعد الاحتفاظ بمعالج النقر على XML الواسع
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- سبب التأثير السلبي في الأداء: يؤدي الاحتفاظ بكل طريقة
public void *(View)في كل فئة في الوحدة إلى منع R8 من إزالة أي طريقة أو تضمينها إذا كانت تقبل مَعلمةViewواحدة. - الحلّ المقترَح: احذف القاعدة. يفحص AAPT2 ملفات XML الخاصة بالتصميم ويضع قواعد استبقاء مستهدَفة للطُرق التي تشير إليها سمات
android:onClick.
قواعد الاحتفاظ بعناصر إرجاع القيمة وعناصر تحديد القيمة في "العرض الواسع"
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- سبب تأثيرها سلبًا في الأداء: تجبر هذه القاعدة أداة R8 على الاحتفاظ بكل دوال الجلب والتعديل في كل فئة فرعية من
Viewفي التطبيق وجميع المكتبات المرتبطة (بما في ذلك مكتبات AndroidX وMaterial)، ما يمنع إزالة الدوال غير المستخدَمة وتضمينها في رمز واجهة المستخدم. - الحلّ المقترَح: احذف القاعدة. يحتفظ AAPT2 حاليًا بمنشئات
لفئات
Viewالمخصّصة التي يتم تضخيمها من ملفات XML للتصميم. إذا كان الرمز البرمجي يحرك سمة عرض باستخدام أسماء سلاسل انعكاسية، مثلObjectAnimator.ofFloat(view, "translationZ", ...)، استبدِل اسم السلسلة بمرجع سمة مكتوب (View.TRANSLATION_ZأوFloatPropertyأوIntPropertyمخصّص) لتجنُّب الانعكاس تمامًا. إذا كان لا يمكن تجنُّب الوصول إلى سمة انعكاسية، أضِف التعليق التوضيحي@UsedByReflectionإلى أداة الجلب أو الضبط المحدّدة.
إيقاف التحسين أو التشويش الشامل
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- سبب التأثير السلبي في الأداء: يؤدي وضع
-dontoptimizeأو-dontshrinkداخل ملف.flagsإلى تجاهل إعداداتAndroid.bpالخاصة بالوحدة النمطية بدون تنبيه، كما يؤدي إلى إيقاف عمليات التحسين على مستوى الهدف بأكمله. والأسوأ من ذلك، إذا صدّرت إحدى المكتبات ملف.flagsيحتوي على-dontoptimize، سيؤدي ذلك إلى إيقاف تحسين R8 لكلandroid_appلاحق يربط المكتبة. - الحلّ المقترَح: أزِل
-dontoptimizeو-dontshrinkو-dontobfuscateمن ملفات.flags. يمكنك التحكّم في سلوك التحسين بشكلٍ صريح فيAndroid.bpباستخدام الحظرoptimize(shrinkوoptimizeوobfuscate) في الاستهداف النهائي.
علامات التشخيص في القواعد التي تمّت الموافقة عليها
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- سبب حدوث المشكلة في عملية الإنشاء: تؤدي علامات التشخيص إلى إغراق سجلّات الإنشاء أو محاولة كتابة ملفات الإخراج إلى مسارات محلية أثناء عمليات إنشاء Soong في بيئة معزولة.
- الحل المقترَح: احذف هذه العلامات من ملفات
.flagsالتي تم إرسالها. تكتب أداة Soong تلقائيًا مخرجات الربط والاستخدام الخاصة بأداة R8، مثلproguard_dictionaryوproguard_usage.zip، إلى الدليل الوسيط للوحدة ضمنout/soong/.intermediates/.
قواعد الاحتفاظ بالإنتاج للرمز البرمجي للاختبار
- سبب التأثير السلبي في الأداء: تؤدي إضافة قواعد
-keepمخصّصة للوصول إلى الاختبار فقط إلى إبقاء الرمز البرمجي غير مشفَّر والاحتفاظ به في إصدارات الإنتاج. على الرغم من أنّ إضافة التعليقات التوضيحية إلى الطرق باستخدام@VisibleForTestingأو ضبطtrace_references_fromيؤدي إلى تجنُّب الحفاظ على قواعد-keepاليدوية، إلا أنّ هذه الرموز تظل مضمّنة في الرمز الثنائي الخاص بالإنتاج. - الحلّ المقترَح: لإبقاء نقاط الدخول المخصّصة للاختبار فقط خارج حِزمة APK الخاصة بالإنتاج، قسِّم التطبيق إلى مكتبة
.implواربطها بحِزمة APK للاختبار ذاتية القياس باستخدامstatic_libs، كما هو موضّح في الخطوة 2: نقل قواعد الإبقاء المستندة إلى الاختبار.
أحرف بدل شاملة للمراجع في ملف keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- سبب التأثير السلبي في الأداء: تحتفظ أحرف البدل الشاملة بكل مورد من النوع المطابق في الوحدة النمطية والموارد التابعة لها، ما يؤدي إلى إبطال تصغير الموارد (
shrink_resources: true) وتضخيم حزمة APK وجدولresources.arscالذي تم ربطه. - الإجراء المقترَح لحلّ المشكلة:
- تفضيل مراجع الموارد الثابتة على
keep.xml: تجنَّب استخدام طريقةResources.getIdentifierللبحث عن مجموعة محدودة من الموارد بشكل ديناميكي، مثل سلاسل التجارب المرقمة أو الرسومات المتوافقة مع سمة معيّنة. بدلاً من ذلك، استخدِم عبارةswitchفي وقت الترجمة أو قم بتعيين الثوابت الثابتةR.idأوR.stringأوR.drawable. تتيح المراجع الثابتة لأداتَي R8 وAAPT2 تتبُّع الموارد النشطة بدقة، وإزالة النفقات العامة لعمليات البحث عن السلاسل في وقت التشغيل، وإلغاء الحاجة إلىkeep.xmlتمامًا. - إدراج أسماء مراجع محدّدة: إذا كان البحث الديناميكي مطلوبًا، مثلاً
من خلال قارئ تراخيص تابع لجهة خارجية، أدرِج معرّفات المراجع الدقيقة في
tools:keep(على سبيل المثال،tools:keep="@raw/third_party_licenses"). - حذف ملفات
keep.xmlالمكرّرة: إذا كانت الموارد المدرَجة تتم الإشارة إليها بشكل ثابت في الرمز (R.raw.foo) أو XML (@raw/foo)، سيحتفظ بها أداة التصغير تلقائيًا، لذا يمكنك حذفres/raw/keep.xml.
- تفضيل مراجع الموارد الثابتة على
التحقّق من تغييرات القواعد وتدقيقها
عند إزالة قواعد الاحتفاظ أو تضييق نطاقها في proguard.flags أو keep.xml،
تأكَّد من أنّ عملية إنشاء الوحدة تتم بدون أخطاء، وأنّها تجتاز اختبارات الوحدات واختبارات الأجهزة،
وتحتفظ بجميع نقاط الدخول المطلوبة.
إنشاء الوحدات واختبارها
أنشئ الوحدة بشكل سليم للتأكّد من أنّ R8 وAAPT2 يكتملان بدون تحذيرات بشأن مراجع غير متوفّرة:
m <MODULE_NAME>نفِّذ اختبارات الوحدة والاختبارات الآلية للوحدة باستخدام
atest:atest <TEST_MODULE_NAME>بالنسبة إلى تطبيقات النظام أو الخدمات ذات الامتيازات أو الوحدات النمطية التي تعتمد على الأجهزة، شغِّل اختبارات الأجهزة واختبارات واجهة المستخدم على الأجهزة المادية المستهدَفة أو في مختبر اختبار الأجهزة الخاص بك لتنفيذ مسارات الانعكاس في وقت التشغيل وعمليات ربط الاتصال بين العمليات (IPC) وتضخيم الموارد.
فحص الاختلافات في DEX والموارد
قارِن حِزمة APK أو JAR التي تم تجميعها قبل وبعد تغييرات القواعد للتأكّد من أنّ R8 يزيل الرموز البرمجية والموارد غير المستخدَمة بدون إزالة نقاط الدخول المتوقّعة:
استخدِم
apkanalyzerأوdexdumpلفحص الفئات والطُرق والحقول المحتفظ بها في حِزم APK أو ملفات DEX الناتجة:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkعند تعديل
keep.xmlأو تفعيلshrink_resources، استخدِمaapt2 dump resourcesللتحقّق من أنّ الإصدار يزيل الموارد غير المستخدَمة ويحتفظ بالموارد المطلوبة:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
تحليل نصف قطر الاحتفاظ بالبيانات وتضمين القواعد باستخدام R8
يتضمّن برنامج الترجمة البرمجية R8 مفتوح المصدر أداة تحليل نطاق الاحتفاظ بالبيانات
(تتوفّر أيضًا في "استوديو Android" وGradle باسم
أداة تحليل إعدادات R8) تقيس التأثير الدقيق
لكل قاعدة احتفاظ بالبيانات أثناء الترجمة البرمجية. يدمج Soong هذا المحلّل مباشرةً في إصدار نظام Android الأساسي (يتم ضبطه في build/soong/java/dex.go).
لتحليل قواعد الاحتفاظ بوحدة نظام أساسي أو في إصدار كامل، اتّبِع الخطوات التالية:
نفِّذ الإصدار باستخدام متغيّر البيئة
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>عند ضبط
R8_DUMP_KEEP_RADIUS=true، يطلب Soong من R8 تسجيل مقاييس قواعد الاحتفاظ في ملفr8keepradius.pbوسيط لكل وحدة تم تجميعها ضمنout/soong/.intermediates/. بالنسبة إلى كل قاعدة حفظ وتعليق توضيحيkeepanno، يسجّل R8 ما يلي:- نطاق الحفاظ الفوري: الفئات والحقول والطرق الدقيقة التي تحتفظ بها القاعدة، بالإضافة إلى القيود المحدّدة التي تفرضها على التصغير أو التحسين أو التشويش
- الاستيعاب: قواعد الاحتفاظ أو التعليقات التوضيحية الأخرى التي تحتفظ بالعناصر نفسها إذا كانت قاعدة مخصّصة مشمولة بالكامل بقاعدة AAPT2 أو قاعدة أساسية عامة، يمكنك حذفها بأمان.
- القواعد على مستوى الحزمة والقواعد العامة: القواعد التي تستخدم أحرف بدل واسعة النطاق على مستوى الحزمة أو تطبّق توجيهات الإعدادات العامة.
حوِّل ناتج
r8keepradius.pbإلى تقرير HTML تفاعلي باستخدامKeepRadiusHtmlReportGenerator(المضمّن فيprebuilts/r8/r8.jar):# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsعند توفير دليل، يتنقّل
KeepRadiusHtmlReportGeneratorبين جميع*keepradius*.pbالملفات، وينشئ تقرير HTML لكل وحدة، وينشئout/keep_radius_reports/keepradius.htmlيلخّص عدد العناصر النشطة، وعدد العناصر المحفوظة، وقواعد الحفظ ذات النطاق الأوسع على مستوى الإصدار. لمزيد من المعلومات حول تفسير نتائج التصغير والتحسين والتشويش في التقرير الذي تم إنشاؤه، راجِع مقالة استخدام أداة تحليل إعدادات R8.
تتيح أداة Soong أيضًا استخدام متغيرَي بيئة تشخيص إضافيَين في R8 في
build/soong/java/dex.go:
-
R8_DUMP_INPUT=true: يكتبr8inputs.zipإلى الدليل الوسيط للوحدة النمطية الذي يحتوي على جميع ملفات JAR المدخلة وملفات JAR الخاصة بالمكتبة وإعدادات ProGuard المدمجة لإعادة إنتاج R8 المستقل. R8_DUMP_PERFETTO_TRACE=true: يكتبr8trace.ptraceفي الدليل الوسيط للوحدة من أجل فحص عمليات تجميع R8 في Perfetto.
التحقّق من صحة قواعد المستهلك في المكتبة
عندما تحتفظ عمليات التصدير java_library أو android_library بالقواعد للمستهلكين في المراحل اللاحقة (export_proguard_flags_files: true)، يجب ألا تتضمّن هذه القواعد علامات عامة تغيّر عملية التحسين أو توقفها للتطبيق المستهلك.
توفّر أداة R8 أداة تحليل مفتوحة المصدر لقواعد الاحتفاظ بالبيانات (ProcessKeepRules)، يتم عرضها في إصدار نظام Android الأساسي على أنّها أداة المضيف process-keep-rules (محدّدة في prebuilts/r8/Android.bp):
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
تحلّل أداة process-keep-rules كل ملف إعداد وتتعذّر مع عرض معلومات تشخيصية خاصة بالملف والسطر إذا صادفت توجيهات غير مسموح بها في قواعد مستخدمي المكتبة. تشمل التوجيهات غير المسموح بها عمليات التحسين أو التصغير أو التشويش على مستوى العالم (مثل -dontoptimize أو -dontshrink)، وعمليات إعادة تجميع الحِزم وعلامات تعديل الوصول، وعلامات التشخيص أو الربط، و-keepattributes على مستوى التطبيق.
تدقيق شجرات المصدر باستخدام pgaudit.py
للتدقيق في أدلة المصدر غير المنشأة إلى جانب أدوات تحليل R8 أثناء وقت الإنشاء، تتضمّن شجرة نظام Android الأساسي النص البرمجي pgaudit.py ضمن build/make/core/proguard/tools/. تفحص أداة التحليل الثابت هذه ملفات Android.bp وproguard.flags وkeep.xml في جميع المستودعات لتحديد القواعد التي تغطيها AAPT2 أو خطوط الأساس للمنصات العالمية، وأحرف البدل العامة، وعمليات إلغاء -dontoptimize، وقواعد الإبقاء المخصصة للاختبار فقط:
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
يمكن لفِرق المنصات ومصنّعي المعدات الأصلية الجمع بين pgaudit.py لتحديد المشاكل بسرعة في شجرة المصدر، وprocess-keep-rules للتحقّق من صحة قواعد المكتبة التي تم تصديرها، وR8_DUMP_KEEP_RADIUS=true مع KeepRadiusHtmlReportGenerator لقياس نطاق الاحتفاظ الدقيق بالصفوف والحقول والطرق لكل قاعدة في الإصدار.