Jetpack Keep מאפשר להצהיר על שימוש ב-Reflection וב-Java Native Interface (JNI) באמצעות הערות ישירות בקוד המקור של Kotlin או Java, במקום בקובצי כללי שמירה נפרדים. הפלאגין Jetpack Keep Gradle קורא את ההערות האלה בזמן ה-build ויוצר את כללי השמירה שנדרשים ל-R8.
הספרייה Jetpack Keep עוזרת לאופטימיזציה של R8 להישאר יעילה ככל שמשתנה בסיס הקוד:
כללי השמירה נשארים לצד הקוד שצריך אותם. כשמשנים את השם של קוד שמשתמש ברפלקציה או מוחקים אותו, מעדכנים את ההערה באותו מקום. אין קובץ כללים נפרד שצריך לסנכרן.
הכללים שנוצרים הם מותנים. R8 שומר את היעד של קריאה רפלקטיבית רק אם הוא שומר גם את הקוד שמבצע את הקריאה. אם הקוד שקורא לא משמש והוא מוסר על ידי R8 (בדרך כלל בתכונות אופציונליות של ספרייה), R8 יכול גם להסיר את היעד, כך שכללים מתכונות שלא משמשות לא מוסיפים נפח מיותר.
Lint finds reflection for you. הספרייה כוללת בדיקות lint שמסמנות קריאות רפלקטיביות שלא נוספו להן הערות, ויכולה להוסיף הרבה מההערות באופן אוטומטי.
איך Jetpack Keep פועל
נניח שיש פונקציה שיוצרת מופע של מחלקה לפי שם. אם R8 לא יכול לזהות שהקוד משתמש במחלקה, הוא מסיר את ה-constructor של המחלקה והאפליקציה קורסת בזמן הריצה. כדי למנוע את הקריסה, אפשר להוסיף כלל כמו הכלל הבא לקובץ כללי השמירה:
# 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
}
בזמן ה-build, הפלאגין של Gradle יוצר כלל שמירה ל-constructor.
בניגוד לכלל שנכתב ידנית שמופיע למעלה, הכלל שנוצר הוא מותנה:
R8 שומר את הבונה רק אם loadResourceLocator נשאר באפליקציה שעברה אופטימיזציה.
דרישות
כדי להשתמש ב-Jetpack Keep, הפרויקט צריך את פלאגין של Android Gradle (AGP) בגרסה 8.7 ואילך.
הפלאגין של 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 לכל מודול שמשתמש ב-reflection או ב-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ברמה הבסיסית (root), מצהירים על הפלאגין לצד 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.
לכל וריאנט build של מודול אפליקציה ל-Android, הפלאגין רושם משימה של <variant>ExtractKeepRules, כמו releaseExtractKeepRules, ומעביר את הכללים שנוצרו ל-R8 בשביל הווריאנט הזה. במודולים של ספריות, הפלאגין אורז את הכללים שנוצרו ככללי שמירה לצרכנים בפלט של הספרייה.
מודולים של ספריות JVM דורשים הגדרה נוספת.
חיפוש רפלקציה באמצעות lint
Jetpack Keep כולל את בדיקות ה-lint הבאות:
ReflectionWithoutKeepAnnotations- מסמן שיחות רפלקטיביות, כמו
Class.forName,getDeclaredConstructor,getDeclaredMethodו-getDeclaredField, שאין להן הערה תואמת של keep. MissingKeepAnnotationPlugin- מודולים של דגלים שתלויים ב-
androidx.annotation.keep:keepאבל לא מפעילים את התוסףandroidx.annotation.keepGradle. בלי הפלאגין, תהליך הבנייה לא יוצר כללי שמירה מהאנוטציות.
כדי להשתמש ב-lint להוספת הערות למודול של Android:
מריצים את הכלי lint על המודול כדי למצוא קריאות רפלקטיביות שצריך להוסיף להן הערות:
./gradlew :app:lintמחילים את התיקונים ש-lint יכול לבצע באופן אוטומטי:
./gradlew :app:lintFixכלי ה-Lint יכול להוסיף הערה באופן אוטומטי כשהוא יכול לקבוע לאיזו מחלקה ולאילו איברים הקוד מתייחס.
בודקים כל הערה שנוספה כדי לוודא שהיא תואמת לפעולת הקוד. לדוגמה, צריך לוודא שסוגי הפרמטרים תואמים ל-constructor או ל-method שהקוד קורא להם.
פותרים את שאר הממצאים ב-Android Studio. אם כלי ה-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.** { *; }, חשוב לוודא שההערות או כללים אחרים מכסים את כל ה-reflection במחלקות של החבילה. - בכלל ששומר מחלקות או חברים ספציפיים, ודאו שהוא תואם באופן הדוק לאחד מהכללים שנוצרו, או שאין השתקפות אחרת שתלויה בו.
- בכלל שחל על כל החבילה, כמו
בודקים את גרסת ה-build להפצה.
הוספת הערות לקוד
מוסיפים הערת Jetpack Keep לקוד שמבצע את הרפלקציה: הפונקציה, ה-constructor, השדה או המחלקה שמכילים את הקריאה הרפלקטיבית. ההערה מתארת את היעד של הרפלקציה, והכלל שנוצר שומר את היעד הזה רק אם R8 שומר את הקוד עם ההערה.
בוחרים הערה בהתאם לפעולה שהקוד מבצע:
| הערה | מתי להשתמש בקוד |
|---|---|
@UsesReflectionToConstruct |
קורא ל-constructor באמצעות השתקפות, לדוגמה עם
getDeclaredConstructor().newInstance(). |
@UsesReflectionToAccessMethod |
מפעיל שיטה באמצעות השתקפות, למשל באמצעות getMethod או getDeclaredMethod. |
@UsesReflectionToAccessField |
קורא או כותב שדה באמצעות השתקפות, לדוגמה באמצעות getField או getDeclaredField. |
@UnconditionallyKeep |
נקראת מחוץ לקוד Kotlin או Java של האפליקציה, לדוגמה
מקוד Native דרך JNI. השתמשו בהערה הזו רק כשאי אפשר להשתמש בהערות @UsesReflection*, שיוצרות כללים מותנים. |
כל ההערות של @UsesReflection* פועלות לפי המוסכמות הבאות:
- מזהים את הכיתה הרצויה באמצעות
classConstantאוclassName. אם המחלקה זמינה בזמן ההידור, משתמשים ב-classConstant. הקומפיילר בודק את ההפניה, ופעולות רפקטורינג ב-IDE, כמו שינוי שם, מעדכנות אותה. משתמשים ב-classNameרק כשהמחלקות לא זמינות בזמן ההידור, כמו מחלקה בתלות אופציונלית. שני הפרמטרים האלה לא יכולים להתקיים בו-זמנית. - מטרגטים את המחלקה הכי ספציפית. הכללים שנוצרו ימשיכו להתאים לחברים בכיתה שציינתם ולתת-המחלקות שלה או ליישומי הממשק שלה. ככל שהמחלקה כללית יותר, כך R8 שומר יותר קוד.
- מציינים את סוגי הפרמטרים כדי לשמור על עומס יתר יחיד. אם לא מגדירים את
parameterTypesאו אתparameterTypeNames, הכללים שומרים את כל ה-constructors או את כל ה-overloads של השיטה. כדי להשאיר רק את ה-constructor או את השיטה ללא ארגומנטים, מעבירים מערך ריק. - חוזרים על ההערה עבור כמה יעדים. אם פונקציה אחת משקפת יותר ממחלקה אחת או מחבר אחד, צריך להוסיף הערה אחת לכל יעד.
בניית אובייקטים באמצעות רפלקציה
משתמשים ב-@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 של האפליקציה, כמו שיטה שקוד Native קורא לה דרך 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()
}
הכלל שנוצר שומר על המתודה עם ההערה ועל המחלקה שמכילה אותה, עם השמות המקוריים שלהן. היא לא שומרת מחלקות אחרות שההפניה אליהן מופיעה בחתימה של ה-method. אם קוד Native תלוי במחלקות האלה, צריך להוסיף הערות גם להן, או להוסיף להן כללי שמירה. מידע נוסף על JNI ו-R8 זמין במאמר אינטראקציה עם קוד Native באמצעות JNI.
כברירת מחדל, @UnconditionallyKeep שומר על שם הרכיב. אם המתקשר החיצוני לא תלוי בשם, מגדירים את shouldPreserveName = false כך ש-R8 יוכל לשנות את שם הרכיב.
אימות הכללים שנוצרו
בקטע הזה מוסבר איך לבדוק את הכללים שנוצרו לכל סוג של מודול. במודול של אפליקציה, הפלאגין מעביר את הכללים ישירות ל-R8. במודול של ספרייה, הפלאגין אורז את הכללים ככללי שמירה לצרכנים בפלט של הספרייה. אפליקציות שתלויות בספרייה שלכם יחילו את הכללים האלה באופן אוטומטי כשהן יפעילו את R8, גם אם הן לא משתמשות ב-Jetpack Keep. מידע נוסף על כללי השמירה של הצרכן זמין במאמר אופטימיזציה למפתחי ספריות.
אפליקציות ל-Android
כדי לבדוק אילו כללי שמירה הפלאגין יוצר למודול של אפליקציה, מריצים את משימת החילוץ של וריאנט build:
./gradlew :app:releaseExtractKeepRules
הפלאגין כותב את הכללים לכל וריאנט בקובץ app/build/generated/<variant>/androidx.annotation.keep.rules.pro. הערה # context: לפני כל כלל מזהה את הקוד עם ההערות שיצר אותו.
כדי לבדוק איך הכללים שנוצרו משפיעים על האופטימיזציה יחד עם שאר הכללים לשימור, אפשר להשתמש בכלי לניתוח ההגדרות של R8.
ספריות ל-Android
לא צריך להגדיר שום דבר נוסף במודולים של ספריית Android. כשיוצרים את ה-AAR, הפלאגין מוסיף את הכללים שנוצרו לקובץ proguard.txt של ה-AAR, אחרי כל כללי השמירה על נתונים של צרכנים שכבר קיימים ב-AAR. כדי לעשות את זה, הפלאגין רושם משימת <variant>KeepRulesTransformAar לכל וריאנט build.
כדי לאמת את הכללים, בונים את ה-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.
דוגמה: טעינה של ספריית תוספים אופציונלית
ספריות משתמשות לעיתים קרובות ברפלקציה כדי לטעון תוספים אופציונליים. לדוגמה, Analytics SDK עשוי לכלול ספריית ליבה ותוסף וידאו. התוסף תלוי בספריית הליבה, ולכן ספריית הליבה לא יכולה להפנות ישירות למחלקות של התוסף. במקום זאת, ספריית הליבה מחפשת את המחלקה של התוסף לפי השם בזמן הריצה, ומפעילה את ניתוח הנתונים של הסרטונים רק אם היא מוצאת את המחלקה.
מכיוון שספריית הליבה משתמשת במחלקה של התוסף רק באמצעות השתקפות, R8 עשוי להסיר את המחלקה או את ה-constructor שלה מגרסת build להפצה של האפליקציה, מה שגורם לחיפוש להיכשל. הבעיה מתרחשת רק בגרסאות build להפצה של אפליקציות שכוללות את התוסף, ולכן קשה לזהות אותה בבדיקות.
בלי 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לכיתה שאי אפשר להפנות אליה. הסיווג של התוסף לא נמצא ב-classpath של ההידור של ספריית הליבה, ולכן אי אפשר להשתמש בהערהclassConstant. - משאירים רק את ה-constructor שהקוד קורא לו. החיפוש מפעיל את ה-constructor ללא ארגומנטים, כך ש-
parameterTypesהוא מערך ריק.
כשיוצרים את ה-AAR של ספריית הליבה, הפלאגין מוסיף כלל ששומר על VideoEventTracker ועל בנאי ללא ארגומנטים בכל פעם ש-R8 שומר על initialize. אפליקציות שמשתמשות ב-SDK מחילות את הכלל הזה באופן אוטומטי, גם אם הן לא משתמשות ב-Jetpack Keep, כך שהתוסף לא צריך כלל משלו.
בהשוואה לכלל של התוסף, לגישה הזו יש את היתרונות הבאים:
- הכלל נשאר ליד החיפוש. ספריית הליבה מכילה גם את החיפוש וגם את הכלל, כך שאתם מעדכנים אותם ביחד במקום לתאם שינוי בשתי ספריות.
- R8 עדיין יכול להסיר קוד שלא בשימוש. אם אפליקציה כוללת את התוסף אבל אף פעם לא קוראת ל-
initialize, הכלל שנוצר לא חל, ו-R8 יכול להסיר אתVideoEventTracker. הכלל של התוסף שומר אותו בכל אפליקציה שכוללת את התוסף. - Lint מזהה חיפושים ללא הערות. אם בהמשך נוסף לספריית הליבה עוד חיפוש רפלקטיבי ללא הערה, הכלי lint מסמן אותו כדי שתוכלו לתקן אותו לפני שאתם מפרסמים את הספרייה.
כדי לאשר את התוצאה, מוודאים שהכלל שנוצר מופיע בקובץ proguard.txt של ה-AAR, כמו שמתואר במאמר ספריות Android. אחר כך בודקים גרסת build להפצה של אפליקציה שכוללת את התוסף, כי שם כלל חסר יגרום לשיבוש בניתוח הנתונים של הסרטונים.
מקורות מידע נוספים
- Jetpack Keep sample: פרויקט ב-Android Studio שמשתמש ב-Jetpack Keep באפליקציה, בספריית Android, בספריית Kotlin Multiplatform ובספריית JVM.
- תרחישים לדוגמה ודוגמאות לשימוש בכללים: כללים ששומרים על תרחישים נפוצים של השתקפות ו-JNI.
- יישום השיטות המומלצות: הנחיות לכתיבת כללי שמירה עם היקף מצומצם.