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 โปรเจ็กต์ของคุณต้องมีปลั๊กอิน 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 ลงในแต่ละโมดูลที่ใช้การสะท้อนหรือ 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()ประกาศปลั๊กอินข้าง AGP โดยไม่ใช้ปลั๊กอินในไฟล์
build.gradle.ktsรูท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 }ใช้ปลั๊กอินและเพิ่มทรัพยากร Dependency ของไลบรารีในไฟล์
build.gradle.ktsของแต่ละโมดูลที่ใช้การสะท้อนหรือ JNIplugins { 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แต่ไม่ได้ ใช้ปลั๊กอิน Gradleandroidx.annotation.keepหากไม่มีปลั๊กอิน บิลด์จะไม่สร้างกฎการเก็บรักษาจากคำอธิบายประกอบ
หากต้องการใช้ Lint เพื่อใส่คำอธิบายประกอบโมดูล Android ให้ทำตามขั้นตอนต่อไปนี้
เรียกใช้เครื่องมือวิเคราะห์ซอร์สโค้ดในโมดูลเพื่อค้นหาการเรียกแบบสะท้อนที่ต้องมีคำอธิบายประกอบ
./gradlew :app:lintใช้การแก้ไขที่ Lint ทำได้โดยอัตโนมัติ
./gradlew :app:lintFixLint สามารถเพิ่มคำอธิบายประกอบโดยอัตโนมัติเมื่อกำหนดคลาสและสมาชิกที่โค้ดสะท้อนได้
ตรวจสอบคำอธิบายประกอบแต่ละรายการที่เพิ่มเข้ามาเพื่อให้แน่ใจว่าตรงกับสิ่งที่โค้ดทำ เช่น ตรวจสอบว่าประเภทพารามิเตอร์ตรงกับตัวสร้างหรือเมธอดที่โค้ดเรียก
แก้ไขผลการค้นหาที่เหลือใน 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.** { *; }ให้ตรวจสอบว่าคำอธิบายประกอบหรือกฎอื่นๆ ครอบคลุมการสะท้อนทั้งหมดในคลาสของแพ็กเกจ - สำหรับกฎที่เก็บคลาสหรือสมาชิกที่เฉพาะเจาะจง ให้ตรวจสอบว่ากฎนั้นตรงกับกฎที่สร้างขึ้นกฎใดกฎหนึ่ง หรือไม่มีการสะท้อนอื่นๆ ที่ขึ้นอยู่กับกฎนั้น
- สำหรับกฎระดับแพ็กเกจ เช่น
ทดสอบบิลด์ที่เผยแพร่
ใส่คำอธิบายประกอบโค้ด
เพิ่มคำอธิบายประกอบ Jetpack Keep ลงในโค้ดที่ทำการสะท้อน ได้แก่ ฟังก์ชัน ตัวสร้าง ฟิลด์ หรือคลาสที่มีการเรียกแบบสะท้อน คำอธิบายประกอบจะอธิบายเป้าหมายของการสะท้อน และกฎที่สร้างขึ้นจะเก็บเป้าหมายนั้นไว้ก็ต่อเมื่อ R8 เก็บโค้ดที่มีคำอธิบายประกอบ
เลือกคำอธิบายประกอบตามสิ่งที่โค้ดทำ
| คำอธิบายประกอบ | ใช้เมื่อโค้ด |
|---|---|
@UsesReflectionToConstruct |
เรียกตัวสร้างผ่านการสะท้อน เช่น ด้วย
getDeclaredConstructor().newInstance() |
@UsesReflectionToAccessMethod |
เรียกเมธอดผ่านการสะท้อน เช่น ด้วย
getMethod หรือ getDeclaredMethod |
@UsesReflectionToAccessField |
อ่านหรือเขียนฟิลด์ผ่านการสะท้อน เช่น ด้วย
getField หรือ getDeclaredField |
@UnconditionallyKeep |
ถูกเรียกจากภายนอกโค้ด Kotlin หรือ Java ของแอป เช่น
จากโค้ดแบบเนทีฟผ่าน JNI ใช้คำอธิบายประกอบนี้เฉพาะในกรณีที่คุณใช้
คำอธิบายประกอบ @UsesReflection* ไม่ได้ ซึ่งจะสร้าง
กฎแบบมีเงื่อนไข |
คำอธิบายประกอบ @UsesReflection* ทั้งหมดเป็นไปตามข้อกำหนดต่อไปนี้
- ระบุคลาสเป้าหมายด้วย
classConstantหรือclassNameหากคลาสพร้อมใช้งานในเวลาคอมไพล์ ให้ใช้classConstantคอมไพเลอร์จะตรวจสอบการอ้างอิง และการปรับโครงสร้าง IDE เช่น การเปลี่ยนชื่อจะอัปเดตการอ้างอิง ใช้classNameเฉพาะในกรณีที่คลาสไม่พร้อมใช้งานในเวลาคอมไพล์ เช่น คลาสในการอ้างอิงที่ไม่บังคับ พารามิเตอร์ทั้ง 2 รายการนี้ใช้ร่วมกันไม่ได้ - กำหนดเป้าหมายคลาสที่เฉพาะเจาะจงที่สุด กฎที่สร้างขึ้นจะเก็บสมาชิกที่ตรงกันไว้ในคลาสที่คุณระบุและในคลาสย่อยหรือการติดตั้งอินเทอร์เฟซ คลาสทั่วไปมากขึ้นเท่าใด R8 ก็จะเก็บโค้ดมากขึ้นเท่านั้น
- ระบุประเภทพารามิเตอร์เพื่อเก็บการโอเวอร์โหลดรายการเดียว หากคุณไม่ได้ตั้งค่า
parameterTypesหรือparameterTypeNamesกฎจะเก็บตัวสร้างทั้งหมดหรือการโอเวอร์โหลดทั้งหมดของเมธอด หากต้องการเก็บเฉพาะตัวสร้างหรือเมธอดที่ไม่มีอาร์กิวเมนต์ ให้ส่งอาร์เรย์ว่าง - ใส่คำอธิบายประกอบซ้ำสำหรับเป้าหมายหลายรายการ หากฟังก์ชันหนึ่งสะท้อนคลาสหรือสมาชิกมากกว่า 1 รายการ ให้เพิ่มคำอธิบายประกอบ 1 รายการสำหรับแต่ละเป้าหมาย
สร้างออบเจ็กต์ด้วยการสะท้อน
ใช้ @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
ตัวอย่าง: โหลดไลบรารีส่วนเสริมที่ไม่บังคับ
ไลบรารีมักใช้การสะท้อนเพื่อโหลดส่วนเสริมที่ไม่บังคับ ตัวอย่างเช่น Analytics SDK อาจจัดส่งไลบรารีหลักและส่วนเสริมวิดีโอ ส่วนเสริมขึ้นอยู่กับไลบรารีหลัก ดังนั้นไลบรารีหลักจึงอ้างอิงคลาสของส่วนเสริมโดยตรงไม่ได้ แต่ไลบรารีหลักจะค้นหาคลาสของส่วนเสริมตามชื่อในระหว่างรันไทม์และเปิดการวิเคราะห์วิดีโอเฉพาะในกรณีที่พบคลาส
เนื่องจากไลบรารีหลักใช้คลาสของส่วนเสริมผ่านการสะท้อนเท่านั้น R8 จึงอาจนำคลาสหรือตัวสร้างของคลาสออกจากบิลด์ที่เผยแพร่ของแอป ซึ่งทำให้การค้นหาล้มเหลว ความล้มเหลวจะเกิดขึ้นเฉพาะในบิลด์ที่เผยแพร่ของแอปที่มีส่วนเสริม ดังนั้นจึงตรวจพบได้ยากในการทดสอบ
หากไม่มี Jetpack Keep ส่วนเสริมจะจัดส่งกฎการเก็บรักษาของผู้ใช้สำหรับคลาสของส่วนเสริม ดังที่แสดงใน หัวข้อการสะท้อนเพื่อรองรับการอ้างอิงที่ไม่บังคับ
# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
กฎนี้อยู่ในไลบรารีอื่นจากการค้นหาที่ต้องใช้กฎ ดังนั้นกฎทั้ง 2 จึงอาจไม่ซิงค์กัน 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ไม่ได้ - เก็บเฉพาะตัวสร้างที่โค้ดเรียก การค้นหาจะเรียกตัวสร้างที่ไม่มีอาร์กิวเมนต์ ดังนั้น
parameterTypesจึงเป็นอาร์เรย์ว่าง
เมื่อคุณสร้าง AAR ของไลบรารีหลัก ปลั๊กอินจะเพิ่มกฎที่เก็บ VideoEventTracker และตัวสร้างที่ไม่มีอาร์กิวเมนต์ของคลาสนี้ไว้ทุกครั้งที่ R8 เก็บ initialize แอปที่ใช้ SDK จะใช้กฎนี้โดยอัตโนมัติ แม้ว่าจะไม่ได้ใช้ Jetpack Keep ก็ตาม ดังนั้นส่วนเสริมจึงไม่จำเป็นต้องมีกฎของตัวเอง
เมื่อเทียบกับกฎของส่วนเสริม แนวทางนี้มีข้อดีดังต่อไปนี้
- กฎจะอยู่ข้างๆ การค้นหา ไลบรารีหลักเป็นเจ้าของการค้นหาและกฎ ดังนั้นคุณจึงอัปเดตทั้ง 2 รายการพร้อมกันแทนที่จะประสานงานการเปลี่ยนแปลงในไลบรารี 2 รายการ
- R8 ยังคงนำโค้ดที่ไม่ได้ใช้ออกได้ หากแอปมีส่วนเสริมแต่ไม่เคยเรียก
initializeกฎที่สร้างขึ้นจะไม่มีผล และ R8 สามารถนำVideoEventTrackerออกได้ กฎของส่วนเสริมจะเก็บส่วนเสริมไว้ในทุกแอปที่มีส่วนเสริม - Lint จะตรวจพบการค้นหาที่ไม่มีคำอธิบายประกอบ หากไลบรารีหลักเพิ่มการค้นหาแบบสะท้อนอีกรายการในภายหลังโดยไม่มีคำอธิบายประกอบ Lint จะแจ้งการค้นหาดังกล่าว เพื่อให้คุณแก้ไขได้ก่อนที่จะเผยแพร่ไลบรารี
หากต้องการยืนยันผลลัพธ์ ให้ตรวจสอบว่ากฎที่สร้างขึ้นปรากฏในไฟล์ AAR's
proguard.txt ตามที่อธิบายไว้ใน ไลบรารี Android จากนั้นทดสอบบิลด์ที่เผยแพร่ของแอปที่มีส่วนเสริม เนื่องจากกฎที่ขาดหายไปจะทำให้การวิเคราะห์วิดีโอหยุดทำงาน
แหล่งข้อมูลเพิ่มเติม
- ตัวอย่าง Jetpack Keep: โปรเจ็กต์ Android Studio ที่ ใช้ Jetpack Keep ในแอป ไลบรารี Android ไลบรารี Kotlin Multiplatform และไลบรารี JVM
- Use Case และตัวอย่างกฎการเก็บรักษา: กฎการเก็บรักษาสำหรับสถานการณ์การสะท้อนและ JNI ที่พบบ่อย
- ทำตามแนวทางปฏิบัติแนะนำ: คำแนะนำในการเขียนกฎการเก็บรักษาที่มีขอบเขตแคบ