تتيح لك مكتبة Jetpack Keep إمكانية الإشارة إلى استخدام الانعكاس وJava Native Interface (JNI) من خلال التعليقات التوضيحية مباشرةً في رمز المصدر بلغتَي Kotlin أو Java، بدلاً من استخدام ملفات منفصلة لقواعد الاحتفاظ بالبيانات. يقرأ مكوّن Jetpack Keep الإضافي في Gradle هذه التعليقات التوضيحية في وقت الإنشاء ويُنشئ قواعد الاحتفاظ بالبيانات التي يحتاجها R8.
تساعد مكتبة Jetpack Keep في الحفاظ على فعالية تحسين R8 مع تغيُّر قاعدة الرموز البرمجية:
تبقى قواعد الاحتفاظ بالبيانات بجانب الرمز البرمجي الذي يحتاجها. عند إعادة تسمية الرمز البرمجي الذي يستخدم الانعكاس أو حذفه، يمكنك تعديل التعليق التوضيحي في المكان نفسه. وليس هناك ملف قواعد منفصل يجب الحفاظ على مزامنته.
القواعد التي يتم إنشاؤها مشروطة. يحتفظ R8 بهدف طلب انعكاسي فقط إذا كان يحتفظ أيضًا بالرمز البرمجي الذي يُجري الطلب. إذا لم يتم استخدام الرمز البرمجي الذي يُجري الطلب وأزاله R8 (وهو ما يحدث غالبًا في ميزات المكتبة الاختيارية)، يمكن أن يزيل R8 أيضًا الهدف، لذا لا تضيف القواعد من الميزات غير المستخدَمة حجمًا كبيرًا.
تعثر أداة Lint على الانعكاس نيابةً عنك. تتضمّن المكتبة عمليات فحص باستخدام أداة Lint تشير إلى الطلبات الانعكاسية التي لم تتم إضافة تعليقات توضيحية إليها، ويمكنها إضافة العديد من التعليقات التوضيحية تلقائيًا.
آلية عمل مكتبة Jetpack Keep
لنفترض أنّ هناك دالة تُنشئ فئة حسب الاسم. إذا لم يتمكّن R8 من تحديد أنّ الرمز البرمجي يستخدم الفئة، فسيُزيل الدالة الإنشائية للفئة، ويتوقف التطبيق عن العمل في وقت التشغيل. لمنع حدوث هذا التوقف، يمكنك إضافة قاعدة مثل ما يلي إلى ملف قواعد الاحتفاظ بالبيانات:
# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
<init>();
}
باستخدام مكتبة Jetpack Keep، يمكنك إضافة تعليق توضيحي إلى الدالة التي تُجري الانعكاس بدلاً من ذلك:
// ResourceLocatorImpl isn't on this module's compile classpath, so both the
// annotation and the code refer to it by name.
@UsesReflectionToConstruct(
className = "com.example.ResourceLocatorImpl",
// Omitting parameterTypes keeps every constructor. An empty array keeps
// only the no-argument constructor, which the code calls.
parameterTypes = [],
)
fun loadResourceLocator(): ResourceLocator {
val implClass = Class.forName("com.example.ResourceLocatorImpl")
return implClass.getDeclaredConstructor().newInstance() as ResourceLocator
}
في وقت الإنشاء، يُنشئ مكوّن Gradle الإضافي قاعدة احتفاظ بالبيانات للدالة الإنشائية.
على عكس القاعدة المكتوبة يدويًا سابقًا، تكون القاعدة التي يتم إنشاؤها مشروطة: يحتفظ R8 بالدالة الإنشائية فقط إذا بقيت loadResourceLocator في التطبيق المحسَّن.
المتطلبات
لاستخدام مكتبة Jetpack Keep، يجب أن يكون مشروعك متوافقًا مع الإصدار 8.7 أو إصدار أحدث من Android Gradle Plugin (AGP).
يتوافق مكوّن Gradle الإضافي مع أنواع الوحدات التالية:
| نوع الوحدة | المكوّن الإضافي الذي تُطبّقه الوحدة | الإعداد الإضافي |
|---|---|---|
| تطبيق Android | com.android.application |
بدون |
| مكتبة Android | com.android.library |
بدون |
| مكتبة Kotlin Multiplatform مع هدف Android | com.android.kotlin.multiplatform.library |
بدون |
| مكتبة JVM (Java أو Kotlin) | java-library أو org.jetbrains.kotlin.jvm |
تسجيل عملية تحويل JAR |
إعداد مكتبة Jetpack Keep
لاستخدام مكتبة Jetpack Keep، أضِف كلاً من المكتبة ومكوّن Gradle الإضافي إلى كل وحدة تستخدم الانعكاس أو JNI. توفّر المكتبة التعليقات التوضيحية وعمليات الفحص باستخدام أداة Lint، ويحوّل المكوّن الإضافي التعليقات التوضيحية إلى قواعد الاحتفاظ بالبيانات.
في ملف
libs.versions.toml، أضِف المكتبة والمكوّن الإضافي. تتشارك المكتبة والمكوّن الإضافي الإصدار نفسه:[versions] # The library and the plugin ship together. Use one version for both so # that they stay in sync when you upgrade. annotationKeep = "1.0.0-alpha01" [libraries] androidx-annotation-keep = { group = "androidx.annotation.keep", name = "keep", version.ref = "annotationKeep" } [plugins] androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }ينزِّل Gradle المكوّن الإضافي من مستودع Maven من Google، لذا تأكّد من أنّ مستودعات
pluginManagementفي ملفsettings.gradle.ktsتتضمّنgoogle().في ملف
build.gradle.ktsالجذر، أعلِن عن المكوّن الإضافي بجانب AGP بدون تطبيقه:plugins { alias(libs.plugins.android.application) apply false // Other plugins. // The plugin relies on AGP classes that it doesn't bundle, so declare // it next to AGP. Declaring it here also gives every module the same // plugin version. alias(libs.plugins.androidx.annotation.keep) apply false }في ملف
build.gradle.ktsلكل وحدة تستخدم الانعكاس أو JNI، طبِّق المكوّن الإضافي وأضِف تبعية المكتبة:plugins { alias(libs.plugins.android.application) // The plugin reads annotations only from this module's own code, so // apply it in every module that uses Jetpack Keep. alias(libs.plugins.androidx.annotation.keep) } dependencies { implementation(libs.androidx.annotation.keep) }زامِن مشروعك مع ملفات Gradle.
بالنسبة إلى كل تنويعة تصميم لوحدة تطبيق Android، يُسجِّل المكوّن الإضافي مهمة
<variant>ExtractKeepRules، مثل releaseExtractKeepRules، ويُمرِّر
القواعد التي تم إنشاؤها إلى R8 لتنويعة التصميم هذه. بالنسبة إلى وحدات المكتبة، يُغلِّف المكوّن الإضافي القواعد التي تم إنشاؤها كقواعد احتفاظ بالبيانات للمستهلك في ناتج المكتبة.
تحتاج وحدات مكتبة JVM إلى إعداد إضافي.
العثور على الانعكاس باستخدام أداة Lint
تتضمّن مكتبة Jetpack Keep عمليات الفحص التالية باستخدام أداة Lint:
ReflectionWithoutKeepAnnotations- تشير إلى الطلبات الانعكاسية، مثل
Class.forNameوgetDeclaredConstructorوgetDeclaredMethodوgetDeclaredField، التي ليس لها تعليق توضيحي مطابق للاحتفاظ بالبيانات. MissingKeepAnnotationPlugin- تشير إلى الوحدات التي تعتمد على
androidx.annotation.keep:keepولكن لا تُطبِّق مكوّن Gradle الإضافيandroidx.annotation.keep. بدون المكوّن الإضافي، لا يُنشئ إصدارك قواعد الاحتفاظ بالبيانات من التعليقات التوضيحية.
لاستخدام أداة Lint لإضافة تعليقات توضيحية إلى وحدة Android، اتّبِع الخطوات التالية:
شغِّل أداة Lint على الوحدة للعثور على الطلبات الانعكاسية التي تحتاج إلى تعليقات توضيحية:
./gradlew :app:lintطبِّق الإصلاحات التي يمكن أن تُجريها أداة Lint تلقائيًا:
./gradlew :app:lintFixيمكن أن تضيف أداة Lint تعليقًا توضيحيًا تلقائيًا عندما تتمكّن من تحديد الفئة والأعضاء الذين يعكسهم الرمز البرمجي.
راجِع كل تعليق توضيحي تمت إضافته للتأكّد من أنّه يطابق ما يفعله الرمز البرمجي. على سبيل المثال، تأكّد من أنّ أنواع المَعلمات تطابق الدالة الإنشائية أو الطريقة التي يستدعيها الرمز البرمجي.
يمكنك حلّ النتائج المتبقية في "استوديو Android". عندما لا تتمكّن أداة Lint من تحديد الفئة المستهدَفة، يُدرِج الإصلاح السريع التعليق التوضيحي مع عنصر نائب
TODOلتتمكن من إكماله. يمكنك أيضًا إضافة التعليق التوضيحي يدويًا.على سبيل المثال، تتلقّى الدالة التالية اسم الفئة كمعلَمة، لذا لا يمكن لأداة Lint تحديد الفئة التي تُنشئها. يضيف الإصلاح السريع تعليقًا توضيحيًا مع عنصر نائب:
@UsesReflectionToConstruct( className = TODO(), parameterTypes = [] ) fun createInstance(className: String): Any = Class.forName(className).getDeclaredConstructor().newInstance()استبدِل العنصر النائب بالفئة التي يُنشئها الرمز البرمجي. إذا كانت الفئة متاحة في وقت التجميع، استخدِم
classConstantبدلاً منclassName. في Kotlin، لا يتم تجميع الرمز البرمجي إلى أن تستبدِلTODO().تحقَّق من قواعد الاحتفاظ بالبيانات التي يُنشئها المكوّن الإضافي من التعليقات التوضيحية قبل إزالة أي قواعد حالية. بالنسبة إلى وحدة التطبيق، شغِّل مهمة الاستخراج لصيغة الإنتاج:
./gradlew :app:releaseExtractKeepRulesيكتب المكوّن الإضافي القواعد في
app/build/generated/release/androidx.annotation.keep.rules.pro. بالنسبة إلى وحدات المكتبة، اطّلِع على مقالة التحقّق من القواعد التي تم إنشاؤها.أزِل قواعد الاحتفاظ بالبيانات التي تحل محلها التعليقات التوضيحية الجديدة من ملفات قواعد الاحتفاظ بالبيانات. قد تغطي القاعدة أيضًا الانعكاس الذي لم تشِر إليه أداة Lint، مثل الانعكاس في وحدة أخرى أو في مكتبة، لذا تحقَّق من كل قاعدة قبل إزالتها:
- بالنسبة إلى قاعدة على مستوى الحزمة، مثل
-keep class com.example.** { *; }، تأكَّد من أنّ التعليقات التوضيحية أو القواعد الأخرى تغطي جميع الانعكاسات على فئات الحزمة. - بالنسبة إلى قاعدة تحتفظ بفئات أو أعضاء معيّنين، تأكَّد من أنّها تطابق إحدى القواعد التي تم إنشاؤها بشكلٍ وثيق، أو أنّه لا يعتمد عليها أي انعكاس آخر.
- بالنسبة إلى قاعدة على مستوى الحزمة، مثل
اختبِر بنية الإصدار.
إضافة تعليقات توضيحية إلى الرمز البرمجي
أضِف تعليقًا توضيحيًا من Jetpack Keep إلى الرمز البرمجي الذي يُجري الانعكاس: الدالة أو الدالة الإنشائية أو الحقل أو الفئة التي تحتوي على الطلب الانعكاسي. يصف التعليق التوضيحي هدف الانعكاس، وتحتفظ القاعدة التي تم إنشاؤها بهذا الهدف فقط إذا كان R8 يحتفظ بالرمز البرمجي الذي تمت إضافة التعليق التوضيحي إليه.
اختَر تعليقًا توضيحيًا استنادًا إلى ما يفعله الرمز البرمجي:
| التعليق التوضيحي | استخدِم هذا التعليق عندما |
|---|---|
@UsesReflectionToConstruct |
يستدعي الرمز البرمجي دالة إنشائية من خلال الانعكاس، مثلاً باستخدام
getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
يستدعي الرمز البرمجي طريقة من خلال الانعكاس، مثلاً باستخدام
getMethod أو getDeclaredMethod. |
@UsesReflectionToAccessField |
يقرأ الرمز البرمجي حقلاً أو يكتب فيه من خلال الانعكاس، مثلاً باستخدام
getField أو getDeclaredField. |
@UnconditionallyKeep |
يتم استدعاء الرمز البرمجي من خارج رمز Kotlin أو Java في تطبيقك، مثلاً
من رموز برمجية أصلية من خلال JNI. لا تستخدِم هذا التعليق التوضيحي إلا عندما لا يمكنك
استخدام التعليقات التوضيحية @UsesReflection* التي تُنشئ
قواعد مشروطة. |
تتّبع جميع التعليقات التوضيحية @UsesReflection* الاصطلاحات التالية:
- حدِّد الفئة المستهدَفة باستخدام
classConstantأوclassName. إذا كانت الفئة متاحة في وقت التجميع، استخدِمclassConstant. يتحقّق المُجمِّع من المرجع، وتعدِّله عمليات إعادة البناء في بيئة التطوير المتكاملة، مثل إعادة التسمية. لا تستخدِمclassNameإلا عندما لا تكون الفئة متاحة في وقت التجميع، مثل فئة في تبعية اختيارية. هاتان المَعلمتان لا يمكن استخدامهما معًا. - استهدِف الفئة الأكثر تحديدًا. تحتفظ القواعد التي تم إنشاؤها بالأعضاء المطابقين في الفئة التي تحدِّدها وفي فئاتها الفرعية أو عمليات تنفيذ الواجهة. كلما كانت الفئة أكثر عمومية، احتفظ R8 بعدد أكبر من الرموز البرمجية.
- حدِّد أنواع المَعلمات للاحتفاظ بدالة واحدة ذات حمولة زائدة. إذا لم تضبط
parameterTypesأوparameterTypeNames، تحتفظ القواعد بجميع الدوال الإنشائية أو جميع الدوال ذات الحمولة الزائدة للطريقة. للاحتفاظ بالدالة الإنشائية أو الطريقة التي لا تتضمّن وسيطة فقط، مرِّر صفيفًا فارغًا. - كرِّر التعليق التوضيحي لأهداف متعددة. إذا كان هناك دالة واحدة تعكس أكثر من فئة أو عضو، أضِف تعليقًا توضيحيًا واحدًا لكل هدف.
إنشاء الكائنات باستخدام الانعكاس
استخدِم @UsesReflectionToConstruct عندما يُنشئ الرمز البرمجي فئة من خلال الانعكاس. في المثال التالي، يُنشئ مشغِّل المهام عمليات تنفيذ لواجهة StartupTask حسب الاسم. بما أنّ التعليق التوضيحي يحدِّد الواجهة، تحتفظ القواعد التي تم إنشاؤها بالدالة الإنشائية التي لا تتضمّن وسيطة لكل عملية تنفيذ StartupTask، طالما أنّ R8 يحتفظ بـ execute:
interface StartupTask {
fun run()
}
object TaskRunner {
// The task class is known only at runtime, so target the interface that
// every task implements.
@UsesReflectionToConstruct(classConstant = StartupTask::class, parameterTypes = [])
fun execute(className: String) {
val task = Class.forName(className)
.getDeclaredConstructor()
.newInstance() as StartupTask
task.run()
}
}
استدعاء الطرق باستخدام الانعكاس
استخدِم @UsesReflectionToAccessMethod عندما يستدعي الرمز البرمجي طريقة من خلال الانعكاس. اضبط methodName على اسم الطريقة. في المثال التالي، يستدعي الرمز البرمجي طريقة ثابتة في تبعية اختيارية. لا تتوفّر الفئة في وقت التجميع، لذا يستخدم التعليق التوضيحي className بدلاً من classConstant:
object CrashReporting {
@UsesReflectionToAccessMethod(
className = "com.example.crashlib.CrashReporter",
methodName = "log",
// Omitting parameterTypes keeps every log overload. Keep only the one
// that the code calls.
parameterTypes = [String::class],
)
fun log(message: String) {
val reporterClass = try {
Class.forName("com.example.crashlib.CrashReporter")
} catch (e: ClassNotFoundException) {
// Crash reporting is optional, so skip it instead of failing.
return
}
reporterClass.getMethod("log", String::class.java).invoke(null, message)
}
}
الوصول إلى الحقول باستخدام الانعكاس
استخدِم @UsesReflectionToAccessField عندما يقرأ الرمز البرمجي حقلاً أو يكتب فيه من خلال الانعكاس. اضبط fieldName على اسم الحقل:
class UserProfileFetcher {
// User doesn't expose internalId, so reading it requires reflection.
@UsesReflectionToAccessField(classConstant = User::class, fieldName = "internalId")
fun readInternalId(user: User): Any? {
val field = User::class.java.getDeclaredField("internalId")
field.isAccessible = true
return field.get(user)
}
}
الاحتفاظ بالرمز البرمجي الذي يتم استدعاؤه من خارج تطبيقك
استخدِم @UnconditionallyKeep للرمز البرمجي الذي يتم استدعاؤه من خارج رمز Kotlin أو Java في تطبيقك، مثل طريقة يستدعيها الرمز الأصلي من خلال JNI. أضِف تعليقًا توضيحيًا إلى العنصر الذي يتم استدعاؤه، وليس إلى الرمز البرمجي الذي يستدعيه:
class NativeBridge {
// Only native code calls this method, through a JNI lookup by name that
// R8 can't see. Keep the method and its name unconditionally.
@UnconditionallyKeep
fun onNativeEvent(message: String) {
Log.d("NativeBridge", "Received from native code: $message")
}
// The default proguard-android-optimize.txt file already keeps native
// method names, so this method doesn't need an annotation.
external fun startNativeProcess()
}
تحتفظ القاعدة التي تم إنشاؤها بالطريقة التي تمت إضافة التعليق التوضيحي إليها والفئة التي تحتوي عليها، مع أسمائهما الأصلية. ولا تحتفظ بالفئات الأخرى التي تشير إليها توقيع الطريقة. إذا كانت الرموز البرمجية الأصلية تعتمد على هذه الفئات، أضِف تعليقات توضيحية إليها أيضًا، أو أضِف قواعد الاحتفاظ بالبيانات لها. لمزيد من المعلومات عن JNI وR8، اطّلِع على مقالة التفاعل مع الرموز البرمجية الأصلية من خلال JNI.
يحافظ @UnconditionallyKeep تلقائيًا على اسم العنصر. إذا لم يكن المتصل الخارجي يعتمد على الاسم، اضبط shouldPreserveName = false ليتمكّن R8 من إعادة تسمية العنصر.
التحقّق من القواعد التي تم إنشاؤها
يوضّح هذا القسم كيفية التحقّق من القواعد التي تم إنشاؤها لكل نوع من أنواع الوحدات. بالنسبة إلى وحدة التطبيق، يُمرِّر المكوّن الإضافي القواعد مباشرةً إلى R8. بالنسبة إلى وحدة المكتبة، يُغلِّف المكوّن الإضافي القواعد كقواعد احتفاظ بالبيانات للمستهلك في ناتج المكتبة. تُطبِّق التطبيقات التي تعتمد على مكتبتك هذه القواعد تلقائيًا عند تشغيل R8، حتى إذا لم تكن التطبيقات تستخدم مكتبة Jetpack Keep. لمزيد من المعلومات عن قواعد الاحتفاظ بالبيانات للمستهلك، اطّلِع على مقالة التحسين لمؤلّفي المكتبات.
تطبيقات Android
لمعرفة قواعد الاحتفاظ بالبيانات التي يُنشئها المكوّن الإضافي لوحدة التطبيق، شغِّل مهمة الاستخراج لتنويعة التصميم:
./gradlew :app:releaseExtractKeepRules
يكتب المكوّن الإضافي القواعد لكل صيغة في
app/build/generated/<variant>/androidx.annotation.keep.rules.pro. يحدِّد التعليق
# context: قبل كل قاعدة الرمز البرمجي الذي تمت إضافة التعليق التوضيحي إليه والذي أدّى إلى إنشائه.
لمعرفة كيفية تأثير القواعد التي تم إنشاؤها في التحسين مع قواعد الاحتفاظ بالبيانات الأخرى ، استخدِم أداة تحليل إعدادات R8.
مكتبات Android
لا تحتاج وحدات مكتبة Android إلى أي إعداد إضافي. عند إنشاء ملف AAR، يُضيف المكوّن الإضافي القواعد التي تم إنشاؤها إلى ملف proguard.txt في ملف AAR، بعد أي قواعد احتفاظ بالبيانات للمستهلك يحتوي عليها ملف AAR حاليًا. يُسجِّل المكوّن الإضافي مهمة
<variant>KeepRulesTransformAar لكل تنويعة تصميم لإجراء ذلك.
للتحقّق من القواعد، أنشئ ملف AAR، مثلاً باستخدام ./gradlew
:mylibrary:assembleRelease، وافتح proguard.txt في ملف AAR الذي تم إنشاؤه. تظهر القواعد التي تم إنشاؤها في قسم يتضمّن العنوان التالي:
#################################################################
# Rules generated from androidx.annotation.keep annotations #
#################################################################
على نظامَي التشغيل macOS وLinux، يمكنك أيضًا طباعة القواعد من سطر الأوامر:
unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt
مكتبات Kotlin Multiplatform
لا تحتاج وحدات مكتبة Kotlin Multiplatform التي تستهدف Android إلى أي إعداد إضافي أيضًا. عند إنشاء ملف AAR لهدف Android، يُضيف المكوّن الإضافي القواعد التي تم إنشاؤها إلى ملف proguard.txt في ملف AAR بالطريقة نفسها كما هو الحال في مكتبات Android. يُسجِّل المكوّن الإضافي مهمة androidMainKeepRulesTransformAar لإجراء ذلك.
للتحقّق من القواعد، شغِّل هذه المهمة وافتح proguard.txt في ملف AAR الذي تُنشئه. على نظامَي التشغيل macOS وLinux، يمكنك طباعة القواعد من سطر الأوامر:
./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt
مكتبات JVM
قبل أن تتمكّن من التحقّق من قواعد وحدة مكتبة JVM، اضبط
الوحدة بحيث يُضيف المكوّن الإضافي القواعد التي تم إنشاؤها إلى نسخة
من ملف JAR. بعد ذلك، شغِّل مهمة التحويل التي سجّلتها واطّلِع على ملفات القواعد في ملف JAR الذي تُنشئه. على سبيل المثال، إذا سمّيت المهمة keepRulesJar، يمكنك طباعة القواعد على نظامَي التشغيل macOS وLinux باستخدام الأوامر التالية:
./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
"META-INF/com.android.tools/*" "META-INF/proguard/*"
ضبط مكتبات JVM
بالنسبة إلى وحدات مكتبة JVM التي تُنشئ ملف JAR، لا يُعدِّل المكوّن الإضافي ملف JAR تلقائيًا. بدلاً من ذلك، استخدِم الدالة annotationKeep.registerJavaArchiveTransform لتسجيل مهمة تُنشئ نسخة من ملف JAR مع القواعد التي تم إنشاؤها، ثم انشر ملف JAR هذا. أضِف ما يلي إلى ملف build.gradle.kts الخاص بوحدة المكتبة:
plugins {
id("java-library")
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.androidx.annotation.keep)
}
val jarTask = tasks.named<Jar>("jar")
// Apps run R8 on this library's code, so the generated rules must be inside
// the JAR. The plugin handles this automatically only for Android modules.
val keepRulesJarTask = annotationKeep.registerJavaArchiveTransform(
taskName = "keepRulesJar",
input = jarTask.flatMap { it.archiveFile },
fileName = jarTask.flatMap { it.archiveFileName }.map { it.replace(".jar", "-keep-rules.jar") },
)
// Consumers of this library resolve apiElements and runtimeElements, so add
// the JAR that contains the generated rules to them.
configurations.configureEach {
if (name == "apiElements" || name == "runtimeElements") {
outgoing.artifact(keepRulesJarTask.flatMap { it.outputJar })
}
}
dependencies {
implementation(libs.androidx.annotation.keep)
}
تُضيف عملية التحويل القواعد التي تم إنشاؤها إلى موقع قواعد R8 المستهدَف في ملف JAR، وهو META-INF/com.android.tools/r8/proguard.txt. إذا كان ملف JAR يحتوي على قواعد في META-INF/com.android.tools/ أو META-INF/proguard/، تُلحِق عملية التحويل القواعد التي تم إنشاؤها بملف القواعد الحالي بدلاً من ذلك. لمزيد من المعلومات
عن هذه المواقع، اطّلِع على مقالة دعم إصدارات R8 المختلفة.
بعد تسجيل عملية التحويل، تحقَّق من القواعد في ملف JAR.
مثال: تحميل مكتبة إضافية اختيارية
تستخدم المكتبات غالبًا الانعكاس لتحميل الإضافات الاختيارية. على سبيل المثال، قد تُرسِل حزمة تطوير برامج (SDK) للإحصاءات مكتبة أساسية وإضافة فيديو. تعتمد الإضافة على المكتبة الأساسية، لذا لا يمكن أن تشير المكتبة الأساسية إلى فئات الإضافة مباشرةً. بدلاً من ذلك، تبحث المكتبة الأساسية عن فئة الإضافة حسب الاسم في وقت التشغيل وتُفعِّل إحصاءات الفيديو فقط إذا عثرت على الفئة.
بما أنّ المكتبة الأساسية تستخدم فئة الإضافة من خلال الانعكاس فقط، قد يزيل R8 الفئة أو الدالة الإنشائية الخاصة بها من بنية إصدار التطبيق، ما يؤدي إلى فشل عملية البحث. لا يحدث هذا الفشل إلا في بُنى الإصدار للتطبيقات التي تتضمّن الإضافة، لذا يصعب رصده أثناء الاختبار.
بدون مكتبة Jetpack Keep، تُرسِل الإضافة قاعدة احتفاظ بالبيانات للمستهلك لفئتها، كما هو موضّح في مقالة الانعكاس لدعم التبعيات الاختيارية:
# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
توجد هذه القاعدة في مكتبة مختلفة عن عملية البحث التي تحتاج إليها، لذا يمكن أن يصبح الاثنان غير متزامنين. باستخدام مكتبة Jetpack Keep، يمكنك إضافة تعليق توضيحي إلى عملية البحث في المكتبة الأساسية بدلاً من ذلك:
// In the core library.
interface AnalyticsPlugin {
fun start()
}
object AnalyticsManager {
// The annotation and the lookup share this constant so that they can't
// refer to different classes.
private const val VIDEO_TRACKER_CLASS =
"com.example.analytics.video.VideoEventTracker"
@UsesReflectionToConstruct(
className = VIDEO_TRACKER_CLASS,
parameterTypes = [],
)
fun initialize() {
val tracker = try {
Class.forName(VIDEO_TRACKER_CLASS)
.getDeclaredConstructor()
.newInstance() as AnalyticsPlugin
} catch (e: ClassNotFoundException) {
// Apps that don't play video leave out the add-on, so a missing
// class isn't an error.
return
}
// This is an ordinary interface call, not a reflective one, so R8
// keeps start() without an annotation.
tracker.start()
}
}
يتّبع التعليق التوضيحي الإرشادات التالية:
- أضِف تعليقًا توضيحيًا إلى الرمز البرمجي الذي يُجري الانعكاس. التعليق التوضيحي موجود في
initialize، التي تستدعيClass.forName، وليس في فئة الإضافة. - اختَر التعليق التوضيحي استنادًا إلى ما يفعله الرمز البرمجي. تُنشئ عملية البحث مثيلاً للفئة، لذا يستخدم الرمز البرمجي
@UsesReflectionToConstruct. - استخدِم
classNameلفئة لا يمكنك الإشارة إليها. لا تتوفّر فئة الإضافة في مسار التجميع للمكتبة الأساسية، لذا لا يمكن أن يستخدم التعليق التوضيحيclassConstant. - احتفظ فقط بالدالة الإنشائية التي يستدعيها الرمز البرمجي. تستدعي عملية البحث الدالة الإنشائية التي لا تتضمّن وسيطة، لذا فإنّ
parameterTypesهو صفيف فارغ.
عند إنشاء ملف AAR للمكتبة الأساسية، يُضيف المكوّن الإضافي قاعدة تحتفظ بـ VideoEventTracker والدالة الإنشائية التي لا تتضمّن وسيطة كلما كان R8 يحتفظ بـ initialize. تُطبِّق التطبيقات التي تستخدم حزمة SDK هذه القاعدة تلقائيًا، حتى إذا لم تكن تستخدم مكتبة Jetpack Keep، لذا لا تحتاج الإضافة إلى قاعدة خاصة بها.
مقارنةً بقاعدة الإضافة، يتسم هذا النهج بالمزايا التالية:
- تبقى القاعدة بجانب عملية البحث. تمتلك المكتبة الأساسية كلاً من عملية البحث والقاعدة، لذا يمكنك تعديلهما معًا بدلاً من تنسيق تغيير على مستوى مكتبتَين.
- لا يزال بإمكان R8 إزالة الرمز البرمجي غير المستخدَم. إذا كان أحد التطبيقات يتضمّن الإضافة ولكنه لا يستدعي
initializeمطلقًا، لا تُطبَّق القاعدة التي تم إنشاؤها، ويمكن أن يزيل R8VideoEventTracker. تحتفظ قاعدة الإضافة بها في كل تطبيق يتضمّن الإضافة. - ترصد أداة Lint عمليات البحث التي لم تتم إضافة تعليقات توضيحية إليها. إذا أضافت المكتبة الأساسية لاحقًا عملية بحث انعكاسية أخرى بدون تعليق توضيحي، تشير أداة Lint إلى ذلك، لذا يمكنك إصلاحها قبل إصدار المكتبة.
للتأكّد من النتيجة، تحقَّق من ظهور القاعدة التي تم إنشاؤها في ملف AAR's
proguard.txt، كما هو موضّح في مقالة مكتبات Android. بعد ذلك، اختبِر بنية إصدار لتطبيق يتضمّن الإضافة، لأنّ هذا هو المكان الذي ستؤدي فيه القاعدة غير المتوفّرة إلى إيقاف إحصاءات الفيديو.
مراجع إضافية
- نموذج Jetpack Keep: مشروع "استوديو Android" الذي يستخدم مكتبة Jetpack Keep في تطبيق ومكتبة Android ومكتبة Kotlin Multiplatform ومكتبة JVM.
- حالات استخدام قواعد الاحتفاظ بالبيانات وأمثلة عليها: قواعد الاحتفاظ بالبيانات لسيناريوهات الانعكاس و JNI الشائعة.
- اتّباع أفضل الممارسات: إرشادات حول كتابة قواعد الاحتفاظ بالبيانات ذات النطاق المحدود.