كيفية إعداد قواعد تشويش ProGuard لحزم Android SDK بأمان

opoinstall
2026-11-11
5 min read

كيف تستثني حزم SDK للهواتف المحمولة من تشويش ProGuard على Android؟ يتطلب استثناء حزمة Android SDK من التشويش (Obfuscation) التحقق مما إذا كانت المكتبة تتضمن قواعد ProGuard مدمجة للمستهلك، وإضافة توجيهات -keep و -keepclassmembers محددة النطاق بدقة لنقاط الدخول التي يتم الوصول إليها عبر الانعكاس (Reflection)، وواجهات رد النداء (Callbacks)، وجسور JNI، ونماذج البيانات، بالإضافة إلى تدقيق الرموز المحفوظة عبر تقارير تشخيص R8 والفحص المباشر لملفات DEX الخام. عندما تقوم فرق هندسة Android بتفعيل تقليص الكود (Code Shrinking) وتحسينه لإصدارات الإنتاج، يقوم مترجم R8 بإزالة البايت كود غير المشار إليه وإعادة تسمية الفئات والحقول والدوال إلى معرفات قصيرة. إذا كانت حزمة تطوير البرمجيات (SDK) الخاصة بإسناد الهواتف المحمولة تعتمد على الانعكاس الديناميكي أو استدعاءات JNI غير المحمية ضد الإزالة الصارمة وإعادة التسمية، فإن Deferred Deep Linking، وإلغاء تسلسل نماذج JSON، وبناء حزم القنوات يمكن أن تفشل بصمت في بيئة الإنتاج حتى بعد اجتياز جميع الاختبارات في إصدار تصحيح الأخطاء (Debug) غير المصغر. توفر OpoInstall، وهي منصة لإسناد الهواتف المحمولة و Deferred Deep Linking، أرشيفات Android SDK أصلية ومسارات عمل لحزم القنوات التي يمكن لفرق الهندسة إعدادها والتحقق منها داخل مسارات الإصدار المصغرة بواسطة R8.

في سلاسل أدوات Android Gradle Plugin (AGP) الحديثة، تعمل ملفات قواعد الاحتفاظ (proguard-rules.pro في DSL القديم أو src/<variant>/keepRules/*.keep في AGP 9.3+) بمثابة عقد إعداد القواعد لمترجم R8 من Google. إن استثناء حزمة Android SDK خارجية من التشويش بأمان يعني الحفاظ فقط على نقاط دخول واجهة برمجة التطبيقات (API) العامة المحددة، والمنشئات الانعكاسية، وواجهات رد النداء، واستدعاءات JNI الصاعدة، وحقول النماذج المطلوبة في وقت التشغيل—مع تجنب قواعد الاحتفاظ الواسعة للغاية على مستوى الحزمة أو كتم التحذيرات غير المتحقق منها.

المصطلح التعريف الكيان المرتبط دور نية البحث
Android SDK مكتبة Java أو Kotlin مترجمة يتم دمجها في تطبيق Android لتوفير قدرات متخصصة مثل إسناد التثبيت. البايت كود classes.dex توجيهي / إجرائي
ProGuard / R8 أدوات وقت البناء في Android تعمل على تقليص الكود غير المستخدم، وتحسين البايت كود، وتشويش المعرفات باستخدام صيغة قواعد ProGuard. proguard-rules.pro تقني / معلوماتي
Consumer ProGuard Rules قواعد تشويش مدمجة داخل ملف .aar (/proguard.txt) أو .jar (/META-INF/proguard/) يتم دمجها تلقائياً في R8. consumerProguardFiles تقني / استكشاف الأخطاء وإصلاحها
R8 Diagnostic Reports ملفات المخرجات (mapping.txt، seeds.txt، usage.txt، configuration.txt) التي تفصّل إعادة تسمية الرموز، وبذور الاحتفاظ المتطابقة، والكود المحذوف. تدقيق بناء الإصدار (Release Build) تقني / استكشاف الأخطاء وإصلاحها

لماذا يؤدي تشويش R8 و ProGuard إلى تعطيل استدعاءات الإسناد في Android SDK

تحسين Android R8 يحافظ على رموز SDK الأساسية أثناء ترجمة إصدار الإنتاج.

فخ Debug مقابل Release: لماذا يعمل الإسناد في الاختبار ولكنه يفشل في الإنتاج

تحدث إحدى أكثر تراجعات هندسة الإصدارات شيوعاً في اكتساب مستخدمي الهواتف المحمولة عندما يعمل Deferred Deep Linking الخاص بالتطبيق (handleDeepLink) واستعادة معلمات التثبيت (getInstallParam) بشكل موثوق أثناء اختبارات ضمان الجودة (QA) على إصدارات التصحيح (Debug)، ولكنها تُرجع حمولات فارغة أو تتوقف عن إطلاق استدعاءات الرد (Callbacks) بمجرد ترجمة التطبيق كملف APK موقّع للإصدار أو حزمة تطبيق Android (.aab).

يكمن السبب الجذري في التفاوت بين أنواع البناء التي تم إعدادها في Gradle. بشكل افتراضي، يقوم نوع البناء debug بتعطيل تقليص الكود لتسريع عمليات البناء التزايدية. عندما يقوم المهندسون بترجمة متغير release مع تفعيل تحسين R8، يقوم Android Gradle Plugin باستدعاء مترجم Android R8 لتنفيذ ثلاث تحويلات على البايت كود عبر كل من التطبيق المضيف والمكتبات الخارجية المرتبطة:

  • تقليص الكود (Tree Shaking): يقوم R8 بتحليل الرسم البياني لاستدعاءات التطبيق بدءاً من نقاط الدخول المعلنة (مثل مكونات AndroidManifest.xml وبذور -keep الصريحة) ويزيل أي فئة أو منشئ أو دالة أو حقل يبدو غير قابل للوصول إليه من خلال التحليل الثابت.
  • تحسين البايت كود وتعديل الوصول: يقوم R8 بتضمين الدوال (Inlining)، وإزالة المعلمات غير المستخدمة، ودمج الفئات، وعند اقترانه بتوجيه -allowaccessmodification في إعدادات التحسين الافتراضية لنظام Android، يقوم بتعديل علامات رؤية الفئات والأعضاء (مثل توسيع الأعضاء ذات النطاق private أو الخاص بالحزمة إلى public أثناء التضمين) لتقليل حجم ملفات Dalvik Executable (.dex) وتحسين سرعة التنفيذ في وقت التشغيل.
  • تشويش المعرفات (إعادة التسمية وإعادة الحزم): يقوم R8 بإعادة تسمية الفئات والحزم والحقول والدوال المتبقية إلى معرفات مدمجة (مثل a.a.b()) وقد يعيد تجميع الفئات المشوشة في تسلسلات هرمية مسطحة للحزم، مما يقلل من عبء تجمع السلاسل النصية داخل classes.dex.

تم تفعيل الوضع الكامل (Full Mode) في R8 بشكل افتراضي منذ إصدار Android Gradle Plugin (AGP) 8.0. ومقارنة بوضع التوافق القديم مع ProGuard، يطبق الوضع الكامل في R8 تحسينات أكثر صرامة بكثير—حيث يزيل المنشئات الافتراضية الخالية من الوسائط (<init>())، ويغير معدّلات الوصول للأعضاء، ويجرد سمات البيانات الوصفية ما لم يتم الاحتفاظ بالفئات والأعضاء المرتبطة بها صراحةً بواسطة قاعدة احتفاظ متطابقة. للحصول على سياق أساسي حول الهياكل الثنائية لملفات .aar ومعايير اقتناء حزم SDK، راجع أدلتنا المصاحبة، فهم معاني Mobile SDK وبروتوكولات النمو التقني وكيفية اختيار حزمة تطوير برمجيات (SDK) للإسناد الدقيق.

ستة أنماط فشل مميزة في حزم SDK للإسناد المشوشة

تتطلب استدعاءات الانعكاس والنماذج و JNI في Android SDK قواعد احتفاظ مستهدفة في R8.

نظراً لأن التحليل الثابت للرسم البياني للاستدعاءات لا يمكنه التنبؤ بعمليات البحث الديناميكية في وقت التشغيل، فإن حزمة Android SDK غير المحمية يمكن أن تواجه ستة أنماط فشل مميزة تحت الوضع الكامل لـ R8. يجب على المهندسين التمييز بين أنماط الفشل هذه بعناية، حيث يتطلب كل منها هدف حماية محدد في ProGuard:

نمط الفشل في وقت التشغيل السبب الأساسي في بايت كود R8 هدف الحماية المحدد المطلوب
1. فشل البحث الانعكاسي عن الفئة (ClassNotFoundException) يقوم R8 بإعادة تسمية أو إعادة حزم أو إزالة فئة يتم تحميلها ديناميكياً بالاسم النصي عبر Class.forName() الحفاظ على الفئة المستهدفة ومعرفها الأصلي باستخدام -keep class ...
2. فشل إنشاء الكائنات الانعكاسي (NoSuchMethodException: <init>) في الوضع الكامل لـ R8، حتى لو تم الاحتفاظ بالفئة، يتم تجريد منشئها الافتراضي الخالي من الوسائط (<init>()) إذا لم يتم استدعاؤه مباشرة في الكود الحفاظ صراحةً على توقيع المنشئ (<init>();) داخل كتلة -keep الخاصة بالفئة
3. فشل رد النداء الديناميكي وتعديل الوصول يقوم R8 بإزالة أو إعادة تسمية دوال واجهة رد النداء، أو يغير علامات رؤية الأعضاء المتوقعة بواسطة استدعاءات getDeclaredMethod() / getDeclaredField() الانعكاسية الحفاظ على دوال واجهة رد النداء (-keep interface ... { <methods>; }) وأي أعضاء يتم الوصول إليها انعكاسياً
4. فقدان البيانات الوصفية للتوقيع العام (Generic Signature) في مستمعي رد النداء تخضع الأنواع العامة (Generics) في Java لمحو النوع وقت الترجمة، بينما تحافظ سمات Signature في البايت كود على البيانات الوصفية العامة للانعكاس وقت التشغيل (ResultCallBack<OpoData>). يقوم الوضع الكامل لـ R8 بتجريد البيانات الوصفية لـ Signature ما لم يتم الاحتفاظ بها صراحةً الاحتفاظ بـ -keepattributes Signature بالإضافة إلى الاحتفاظ بكل من واجهة رد النداء العامة وفئة النموذج المستهدفة
5. فشل إلغاء تسلسل نموذج بيانات JSON يقوم R8 بتجريد فئة النموذج (إذا تم إنشاؤها انعكاسياً فقط) أو يعيد تسمية حقولها إلى a، b، c، مما يتسبب في إرجاع محللات JSON القائمة على الانعكاس لحقول فارغة أو null استخدام -keep class ... إذا تم إنشاء النموذج ديناميكياً، أو -keepclassmembers إذا تم الاحتفاظ بالفئة بالفعل من خلال مراجع مباشرة
6. إزالة الاستدعاءات الهابطة لـ JNI والاستدعاءات الصاعدة من Native إلى Java (UnsatisfiedLinkError / NoSuchMethodError) تحافظ قواعد Android الافتراضية على الأسماء في الاستدعاءات الهابطة native المتبقية في Java، ولكنها لا تمنع تقليص دوال RegisterNatives غير المستخدمة، ولا يمكنها رؤية كود C/C++ في ملفات .so الذي يستدعي عائداً إلى Java عبر JNI (FindClass، GetMethodID) استخدام -keep صراحةً لإعلانات native المسجلة ديناميكياً وأي فئات ومنشئات ودوال Java يتم استدعاؤها عبر استدعاءات JNI الصاعدة من C/C++
[Android Release Build: R8 Full Mode Enabled]
                      |
                      v
     [R8 Evaluates Bundled & Host Keep Rules]
                      |
      +---------------+---------------+
      |                               |
      v (Missing / Mismatched Rules)  v (Scoped Keep Rules + Attributes Active)
[SDK Symbols Stripped or Renamed] [Required SDK Entry Points & Models Preserved]
- Reflective () Removed     - Confirmed in -printseeds (seeds.txt)
- JNI Upcall / Model Fields Lost  - Verified in Raw DEX (apkanalyzer dex packages)
- Generic Signature Metadata Lost - Release Attribution Matches Debug Behavior

قواعد المستهلك في حزم AAR مقابل الاستيراد اليدوي لملفات JAR

كيف تدمج مكتبات AAR و JAR قواعد ProGuard للمستهلك

تساهم أرشيفات Android AAR و JAR بقواعد المستهلك في إعدادات R8.

قبل إضافة قواعد استثناء يدوية إلى تطبيقك المضيف، تحقق مما إذا كانت حزمة Android SDK الخارجية تدمج بالفعل قواعد R8 مضمنة. كما هو موثق في دليل مطوري Google Android حول تحسين المكتبات، يدعم كل من AGP و R8 الاكتشاف التلقائي لقواعد المستهلك عبر توزيعات .aar و .jar، بما في ذلك قواعد R8 المستهدفة حسب الإصدار:

تنسيق توزيع SDK موقع قاعدة المستهلك المضمنة سلوك البناء في Android Gradle Plugin / R8
أرشيف Android (.aar) /proguard.txt في جذر AAR (عبر consumerProguardFiles)، أو القواعد المستهدفة حسب الإصدار داخل /classes.jar تحت /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ يتم استخراجها ودمجها تلقائياً في إعدادات R8 الخاصة بالتطبيق المضيف وقت الترجمة
أرشيف Java (.jar) مع قواعد مدمجة /META-INF/proguard/*.pro أو /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ (بما في ذلك متغيرات r8، و r8-from-<X>، و r8-upto-<Y>) يتم اكتشافها وتطبيقها تلقائياً بواسطة AGP و R8 وفقاً لإصدار مترجم R8 النشط
ملف .jar أو .aar مستقل بدون قواعد مدمجة لا يوجد داخل الأرشيف لا يتم توفير حماية مدمجة للمستهلك؛ أضف قواعد احتفاظ على مستوى المضيف فقط عندما يتطلب الانعكاس وقت التشغيل، أو JNI، أو التسلسل، أو الرموز الأخرى التي يتم الوصول إليها ديناميكياً احتفاظاً صريحاً

(ملاحظة حول قواعد تسلسل Gson: كما هو موثق في دليل استكشاف أخطاء Gson وإصلاحها لـ ProGuard و R8، يدمج الإصدار Gson 2.11.0 والإصدارات الأحدث قواعد R8 لـ TypeToken والحقول المزودة بالتعليق التوضيحي @SerializedName. لا تلغي هذه القواعد الحاجة إلى تقييد الوصول الانعكاسي للنماذج، أو توفير منشئات متوافقة خالية من الوسائط، أو اختبار جميع مسارات التسلسل وإلغاء التسلسل في إصدارات الإنتاج المصغرة.)

فحص أرشيف SDK الذي تم تنزيله قبل كتابة القواعد

تعد مطابقة القواعد في ProGuard و R8 حساسة لحالة الأحرف بدقة وتتطلب تطابق مسار الحزمة تماماً. عبر إصدارات SDK المختلفة أو التوزيعات الإقليمية، يمكن أن تختلف مساحات أسماء الحزم—وإذا قمت بنسخ قاعدة من درس تعليمي قديم لا تتطابق فيه حالة أحرف الحزمة مع ملفات .class الفعلية في أرشيفك، فسيقوم R8 بمطابقة صفر من الفئات بصمت.

قبل تحرير قواعد الاحتفاظ الخاصة بك، قم بتنزيل حزمة SDK المستهدفة (مثل Android SDK v2.1.0 من مركز تنزيل Mobile SDK) وافحص قواعدها المضمنة (سواء في /proguard.txt أو داخل classes.jar) جنباً إلى جنب مع التسلسل الهرمي الدقيق لملفات .class:

# 1. Inspect an .aar archive for root consumer rules (proguard.txt) and extract classes.jar to check META-INF rules
unzip -l opoinstall-android-sdk.aar
unzip -p opoinstall-android-sdk.aar proguard.txt
unzip -p opoinstall-android-sdk.aar classes.jar > temp_classes.jar
jar tf temp_classes.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

# 2. If using a standalone .jar archive, inspect embedded META-INF rules and exact case-sensitive .class paths
jar tf libs/OpoInstall_SDK.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

إعداد proguard-rules.pro لحزم Android SDK الخاصة بالإسناد

تجنب الأنماط الخاطئة للاحتفاظ على مستوى الحزمة وكتم التحذيرات الشامل

عند كتابة قواعد مخصصة، يجب على المهندسين اتباع أفضل ممارسات Google في اختيار ما يجب الاحتفاظ به (قواعد الاحتفاظ في R8) لتجنب تعطيل التحسين بدون داعٍ:

  • تفضيل القواعد الضيقة على الأنماط الخاطئة الشاملة للحزمة -keep class package.** { *; }: تجبر القاعدة الشاملة على مستوى الحزمة مترجم R8 على الاحتفاظ بكل فئة مساعدة داخلية في تلك الحزمة وتخطي تحسينها. حدد نطاق قواعدك على الفئات المحددة، والمنشئات (<init>();)، وواجهات رد النداء، وأهداف JNI، وحقول النماذج التي يتم الوصول إليها ديناميكياً في وقت التشغيل (أو استخدم التعليق التوضيحي @UnconditionallyKeep من Jetpack على الكود الذي تتحكم فيه).
  • التمييز بين -keep و -keepclassmembers في نماذج البيانات: يحافظ التوجيه -keepclassmembers فقط على الحقول أو الدوال المحددة إذا تم الاحتفاظ بالفئة الحاوية نفسها في الرسم البياني للاستدعاءات. إذا تم إنشاء نموذج بيانات (مثل OpoData) حصرياً عبر الانعكاس أو تمت الإشارة إليه فقط من خلال الأنواع العامة الممحوة، فاستخدم -keep class ... حتى لا يقوم R8 بإزالة الفئة نفسها.
  • فهم قيود -keepclasseswithmembernames بالنسبة لـ JNI: تتضمن قواعد التحسين الافتراضية لنظام Android التوجيه -keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }. وكما هو محدد في وثائق قواعد الاحتفاظ لـ R8 من Google ودليل ProGuard حول توجيهات الاحتفاظ، فإن -keepclasseswithmembernames يكافئ -keepclasseswithmembers,allowshrinking. فهو يحافظ على الأسماء للفئات والأعضاء المتطابقة التي تنجو من مرحلة التقليص، ولكنه لا يمنع إزالة الفئات غير المشار إليها أو دوال native أثناء التقليص. إذا تم تسجيل دوال native ديناميكياً عبر RegisterNatives بدون مواقع استدعاء مباشرة في Java، أو إذا كان كود C/C++ يستدعي دوال Java عبر استدعاءات JNI الصاعدة، فيجب عليك استخدام قواعد -keep أو -keepclassmembers صريحة وفقاً لكيفية إنشاء الفئة المستهدفة.
  • تجنب توجيهات -dontwarn الشاملة غير المتحقق منها: لا يوفر التوجيه -dontwarn أي حماية ضد التقليص أو إعادة التسمية، بل يكتم تحذيرات المراجع غير المحلولة التي يمكن أن تخفي أعطال NoClassDefFoundError في وقت التشغيل. أضف فقط توجيه -dontwarn ضيق النطاق إذا أبلغ سجل البناء عن فئة مفقودة محددة خاصة بوقت الترجمة فقط وتحققت من أنها اختيارية في وقت التشغيل.
توجيه ProGuard / R8 الصيغة المطولة المكافئة التقليص (الإزالة) التشويش (إعادة التسمية) النطاق الهندسي الموصى به
-keep class ... -keep ممنوع (يتم الاحتفاظ بالفئة والأعضاء المحددة) ممنوع (يتم الاحتفاظ بالأسماء الأصلية) نقاط دخول SDK العامة، وواجهات رد النداء، والفئات التي يتم إنشاؤها ديناميكياً عبر الانعكاس أو JNI (تتضمن <init>(...);)
-keepclassmembers class ... -keepclassmembers يتم الاحتفاظ بالأعضاء فقط إذا تم الاحتفاظ بالفئة ممنوع للأعضاء المتطابقة الحفاظ على <fields>; أو دوال استدعاءات JNI الصاعدة في الفئات التي يتم إنشاؤها مباشرة بالفعل في Java/Kotlin
-keepclasseswithmembernames -keepclasseswithmembers,allowshrinking مسموح به إذا كان غير مشار إليه (allowshrinking) ممنوع إذا نجت الفئة والأعضاء من التقليص الحفاظ الافتراضي على أسماء استدعاءات JNI الهابطة (native <methods>;) عندما يستدعي كود Java دالة native مباشرة
-keepnames class ... -keep,allowshrinking مسموح به إذا كان غير مشار إليه (allowshrinking) ممنوع إذا تم الاحتفاظ بالفئة الاحتفاظ بأسماء فئات قابلة للقراءة لتتبعات المكدس (Stack Traces) دون إجبار الفئات غير المستخدمة على البقاء في classes.dex
-keepattributes ... -keepattributes يحافظ على البيانات الوصفية للعناصر المحفوظة غير متاح (سمة بايت كود) الاحتفاظ بـ Signature (الأنواع العامة)، و InnerClasses، و EnclosingMethod، و *Annotation* في الفئات المحفوظة صراحةً
-dontwarn <specific_class> -dontwarn لا تأثير على التقليص لا تأثير على إعادة التسمية كتم تحذيرات وقت الترجمة فقط لتبعية اختيارية خاصة بوقت الترجمة تم التحقق منها

إعداد Gradle عبر سلاسل أدوات AGP 8.x و AGP 9.3+

قدم Android Gradle Plugin 9.3 كتلة DSL مبسطة باسم optimization ومجلدات keepRules خاصة بالمتغيرات (ملفات .keep)، بينما تستخدم الإصدارات AGP 8.x حتى 9.2 (والإعدادات القديمة التي لا تزال مدعومة في AGP 9.3+) الخصائص isMinifyEnabled و proguardFiles. يجب على المهندسين التمييز بين ثلاثة كائنات إعداد:

كائن الإعداد موقع الملف أو DSL الدور المعماري في عمليات بناء R8
قواعد الاحتفاظ في DSL القديم (AGP 8.x–9.2 والقديم في 9.3+) app/proguard-rules.pro (يتم الإشارة إليه عبر proguardFiles(...)) يعلن عن قواعد الاحتفاظ الخاصة بالمضيف جنباً إلى جنب مع getDefaultProguardFile("proguard-android-optimize.txt")
قواعد الاحتفاظ في DSL التحسين الجديد (AGP 9.3+) app/src/main/keepRules/*.keep أو app/src/<variant>/keepRules/*.keep يعلن عن ملفات .keep عند استخدام optimization { enable = true } (يتم تطبيق قواعد منصة Android الافتراضية تلقائياً؛ ويدعم DSL القديم أيضاً مجموعات مصادر .keep)
توجيهات تقارير تشخيص R8 -printseeds، و -printusage، و -printconfiguration في ملف إعداد R8 تم التحقق منه (proguard-rules.pro) يوجه R8 لإصدار تقارير التدقيق في المسار app/build/outputs/mapping/<buildVariant>/
// Option A: AGP 8.x - 9.2 Legacy DSL (also supported in AGP 9.3+) — app/build.gradle.kts
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

// Option B: AGP 9.3+ Simplified Optimization DSL — app/build.gradle.kts
// Place scoped class/member keep rules in app/src/main/keepRules/sdk-attribution.keep
android {
    buildTypes {
        release {
            optimization {
                enable = true
            }
        }
    }
}

قواعد احتفاظ توضيحية محددة النطاق وتوجيهات تشخيصية

نظراً لأن الوثائق العامة لـ OpoInstall وصفحات الأسئلة الشائعة التاريخية تشير إلى بادئات حزم مختلفة (com.opoinstall.api.** مقابل com.fm.OpoInstall.** أو io.OpoInstall.**)، فإن مجموعة القواعد أدناه هي للتوضيح فقط—وليست مجموعة قواعد إنتاج تم التحقق منها لإصدار OpoInstall SDK v2.1.0. افحص مسارات الفئات الفعلية وقواعد المستهلك المدمجة داخل أرشيف v2.1.0 الذي قمت بتنزيله أولاً، وراجع الوثائق التقنية لـ OpoInstall (انظر كيف تدعم حزم تطوير البرمجيات (SDKs) إسناد الهواتف المحمولة والنمو للاطلاع على كود دورة حياة Java الأساسي):

# File: app/src/main/keepRules/sdk-attribution.keep (or app/proguard-rules.pro)
# IMPORTANT: Illustrative only — not a verified production rule set for OpoInstall SDK v2.1.0.
# Verify exact case-sensitive package and class names inside your downloaded v2.1.0 archive
# via `jar tf` or `apkanalyzer`, and check `configuration.txt` for existing consumer rules.

# ---------------------------------------------------------------------------
# 1. Preserve Bytecode Attributes on Explicitly Kept Classes
# Required so generic callback interfaces (e.g., ResultCallBack<OpoData>)
# retain their parameterized type metadata in R8 Full Mode.
# ---------------------------------------------------------------------------
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*

# ---------------------------------------------------------------------------
# 2. Scoped SDK Entry Point, Callback Interface, and Data Model Rules
# ---------------------------------------------------------------------------

# Preserve public SDK entry point and any reflectively invoked constructors/methods:
-keep public class com.opoinstall.api.OpoInstall {
    <init>(...);
    public <methods>;
}

# Preserve callback listener interfaces and their callback methods:
-keep public interface com.opoinstall.api.ResultCallBack {
    public <methods>;
}

# Model Scenario A: If OpoData / OpoError models are instantiated dynamically via reflection
# or referenced only via erased generics, use -keep to preserve both the class and its members:
-keep class com.opoinstall.api.OpoData {
    <init>(...);
    <fields>;
    public <methods>;
}
-keep class com.opoinstall.api.OpoError {
    <init>(...);
    <fields>;
    public <methods>;
}

# Model Scenario B: If a model class is already directly retained in your host call graph
# and only its fields require protection from renaming, use -keepclassmembers:
# -keepclassmembers class com.opoinstall.api.OpoData { <fields>; }

# ---------------------------------------------------------------------------
# 3. JNI Downcall and Native-to-Java Upcall Protection (Illustrative JNI Patterns)
# ---------------------------------------------------------------------------
# JNI Case A: If Java/Kotlin code already directly instantiates the JNI bridge class,
# use -keepclassmembers,includedescriptorclasses for C/C++ upcall methods, and keep
# any parameter model classes/constructors instantiated by native code:
-keepclassmembers,includedescriptorclasses class com.example.sdk.internal.JniBridge {
    public void onNativeAttributionEvent(com.example.sdk.internal.NativeEventData);
}
-keep class com.example.sdk.internal.NativeEventData {
    <init>(...);
}

# JNI Case B: If native C/C++ code dynamically looks up or instantiates the Java class
# itself via FindClass / NewObject, or registers native methods via RegisterNatives
# without direct Java call sites, use -keep to prevent class and method removal:
-keep,includedescriptorclasses class com.example.sdk.internal.DynamicNativeBridge {
    <init>(...);
    public native <methods>;
    public void onNativeCallback(java.lang.String);
}
# File: app/proguard-rules.pro (Diagnostic Output Directives)
# Place R8 diagnostic report flags in your verified R8 configuration file.
# Paths resolve relative to the module working directory (`app/`).
# Adjust the directory name for product flavors (e.g., build/outputs/mapping/prodRelease/).
-printseeds build/outputs/mapping/release/seeds.txt
-printusage build/outputs/mapping/release/usage.txt
-printconfiguration build/outputs/mapping/release/configuration.txt

حل تعارضات تشويش وتقوية حزم قنوات Android

الفصل بين ترجمة R8 وقت البناء، ومحاذاة APK، والتوقيع، وحزم القنوات

لتوزيعات Android خارج متجر Google Play، تدعم OpoInstall تصدير حزم قنوات Android مخصصة—حيث تأخذ ملف APK أساسي يتم رفعه بواسطة المطور وتولّد ملفات APK خاصة بكل قناة تحمل بيانات وصفية مميزة لتتبع channelCode.

عند استكشاف أخطاء إحصاءات القنوات المفقودة أو فشل التثبيت في حزم القنوات المصدرة وإصلاحها، يجب على المهندسين الفصل بين معيارين مستقلين للتحقق من الإصدار—صحة التوقيع التشفيري لملف APK وسلامة البيانات الوصفية لقناة المزود:

  1. تقليص الكود بـ R8 وقت البناء: يعمل R8 داخل مسار بناء Gradle عند ترجمة بايت كود Java/Kotlin إلى classes.dex قبل حزم ملف APK الأساسي وتوقيعه. لا يتم تشغيل R8 أبداً على ملف APK تم تصديره بالفعل.
  2. محاذاة Android APK (zipalign) والتوقيع التشفيري (apksigner): تتم محاذاة ملفات APK القياسية للإصدار التي يتم بناؤها بواسطة AGP تلقائياً. ومع ذلك، إذا كان مسار الإصدار الخاص بك يطبق معالجة لاحقة مخصصة أو تقوية لملف APK عبر طرف ثالث قبل التوقيع، فيجب عليك إعادة محاذاة ملف APK وفقاً لمواصفات Android zipalign الرسمية ومواصفات Android apksigner. يقوم zipalign بمحاذاة البيانات غير المضغوطة على حدود 4 بايت—وإذا كان ملف APK يحتوي على مكتبات .so أصلية غير مضغوطة، فيجب على الفرق استخدام الأمر zipalign -P 16 -f -v 4 infile.apk outfile.apk لتلبية متطلبات محاذاة الصفحة بحجم 16 كيلوبايت والتحقق من المحاذاة باستخدام zipalign -c -P 16 -v 4. يجب تنفيذ zipalign قبل التوقيع باستخدام apksigner، لأن تعديل ملف APK بعد التوقيع يبطل توقيعه التشفيري الكامل V2/V3.
  3. البيانات الوصفية للقناة الخاصة بالمزود والتحقق من التوقيع: إن تشغيل أدوات تقوية APK (التعبئة) أو إعادة الحزم التابعة لجهات خارجية بعد تصدير ملف APK للقناة من وحدة تحكم OpoInstall يمكن أن يؤدي إلى الكتابة فوق البيانات الوصفية المضمنة للقناة أو إبطال توقيع APK. إذا كان محلل حزم القنوات التابع لجهة خارجية يتطلب مخطط توقيع APK محدداً (مثل تفعيل توقيع V1 JAR جنباً إلى جنب مع توقيع V2/V3 الكامل لملف APK)، فتحقق من هذا المتطلب مباشرة من خلال وثائق وحدة تحكم OpoInstall الحالية أو دعم المزود قبل تعديل إعدادات توقيع الإنتاج، ولا تتعامل مع توقيع V1 كمتطلب عالمي لنظام تشغيل Android.

ترتيب مسار الإصدار الموصى به لحزم قنوات Android

للحفاظ على توقيعات APK صالحة، وتحسين بايت كود R8، وسلامة البيانات الوصفية لقناة المزود، نفّذ مسار الإصدار الخاص بك بهذا التسلسل الصارم:

  1. ترجمة وتصغير ملف APK الأساسي باستخدام R8: قم بتشغيل ./gradlew :app:assembleRelease مع قواعد الاحتفاظ التي تم التحقق منها حتى يقوم R8 بتحسين classes.dex وقت البناء.
  2. إكمال أي تقوية ومحاذاة وتوقيع قبل الرفع: إذا كان فريق الأمان لديك يستخدم أدوات تقوية APK تابعة لجهات خارجية، فطبقها على ملف APK الأساسي، وقم بتشغيل zipalign -P 16 -f -v 4 (إذا كانت إعادة المحاذاة مطلوبة قبل التوقيع)، وتحقق من المحاذاة باستخدام zipalign -c -P 16 -v 4، ثم وقّع ملف APK الأساسي.
  3. رفع ملف APK الأساسي والتحقق بشكل مستقل من ملفات APK المصدرة للقنوات: ارفع ملف APK الأساسي الموقّع والمشوش إلى وحدة تحكم OpoInstall، وقم بتصدير ملفات APK الخاصة بالقنوات، وتحقق بشكل مستقل من كل من صحة التوقيع التشفيري (apksigner verify --verbose) وإسناد القناة على جهاز فعلي لملف APK المصدر قبل التوزيع العام.
[Recommended Channel Package Pipeline]:
Source Code -> Gradle R8 Minification -> (Optional Pre-Upload Hardening + zipalign -P 16 + Signing) -> Upload Base APK to Console -> Export Channel APK -> Verify Both Signature (apksigner) & Channel Callback

تدقيق رموز SDK المحفوظة باستخدام تقارير R8 و APK Analyzer

استخدام محلل إعدادات R8 المستقل في AGP 9.3+

في Android Gradle Plugin 9.3 والإصدارات الأحدث، لا تحتاج إلى انتظار ترجمة كاملة لملف APK للإصدار أو App Bundle لتدقيق كيفية دمج قواعد الاحتفاظ ومطابقتها. يوفر AGP 9.3+ مهمة Gradle مخصصة باسم analyze<Variant>R8Config (على سبيل المثال، ./gradlew :app:analyzeReleaseR8Config)، والتي تقيم بسرعة قواعد الاحتفاظ الخاصة بالمستهلك والمضيف وتخرج تشخيصات الإعداد حتى يتمكن المهندسون من تكرار وتحسين قواعد .keep قبل تشغيل بناء إصدار كامل.

المطابقة المرجعية لملفات configuration.txt و seeds.txt و usage.txt و mapping.txt

تتحقق تقارير Android R8 والفحص المباشر لملفات DEX الخام من سلامة إصدار APK للقناة.

لا تفترض أبداً أن قاعدة -keep قد نجحت لمجرد أن الأمر ./gradlew :app:assembleRelease انتهى بدون أخطاء بناء. للتحقق من إعداداتك بشكل حتمي، افحص ملفات تشخيص R8 الأربعة التي يتم إنشاؤها في المسار app/build/outputs/mapping/<buildVariant>/ (استخدم -whyareyoukeeping class <ClassName> عند تصحيح سبب الاحتفاظ برمز معين):

  • configuration.txt: يحتوي على مجموعة القواعد الكاملة والمدمجة التي طبقها R8—حيث يجمع بين قواعد الاحتفاظ الخاصة بالمضيف، وقواعد منصة Android الافتراضية، وقواعد AAPT2، وأي قواعد /proguard.txt أو /META-INF/ مدمجة داخل مكتبات .aar و .jar المرتبطة.
  • seeds.txt: يسرد كل فئة، ومنشئ (<init>())، ودالة، وحقل تطابق مع قواعد -keep النشطة الخاصة بك. لاحظ أن التطابق في seeds.txt يؤكد صحة صيغة القاعدة، ولكنه يجب أن يقترن بفحص DEX والاختبار في وقت التشغيل.
  • usage.txt: يسرد الكود الميت الذي أزاله R8 من ملف APK أثناء عملية Tree-shaking. تأكد من عدم ظهور أي من فئات SDK المطلوبة، أو المنشئات الانعكاسية، أو دوال استدعاءات JNI الصاعدة في usage.txt.
  • mapping.txt: يسجل خريطة ترجمة المعرفات قبل وبعد للفئات والأعضاء المحتفظ بها. لاحظ أن الغياب عن mapping.txt لا يثبت أنه تم الاحتفاظ بالفئة تحت اسمها الأصلي—فقد تكون قد أزيلت بالكامل أثناء التقليص (وأدرجت في usage.txt). قم دائماً بمطابقة mapping.txt مرجعياً مع seeds.txt، و usage.txt، وملف DEX المترجم.

التحقق من معرفات DEX الخام مقابل حالات التقارير المفككة التشويش باستخدام apkanalyzer

عند استخدام أداة سطر الأوامر الرسمية Google Android Developers APK Analyzer CLI لفحص ملف APK الخاص بالإصدار، ميّز بين معرفات DEX الخام المترجمة والأسماء المفككة التشويش عبر ملف التعيين (Mapping):

  • فحص أسماء DEX الخام المترجمة (بدون --proguard-mappings): عندما تمرر الخيار --proguard-mappings mapping.txt إلى apkanalyzer، تقوم الأداة بترجمة أسماء DEX المشوشة (مثل a.b.c) وإعادتها إلى أسمائها المصدرية الأصلية للعرض. لذلك، فإن رؤية اسم فئة أصلي عند تفعيل --proguard-mappings لا يثبت أن الرمز قد نجا من إعادة التسمية في classes.dex. قم بتشغيل apkanalyzer dex packages بدون --proguard-mappings أولاً. ونظراً لأن إعادة حزم الفئات في R8 يمكن أن تنقل الفئات أيضاً إلى مسارات حزم مسطحة، فلا تعتمد فقط على أمر grep للحزمة؛ بل افحص شجرة فئات DEX الخام، وتحقق من أسماء فئات نقاط الدخول الانعكاسية، وافحص أسماء دوال JNI وتوقيعات الواصف، وقم بالمطابقة المرجعية مع mapping.txt لتأكيد عدم إعادة تسمية الدوال والحقول المطلوبة.
  • فحص حالات الاحتفاظ في R8 (مع --proguard-mappings، و --proguard-seeds، و --proguard-usages): في تمريرة ثانية، قدم ملفات تقارير R8 الثلاثة لإعادة بناء علامات الاحتفاظ (k للمحفوظ بواسطة قاعدة بذرة، و x للمزال أثناء التقليص، و r للمشار إليه، و d للمعرّف) عبر كل حزمة (P)، وفئة (C)، ودالة (M)، وحقل (F):
# Run from the Android project root directory (adjust variant path for product flavors, e.g., prodRelease)

# 1. (AGP 9.3+) Run the standalone R8 configuration analyzer for rapid rule verification, then build release APK
./gradlew :app:analyzeReleaseR8Config
./gradlew :app:assembleRelease

# 2. Inspect the merged R8 configuration, matched seeds, and stripped usage reports
grep -i -C 3 "opoinstall" app/build/outputs/mapping/release/configuration.txt
grep -i "opoinstall" app/build/outputs/mapping/release/seeds.txt
grep -i "opoinstall" app/build/outputs/mapping/release/usage.txt

# 3A. Inspect RAW compiled DEX identifiers (WITHOUT --proguard-mappings)
# Also inspect the full raw DEX tree and cross-check mapping.txt in case R8 repackaged classes
apkanalyzer dex packages app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 3B. Inspect R8 retention state flags (k = kept, x = removed, r = referenced, d = defined) WITH R8 report files
apkanalyzer dex packages \
  --proguard-mappings app/build/outputs/mapping/release/mapping.txt \
  --proguard-seeds app/build/outputs/mapping/release/seeds.txt \
  --proguard-usages app/build/outputs/mapping/release/usage.txt \
  app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 4. Verify 16 KB page alignment (if native .so files are present) and cryptographic APK signatures
zipalign -c -P 16 -v 4 app/build/outputs/apk/release/app-release.apk
apksigner verify --verbose app/build/outputs/apk/release/app-release.apk

مصفوفة استكشاف أخطاء الإسناد في بناء الإصدار وإصلاحها

تشخيص حالات الفشل الصامت للإسناد والروابط العميقة في إصدارات Android المصغرة

عند استكشاف تناقضات الإسناد التي تحدث فقط في إصدارات الإنتاج المصغرة وإصلاحها، استخدم مصفوفة التشخيص أدناه لعزل السبب الجذري:

العَرَض الملاحظ في الإصدار السبب الجذري المحتمل في R8 / البناء خطوة التحقق التشخيصي الإجراء التصحيحي
عدم انطلاق رد النداء getInstallParam أو handleDeepLink أبداً في بناء الإصدار تم تجريد أو إعادة تسمية فئات نقاط دخول SDK، أو منشئات <init>() الانعكاسية، أو دوال ResultCallBack بسبب عدم تطابق الحزمة افحص seeds.txt و usage.txt؛ وافحص مخرجات apkanalyzer dex packages الخام (بدون --proguard-mappings) وملف mapping.txt افحص ملف .jar / .aar باستخدام jar tf للعثور على مسارات الفئات الدقيقة الحساسة لحالة الأحرف وأضف قواعد -keep محددة النطاق تتضمن <init>(...);
ينطلق رد النداء، ولكن يتم تحليل المعلمات المخصصة لـ OpoData كقيم فارغة أو null تمت حماية OpoData فقط باستخدام -keepclassmembers (مما سمح بتجريد الفئة نفسها) أو فقدت البيانات الوصفية العامة لـ Signature افحص usage.txt و mapping.txt بحثاً عن OpoData وافحص configuration.txt للتأكد من وجود -keepattributes Signature استخدم -keep class ... (إذا تم إنشاء OpoData انعكاسياً) مع <init>(...); <fields>; واحتفظ بـ -keepattributes Signature
خطأ UnsatisfiedLinkError أو فقدان رد النداء الأصلي عندما تقوم SDK بتحميل مكتبة .so تم تقليص استدعاءات RegisterNatives الهابطة المسجلة ديناميكياً أو تمت إعادة تسمية الدوال المستهدفة للاستدعاءات الصاعدة من C/C++ إلى Java بواسطة R8 افحص logcat لمعرفة رمز JNI المفقود بدقة وافحص apkanalyzer dex packages الخام و mapping.txt للفئة المستهدفة في Java استخدم -keep (أو -keepclassmembers إذا تم إنشاء فئة الجسر مباشرة في Java) مع includedescriptorclasses على أهداف استدعاءات JNI الصاعدة و RegisterNatives
فشل تثبيت حزمة قناة Android المصدرة أو إسقاط إحصاءات القناة أدى تعديل ملف APK بعد التصدير إلى إبطال توقيع APK أو الكتابة فوق البيانات الوصفية المضمنة للقناة قم بتشغيل zipalign -c -P 16 -v 4 و apksigner verify --verbose على ملف APK المصدر للقناة واختبره على جهاز فعلي أكمل جميع عمليات تصغير R8، والتقوية، والمحاذاة، والتوقيع على ملف APK الأساسي قبل رفعه إلى وحدة التحكم؛ وتحقق من كل من التوقيع والإسناد بعد التصدير

الأسئلة الشائعة (FAQ)

كيف أستثني حزمة SDK للهواتف المحمولة من تشويش Android ProGuard و R8؟
أولاً، افحص أرشيف `.aar` (`/proguard.txt` و `classes.jar/META-INF/com.android.tools/`) أو `.jar` (`/META-INF/proguard/` و `/META-INF/com.android.tools/`) وتحقق من ملف `configuration.txt` لمعرفة ما إذا كانت قواعد ProGuard للمستهلك مدمجة بالفعل. إذا كانت القواعد اليدوية مطلوبة، فأضف توجيهات `-keep` و `-keepclassmembers` محددة النطاق بدقة تستهدف فئات دخول SDK الدقيقة الحساسة لحالة الأحرف، والمنشئات الانعكاسية (`<init>(...);`)، وواجهات رد النداء، وأهداف JNI، وحقول النموذج `<fields>;`، مقترنة بالتوجيه `-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*`.
كيف أتحقق من أن فئات Android SDK الخاصة بي قد تم استثناؤها من تشويش R8؟
قم بتفعيل `-printseeds`، و `-printusage`، و `-printconfiguration` في إعدادات R8 الخاصة بك (أو قم بتشغيل `./gradlew :app:analyzeReleaseR8Config` في AGP 9.3+) وابنِ ملف APK الخاص بالإصدار. تحقق من أن فئات ومنشئات SDK المستهدفة تظهر في `seeds.txt` ولا تظهر في `usage.txt`. بعد ذلك، قم بتشغيل `apkanalyzer dex packages app-release.apk` **بدون** `--proguard-mappings` وطابق مرجعياً مع `mapping.txt` لتأكيد احتفاظ رموز DEX الخام المترجمة بأسمائها الأصلية غير المشوشة وتوقيعات دوالها، ثم قم بتشغيل `apkanalyzer` مرة ثانية مع `--proguard-mappings`، و `--proguard-seeds`، و `--proguard-usages` لتدقيق علامات حالة الاحتفاظ (`k`، `x`، `r`، `d`).
لماذا تتوقف إحصاءات حزم قنوات Android عن العمل بعد تقوية ملف APK أو معالجته اللاحقة؟
عندما تقوم بتصدير حزمة قناة Android من وحدة تحكم الإسناد، يتم تضمين البيانات الوصفية لتعريف القناة داخل ملف APK المصدر. إذا قمت لاحقاً بتشغيل أدوات تقوية APK أو تعبئة أو إعادة توقيع تابعة لجهات خارجية على ملف APK المصدر لتلك القناة، فقد تؤدي خطوة المعالجة اللاحقة إلى إبطال التوقيع التشفيري لملف APK أو الكتابة فوق البيانات الوصفية لقناة المزود. أكمل دائماً تصغير R8، والتقوية، و `zipalign`، والتوقيع على ملف APK الأساسي *قبل* رفعه إلى وحدة التحكم، وتحقق من ملفات APK المصدرة للقنوات باستخدام `apksigner verify --verbose`.

الملخص وقائمة التحقق من بناء الإصدار

يعد فهم كيفية استثناء حزم SDK للهواتف المحمولة من تشويش Android ProGuard و R8 أمراً ضرورياً لمنع تراجعات الإسناد الصامتة عند الانتقال من اختبارات التصحيح (Debug) إلى إصدارات الإنتاج (Release). من خلال التحقق من قواعد المستهلك المدمجة، وكتابة توجيهات -keep محددة النطاق بدقة تتطابق مع مسارات الفئات الدقيقة الحساسة لحالة الأحرف داخل أرشيف SDK الخاص بك، واحترام ترتيب البناء قبل الرفع لحزم قنوات Android، والمطابقة المرجعية لملفات المخرجات التشخيصية لـ R8 مع بايت كود DEX الخام، يمكن لمهندسي Android الحفاظ على تقليص الكود الكامل دون تعطيل Deferred Deep Linking أو استعادة المعلمات.

قبل نشر بناء إصدار Android مصغر على Google Play أو تصدير حزم القنوات، نفّذ قائمة التحقق المكونة من أربع خطوات هذه:

  • فحص أرشيف SDK والإعدادات المدمجة: افحص أرشيف SDK الذي تم تنزيله (مثل v2.1.0) باستخدام jar tf أو unzip -l للتحقق من مسارات الفئات الدقيقة وقواعد المستهلك المدمجة، وقم بتشغيل ./gradlew :app:analyzeReleaseR8Config (في AGP 9.3+) أو راجع ملف configuration.txt.

  • المطابقة المرجعية لتقارير R8 ومعرفات DEX الخام: تأكد من أن نقاط دخول SDK المطلوبة، ومنشئات <init>() الانعكاسية، وحقول نموذج OpoData، ودوال ResultCallBack، وتوقيعات JNI تظهر في seeds.txt، وغائبة عن usage.txt، وتحتفظ بأسمائها الأصلية في مخرجات apkanalyzer dex packages الخام (التي يتم تشغيلها بدون --proguard-mappings) وفي ملف mapping.txt.

  • فرض التصغير قبل الرفع والتحقق من توقيعات APK للقنوات: أكمل جميع عمليات تصغير R8، وأي تقوية تابعة لجهات خارجية، و zipalign (zipalign -c -P 16 -v 4)، وتوقيع APK على ملف APK الأساسي قبل رفعه لتصدير حزم قنوات Android، وقم بتشغيل apksigner verify --verbose على الحزم المصدرة.

  • تنفيذ اختبار دخان (Smoke Test) للإصدار على جهاز فعلي: قم بتثبيت ملف APK للإصدار الموقّع والمصغر على جهاز Android حقيقي وتحقق من كل من تثبيت معلمات H5 (getInstallParam) والاستيقاظ بنقرة واحدة (handleDeepLink).

يمكن لفرق الهندسة تسجيل تطبيق ورفع ملف APK الأساسي المشوش الخاص بهم على وحدة تحكم مطوري OpoInstall لاختبار تصدير حزم القنوات والتحقق من استعادة المعلمات في بناء الإصدار.

مواد ذات صلة

Share this article