เพิ่มกฎการเก็บรักษา

ในระดับสูง กฎการเก็บรักษาจะระบุคลาส (หรือคลาสย่อยหรือการติดตั้งใช้งาน) แล้วระบุสมาชิก ซึ่งได้แก่ เมธอด, ตัวสร้าง หรือฟิลด์ ภายในคลาสนั้นที่จะเก็บรักษาไว้

ไวยากรณ์ทั่วไปสำหรับกฎการเก็บรักษาเป็นดังนี้


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

ต่อไปนี้เป็นตัวอย่างของกฎการเก็บรักษาที่ใช้ keepclassmembers เป็นตัวเลือกการเก็บรักษา ใช้ allowoptimization เป็นตัวแก้ไข และเก็บ someSpecificMethod() จาก com.example.MyClass:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

ตัวเลือกการเก็บรักษา

ตัวเลือกการเก็บคือส่วนแรกของกฎการเก็บ ซึ่งจะระบุแง่มุมของคลาสที่ต้องการเก็บรักษาไว้ ตัวเลือกการเก็บรักษาข้อมูลมี 6 ตัวเลือก ได้แก่ keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames และ keepclasseswithmembernames

ตารางต่อไปนี้จะอธิบายตัวเลือกการเก็บรักษาเหล่านี้

ตัวเลือกการเก็บรักษา คำอธิบาย
keepclassmembers เก็บสมาชิกที่ระบุไว้เฉพาะในกรณีที่ R8 ไม่ได้นำคลาสที่มีสมาชิกดังกล่าวออก
keep เก็บรักษาคลาสที่ระบุและสมาชิกที่ระบุ (ฟิลด์และเมธอด) เพื่อป้องกันไม่ให้มีการเพิ่มประสิทธิภาพ

หมายเหตุ: โดยทั่วไปแล้ว keep ควรใช้กับตัวแก้ไขตัวเลือก keep เท่านั้น เนื่องจาก keep เพียงอย่างเดียวจะป้องกันไม่ให้มีการเพิ่มประสิทธิภาพใดๆ ในคลาสที่ตรงกัน
keepclasseswithmembers เก็บคลาสและสมาชิกที่ระบุไว้เฉพาะในกรณีที่คลาสมีสมาชิกทั้งหมดจากข้อกำหนดของคลาส
keepclassmembernames ป้องกันการเปลี่ยนชื่อสมาชิกคลาสที่ระบุ แต่ไม่ป้องกันการนำคลาสหรือสมาชิกออก

หมายเหตุ: ผู้ใช้มักเข้าใจความหมายของตัวเลือกนี้ผิด ให้ลองใช้ -keepclassmembers,allowshrinking ที่เทียบเท่าแทน
keepnames ป้องกันการเปลี่ยนชื่อคลาสและสมาชิก แต่ไม่ได้ป้องกันการนำคลาสและสมาชิกออกทั้งหมดหากถือว่าไม่ได้ใช้

หมายเหตุ: ความหมายของตัวเลือกนี้มักถูกเข้าใจผิด ให้ลองใช้ -keep,allowshrinking ที่เทียบเท่าแทน
keepclasseswithmembernames ป้องกันการเปลี่ยนชื่อคลาสและสมาชิกที่ระบุ แต่เฉพาะในกรณีที่สมาชิกมีอยู่ในโค้ดสุดท้ายเท่านั้น แต่ไม่ได้ป้องกันการนำโค้ดออก

หมายเหตุ: ความหมายของตัวเลือกนี้มักถูกเข้าใจผิด ให้ลองใช้ -keepclasseswithmembers,allowshrinking ที่เทียบเท่าแทน

เลือกตัวเลือกการเก็บที่เหมาะสม

การเลือกตัวเลือก Keep ที่เหมาะสมเป็นสิ่งสำคัญในการกำหนดการเพิ่มประสิทธิภาพที่เหมาะสมสำหรับแอปของคุณ ตัวเลือก Keep บางตัวเลือกจะลดขนาดโค้ด ซึ่งเป็นกระบวนการที่โค้ดที่ไม่มีการอ้างอิงจะถูกนำออก ในขณะที่ตัวเลือกอื่นๆ จะสร้างความสับสนหรือเปลี่ยนชื่อโค้ด ตารางต่อไปนี้แสดงการดำเนินการของตัวเลือกการเก็บรักษาต่างๆ

ตัวเลือกการเก็บรักษา ลดขนาดคลาส สร้างความสับสนให้กับคลาส ลดจำนวนสมาชิก ซ่อนสมาชิก
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

ตัวแก้ไขตัวเลือก "เก็บ"

ตัวแก้ไขตัวเลือกการเก็บรักษาใช้เพื่อควบคุมขอบเขตและลักษณะการทำงานของกฎการเก็บรักษา คุณสามารถเพิ่มตัวแก้ไขตัวเลือกการเก็บรักษาอย่างน้อย 0 รายการลงในกฎการเก็บรักษาได้

ค่าที่เป็นไปได้สำหรับตัวแก้ไขตัวเลือกการเก็บรักษาจะอธิบายไว้ในตารางต่อไปนี้

ค่า คำอธิบาย
allowoptimization อนุญาตให้เพิ่มประสิทธิภาพองค์ประกอบที่ระบุ อย่างไรก็ตาม ระบบจะไม่เปลี่ยนชื่อหรือนำองค์ประกอบที่ระบุออก
allowobfuscation อนุญาตให้เปลี่ยนชื่อองค์ประกอบที่ระบุ อย่างไรก็ตาม ระบบจะไม่นำองค์ประกอบออกหรือเพิ่มประสิทธิภาพองค์ประกอบ
allowshrinking อนุญาตให้นำองค์ประกอบที่ระบุออกได้หาก R8 ไม่พบการอ้างอิงถึงองค์ประกอบเหล่านั้น อย่างไรก็ตาม ระบบจะไม่เปลี่ยนชื่อหรือเพิ่มประสิทธิภาพองค์ประกอบ
includedescriptorclasses สั่งให้ R8 เก็บรักษาคลาสทั้งหมดที่ปรากฏในตัวอธิบายของเมธอด (ประเภทพารามิเตอร์และประเภทการแสดงผล) และฟิลด์ (ประเภทฟิลด์) ที่จะเก็บไว้
allowaccessmodification อนุญาตให้ R8 เปลี่ยน (โดยปกติคือขยาย) ตัวแก้ไขการเข้าถึง (public, private, protected) ของคลาส เมธอด และฟิลด์ในระหว่างกระบวนการเพิ่มประสิทธิภาพ
allowrepackage อนุญาตให้ R8 ย้ายคลาสไปยังแพ็กเกจต่างๆ รวมถึงแพ็กเกจเริ่มต้น (รูท)

ข้อกำหนดของชั้นเรียน

คุณต้องระบุคลาส (รวมถึงอินเทอร์เฟซ, enum และคลาสคำอธิบายประกอบ) ในกฎ Keep ทุกข้อ คุณสามารถเลือกจำกัดกฎตามคำอธิบายประกอบได้โดยระบุคลาสใหญ่สุดหรืออินเทอร์เฟซที่ใช้ หรือโดยระบุตัวแก้ไขการเข้าถึงสำหรับคลาส ต้องระบุคลาสทั้งหมด รวมถึงคลาสจากเนมสเปซ java.lang เช่น java.lang.String โดยใช้ชื่อ Java ที่สมบูรณ์ หากต้องการทำความเข้าใจชื่อที่ควรใช้ ให้ตรวจสอบไบต์โค้ดโดยใช้เครื่องมือที่อธิบายไว้ในตรวจสอบชื่อ Java ที่สร้างขึ้น

ตัวอย่างต่อไปนี้แสดงวิธีระบุคลาส MaterialButton

  • ถูกต้อง: com.google.android.material.button.MaterialButton
  • ไม่ถูกต้อง: MaterialButton

นอกจากนี้ ข้อกำหนดของคลาสยังระบุสมาชิกภายในคลาสที่ควรเก็บไว้ด้วย ตัวอย่างเช่น กฎต่อไปนี้จะเก็บคลาส MyClass และเมธอด someSpecificMethod() ไว้

-keep class com.example.MyClass {
  void someSpecificMethod();
}

ระบุคลาสตามคำอธิบายประกอบ

หากต้องการระบุคลาสตามคำอธิบายประกอบ ให้เติมคำนำหน้าชื่อ Java ที่มีคุณสมบัติครบถ้วนของคำอธิบายประกอบด้วยสัญลักษณ์ @ เช่น

-keep class @com.example.MyAnnotation com.example.MyClass

หากกฎ Keep มีคำอธิบายประกอบมากกว่า 1 รายการ ระบบจะเก็บคลาสที่มีคำอธิบายประกอบทั้งหมดที่ระบุไว้ คุณสามารถระบุคำอธิบายประกอบได้หลายรายการ แต่กฎจะมีผลก็ต่อเมื่อคลาสมีคำอธิบายประกอบที่ระบุไว้ทั้งหมดเท่านั้น ตัวอย่างเช่น กฎต่อไปนี้จะเก็บคลาสทั้งหมดที่มีคำอธิบายประกอบทั้ง Annotation1 และ Annotation2

-keep class @com.example.Annotation1 @com.example.Annotation2 *

ระบุคลาสย่อยและการใช้งาน

หากต้องการกำหนดเป้าหมายคลาสย่อยหรือคลาสที่ใช้อินเทอร์เฟซ ให้ใช้ extend และ implements ตามลำดับ

ตัวอย่างเช่น หากคุณมีคลาส Bar ที่มีคลาสย่อย Foo ดังนี้

class Foo : Bar()

กฎการเก็บรักษาต่อไปนี้จะเก็บรักษาสับคลาสทั้งหมดของ Bar โปรดทราบว่ากฎการเก็บรักษาไม่รวมคลาสใหญ่สุด Bar เอง

-keep class * extends Bar

หากคุณมีคลาส Foo ที่ใช้การติดตั้งใช้งานอินเทอร์เฟซ Bar ให้ทำดังนี้

class Foo : Bar

กฎ Keep ต่อไปนี้จะเก็บรักษาคลาสทั้งหมดที่ใช้ Bar โปรดทราบว่า กฎการเก็บรักษาไม่ได้รวมถึงอินเทอร์เฟซ Bar เอง

-keep class * implements Bar

ระบุคลาสตามตัวแก้ไขการเข้าถึง

คุณระบุตัวแก้ไขการเข้าถึง เช่น public, private, static และ final เพื่อให้กฎการเก็บรักษาแม่นยำยิ่งขึ้นได้

ตัวอย่างเช่น กฎต่อไปนี้จะเก็บคลาส public ทั้งหมดภายในแพ็กเกจ api และแพ็กเกจย่อย รวมถึงสมาชิกแบบสาธารณะและแบบมีการป้องกันทั้งหมดในคลาสเหล่านี้

-keep public class com.example.api.** { public protected *; }

นอกจากนี้ คุณยังใช้ตัวแก้ไขสำหรับสมาชิกในชั้นเรียนได้ด้วย ตัวอย่างเช่น กฎต่อไปนี้จะเก็บเฉพาะpublic staticเมธอดของคลาส Utils

-keep class com.example.Utils {
  public static void *(...);
}

ตัวแก้ไขเฉพาะ Kotlin

R8 ไม่รองรับตัวแก้ไขเฉพาะของ Kotlin เช่น internal และ suspend โปรดใช้หลักเกณฑ์ต่อไปนี้เพื่อเก็บช่องดังกล่าวไว้

  • หากต้องการเก็บคลาส เมธอด หรือฟิลด์ internal ไว้ ให้ถือว่าเป็นแบบสาธารณะ ตัวอย่างเช่น ลองพิจารณาแหล่งที่มาของ Kotlin ต่อไปนี้

    package com.example
    internal class ImportantInternalClass {
      internal val f: Int
      internal fun m() {}
    }
    

    คลาส เมธอด และฟิลด์ internal จะเป็น public ในไฟล์ .class ที่คอมไพเลอร์ Kotlin สร้างขึ้น ดังนั้นคุณจึงต้องใช้คีย์เวิร์ด public ดังที่แสดงในตัวอย่างต่อไปนี้

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • เมื่อคอมไพล์สมาชิก suspend ให้ตรงกับลายเซ็นไบต์โค้ดที่คอมไพล์แล้ว ในกฎการเก็บ

    ตัวอย่างเช่น ลองพิจารณาคลาสที่เก็บข้อมูลในชีวิตจริงที่เป็นรูปธรรมซึ่งกำหนดไว้ใน Kotlin ดังนี้

    package com.example.repository
    
    import com.example.model.User
    
    class UserRepository {
        suspend fun fetchUser(id: String): User {
            // Implementation details...
        }
    }
    

    เมื่อคอมไพเลอร์ Kotlin คอมไพล์คลาสนี้เป็นไบต์โค้ด suspend ฟังก์ชันจะผ่านการแปลงรูปแบบการส่งต่อความต่อเนื่อง (CPS) ระบบจะนำตัวแก้ไข suspend ออก เปลี่ยนประเภทการคืนค่าเป็น java.lang.Object และต่อท้ายพารามิเตอร์ kotlin.coroutines.Continuation ในลายเซ็นของเมธอดเพื่อจัดการเครื่องสถานะแบบไม่พร้อมกัน

    ซิกเนเจอร์ของเมธอดที่คอมไพล์ในไบต์โค้ดจะปรากฏดังนี้

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    หากต้องการเขียนกฎการเก็บรักษาสำหรับฟังก์ชันนี้ คุณจะใช้ไวยากรณ์ suspend ของ Kotlin ไม่ได้ แต่ต้องจับคู่ลายเซ็นของไบต์โค้ดที่คอมไพล์แล้ว (อ้างอิงถึง kotlin.coroutines.Continuation โดยเฉพาะ) หรือใช้ ... แทน

    ตัวอย่างที่ตรงกับซิกเนเจอร์ไบต์โค้ดที่คอมไพล์อย่างแม่นยำมีดังนี้

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation);
    }
    

    ตัวอย่างการใช้ ... มีดังนี้

    -keepclassmembers class com.example.repository.UserRepository {
      public java.lang.Object fetchUser(...);
    }
    

ข้อมูลจำเพาะของสมาชิก

การระบุคลาสอาจรวมถึงสมาชิกของคลาสที่จะเก็บไว้ด้วยก็ได้ หากคุณระบุสมาชิกอย่างน้อย 1 คนสำหรับชั้นเรียน กฎนี้จะไม่มีผลกับสมาชิกคนอื่นๆ

ระบุสมาชิกตามคำอธิบายประกอบ

คุณระบุสมาชิกตามคำอธิบายประกอบได้ เช่นเดียวกับคลาส คุณ จะเติมคำนำหน้าชื่อ Java ที่สมบูรณ์ของคำอธิบายประกอบด้วย @ ซึ่งจะช่วยให้คุณ เก็บเฉพาะสมาชิกในชั้นเรียนที่มีคำอธิบายประกอบ เฉพาะไว้ได้ เช่น หากต้องการเก็บวิธีการและฟิลด์ที่ใส่คำอธิบายประกอบด้วย @com.example.MyAnnotation

-keep class com.example.MyClass {
  @com.example.MyAnnotation <methods>;
  @com.example.MyAnnotation <fields>;
}

คุณสามารถใช้ร่วมกับการจับคู่คำอธิบายประกอบระดับชั้นเรียนเพื่อสร้างกฎที่มีประสิทธิภาพและกำหนดเป้าหมายได้ ดังนี้

-keep class @com.example.ClassAnnotation * {
  @com.example.MethodAnnotation <methods>;
  @com.example.FieldAnnotation <fields>;
}

ซึ่งจะเก็บคลาสที่มีคำอธิบายประกอบด้วย @ClassAnnotation และในคลาสเหล่านั้นจะเก็บเมธอดที่มีคำอธิบายประกอบด้วย @MethodAnnotation และฟิลด์ที่มีคำอธิบายประกอบด้วย @FieldAnnotation

พิจารณาใช้กฎการเก็บตามคำอธิบายประกอบเมื่อเป็นไปได้ แนวทางนี้จะสร้างลิงก์ที่ชัดเจนระหว่างโค้ดกับกฎการเก็บรักษา และมักจะนำไปสู่การกำหนดค่าที่แข็งแกร่งยิ่งขึ้น ตัวอย่างเช่น ไลบรารีคำอธิบายประกอบ androidx.annotation ใช้กลไกนี้

สำหรับการสะท้อนในโค้ดของคุณเอง คุณสามารถใช้ Jetpack Keep ได้ด้วย แทนที่จะเขียนกฎการเก็บรักษาที่กำหนดเป้าหมายเป็นคำอธิบายประกอบ คุณสามารถใส่คำอธิบายประกอบในโค้ด แล้วปลั๊กอิน Gradle จะสร้างกฎการเก็บรักษาให้คุณ

เมธอด

ไวยากรณ์สำหรับการระบุเมธอดในข้อกำหนดสมาชิกสำหรับกฎการเก็บรักษาเป็นดังนี้

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

ตัวอย่างเช่น กฎการเก็บรักษาต่อไปนี้จะเก็บรักษาวิธีการสาธารณะที่ชื่อ getUserId() ซึ่งแสดงผล String

-keep class com.example.model.UserData {
    public java.lang.String getUserId();
}

คุณสามารถใช้ <methods> เป็นทางลัดเพื่อจับคู่เมธอดทั้งหมดในคลาสได้ดังนี้

-keep class com.example.model.UserData {
    <methods>;
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีระบุประเภทสำหรับประเภทการแสดงผลและประเภทพารามิเตอร์ได้ที่ประเภท

ผู้ผลิต

หากต้องการระบุคอนสตรักเตอร์ ให้ใช้ <init> ไวยากรณ์สำหรับการระบุตัวสร้าง ในการระบุสมาชิกสำหรับกฎการเก็บมีดังนี้

[<access_modifier>] <init>(parameter_types);

ตัวอย่างเช่น กฎ Keep ต่อไปนี้จะเก็บคอนสตรัคเตอร์สำหรับตัวเก็บสถานะ UI ที่รับอินสแตนซ์ repository

-keep class com.example.ui.state.UserViewModel {
    public <init>(com.example.repository.UserDataRepository);
}

หากต้องการเก็บตัวสร้างสาธารณะทั้งหมด ให้ใช้ตัวอย่างต่อไปนี้เป็นข้อมูลอ้างอิง

-keep class com.example.ui.state.UserViewModel {
    public <init>(...);
}

ทุ่ง

ไวยากรณ์สำหรับการระบุช่องในข้อกำหนดสมาชิกสำหรับกฎการเก็บรักษาจะเป็นดังนี้

[<access_modifier>...] [<type>] <field_name>;

ตัวอย่างเช่น กฎการเก็บรักษาต่อไปนี้จะเก็บฟิลด์สตริงส่วนตัวที่ชื่อ userId และฟิลด์จำนวนเต็มแบบคงที่สาธารณะที่ชื่อ STATUS_ACTIVE

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

คุณใช้ <fields> เป็นแป้นพิมพ์ลัดเพื่อจับคู่ฟิลด์ทั้งหมดในชั้นเรียนได้ดังนี้

-keep class com.example.models.User {
    <fields>;
}

ประเภท

ส่วนนี้จะอธิบายวิธีระบุประเภทการแสดงผล ประเภทพารามิเตอร์ และประเภทฟิลด์ในข้อกำหนดของสมาชิกกฎการเก็บรักษา อย่าลืมใช้ชื่อ Java ที่สร้างขึ้นเพื่อระบุประเภทหากแตกต่างจากซอร์สโค้ด Kotlin

ประเภทพื้นฐาน

หากต้องการระบุประเภทดั้งเดิม ให้ใช้คีย์เวิร์ด Java ของประเภทนั้น R8 จะจดจำประเภทพื้นฐานต่อไปนี้ boolean, byte, short, char, int, long, float, double

ตัวอย่างกฎที่มีประเภทพื้นฐานมีดังนี้

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

ประเภททั่วไป

ในระหว่างการคอมไพล์ คอมไพเลอร์ Kotlin/Java จะลบข้อมูลประเภททั่วไป ดังนั้นเมื่อคุณเขียนกฎการเก็บรักษาที่เกี่ยวข้องกับประเภททั่วไป คุณต้องกำหนดเป้าหมายการแสดงโค้ดที่คอมไพล์แล้ว ไม่ใช่ซอร์สโค้ดต้นฉบับ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีเปลี่ยนประเภททั่วไปได้ที่การลบประเภท

ตัวอย่างเช่น หากคุณมีโค้ดต่อไปนี้ซึ่งมีประเภททั่วไปที่ไม่มีขอบเขตซึ่งกำหนดไว้ใน Box.kt

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

หลังจากลบประเภทแล้ว T จะถูกแทนที่ด้วย Object หากต้องการเก็บเมธอดและคอนสตรัคเตอร์ของคลาสไว้ กฎของคุณต้องใช้ java.lang.Object แทน T ทั่วไป

ตัวอย่างกฎการเก็บรักษาจะเป็นดังนี้

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

หากคุณมีโค้ดต่อไปนี้ซึ่งมีประเภททั่วไปที่ถูกจำกัดขอบเขตใน NumberBox.kt

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

ในกรณีนี้ การลบประเภทจะแทนที่ T ด้วยขอบเขต java.lang.Number

ตัวอย่างกฎการเก็บมีดังนี้

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

เมื่อใช้ประเภททั่วไปเฉพาะแอปเป็นคลาสพื้นฐาน คุณจะต้องรวมกฎการเก็บสำหรับคลาสพื้นฐานด้วย

ตัวอย่างเช่น สำหรับโค้ดต่อไปนี้

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

คุณใช้กฎการเก็บรักษาได้กับ includedescriptorclasses เพื่อเก็บรักษาทั้งคลาส UnpackOptions และเมธอดคลาส Box ด้วยกฎเดียวได้ดังนี้

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

หากต้องการเก็บฟังก์ชันที่เฉพาะเจาะจงซึ่งประมวลผลรายการออบเจ็กต์ คุณต้องเขียนกฎที่ตรงกับซิกเนเจอร์ของฟังก์ชันนั้นทุกประการ โปรดทราบว่าเนื่องจากประเภททั่วไปถูกลบออก พารามิเตอร์อย่าง List<Product> จึงถูกมองว่าเป็น java.util.List

ตัวอย่างเช่น หากคุณมีคลาสยูทิลิตีที่มีฟังก์ชันที่ประมวลผลรายการออบเจ็กต์ Product ดังนี้

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

คุณสามารถใช้กฎการเก็บรักษาต่อไปนี้เพื่อปกป้องเฉพาะฟังก์ชัน processProducts

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

ประเภทอาร์เรย์

ระบุประเภทอาร์เรย์โดยเพิ่ม [] ต่อท้ายประเภทคอมโพเนนต์สำหรับแต่ละมิติข้อมูลของอาร์เรย์ ซึ่งมีผลกับทั้งประเภทคลาสและประเภทพื้นฐาน

  • อาร์เรย์คลาสแบบ 1 มิติ: java.lang.String[]
  • อาร์เรย์พื้นฐาน 2 มิติ: int[][]

ตัวอย่างเช่น หากคุณมีโค้ดต่อไปนี้

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

คุณใช้กฎการเก็บรักษาต่อไปนี้ได้

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

ตัวอย่าง

เช่น หากต้องการเก็บคลาสที่เฉพาะเจาะจงและสมาชิกทั้งหมดของคลาส ให้ใช้คำสั่งต่อไปนี้

-keep class com.myapp.MyClass { *; }

หากต้องการเก็บเฉพาะคลาสพร้อมกับคอนสตรัคเตอร์เริ่มต้น แต่ไม่เก็บสมาชิกอื่นๆ ให้ใช้โค้ดต่อไปนี้

-keep class com.myapp.MyClass

เราขอแนะนำให้คุณระบุสมาชิกบางรายเสมอ ตัวอย่างเช่น ตัวอย่างต่อไปนี้จะเก็บฟิลด์สาธารณะ text และเมธอดสาธารณะ updateText() ไว้ในคลาส MyClass

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

หากต้องการเก็บฟิลด์สาธารณะและเมธอดสาธารณะทั้งหมดไว้ โปรดดูตัวอย่างต่อไปนี้

-keep public class com.example.api.ApiClient {
    public *;
}

ละเว้นการระบุสมาชิก

การละเว้นข้อกำหนดของสมาชิกจะทำให้ R8 เก็บตัวสร้างเริ่มต้นสำหรับ คลาส

เช่น หากคุณเขียน -keep class com.example.MyClass หรือ -keep class com.example.MyClass {} R8 จะถือว่าคุณเขียนดังนี้

-keep class com.example.MyClass{
  void <init>();
}

ยกเว้นรูปแบบชื่อสมาชิก

ตั้งแต่ปลั๊กอิน Android Gradle (AGP) 9.2.0 เป็นต้นไป คุณสามารถปฏิเสธรูปแบบชื่อสมาชิกภายในกฎการเก็บรักษาได้ ซึ่งช่วยให้คุณระบุสมาชิกที่จะเก็บไว้หรือยกเว้นตามรูปแบบได้ หากต้องการปฏิเสธรูปแบบ ให้ใส่เครื่องหมายอัศเจรีย์ (!) ไว้หน้ารูปแบบชื่อสมาชิก

คุณสามารถใช้ฟีเจอร์นี้ในกรณีที่ต้องการเก็บสมาชิกส่วนใหญ่ที่ ตรงกับรูปแบบที่กว้างขึ้น แต่ยกเว้นสมาชิกบางราย เช่น วิธีการที่ตั้งใจใช้สำหรับการทดสอบเท่านั้น

ตัวอย่าง

หากต้องการเก็บเมธอดสาธารณะทั้งหมดใน com.example.MyClass ยกเว้นเมธอดที่ลงท้ายด้วย "ForTesting" ให้ใช้กฎต่อไปนี้

-keepclassmembers class com.example.MyClass {
    public *** !*ForTesting(...);
}

ฟังก์ชันระดับแพ็กเกจ

หากต้องการอ้างอิงฟังก์ชัน Kotlin ที่กำหนดไว้นอกคลาส (โดยทั่วไปเรียกว่าฟังก์ชันระดับบนสุด) ให้ใช้ชื่อ Java ที่สร้างขึ้นสำหรับคลาสที่คอมไพเลอร์ Kotlin เพิ่มโดยนัย ชื่อคลาสคือชื่อไฟล์ Kotlin ที่มี Kt ต่อท้าย ตัวอย่างเช่น หากคุณมีไฟล์ Kotlin ชื่อ MyClass.kt ซึ่งกำหนดไว้ดังนี้

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

หากต้องการเขียนกฎการเก็บสำหรับฟังก์ชัน isEmailValid ข้อกำหนดของคลาส ต้องกำหนดเป้าหมายเป็นคลาส MyClassKt ที่สร้างขึ้น

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

สัญลักษณ์แทน

ตารางต่อไปนี้แสดงวิธีใช้ไวลด์การ์ดเพื่อใช้กฎการเก็บรักษากับชั้นเรียนหรือสมาชิกหลายรายการที่ตรงกับรูปแบบที่กำหนด

ไวลด์การ์ด ใช้กับคลาสหรือสมาชิก คำอธิบาย
** ทั้งคู่ ใช้กันโดยทั่วไป จับคู่ชื่อประเภทใดก็ได้ รวมถึงตัวคั่นแพ็กเกจจำนวนเท่าใดก็ได้ ซึ่งจะมีประโยชน์สำหรับการจับคู่คลาสทั้งหมดภายในแพ็กเกจและแพ็กเกจย่อย
* ทั้งคู่ สำหรับข้อกำหนดคลาส จะจับคู่ส่วนใดก็ได้ของชื่อประเภทที่ไม่มีตัวคั่นแพ็กเกจ (.)
สำหรับข้อกำหนดสมาชิก จะจับคู่ชื่อเมธอดหรือชื่อฟิลด์ใดก็ได้ เมื่อใช้ด้วยตัวเอง ก็จะเป็นชื่อแทนของ ** ด้วย
? ทั้งคู่ จับคู่อักขระตัวเดียวในชื่อคลาสหรือชื่อสมาชิก
*** สมาชิก จับคู่ประเภทใดก็ได้ รวมถึงประเภทพื้นฐาน (เช่น int) ประเภทคลาส (เช่น java.lang.String) และประเภทอาร์เรย์ของมิติข้อมูลใดก็ได้ (เช่น byte[][])
... สมาชิก จับคู่รายการพารามิเตอร์ใดก็ได้สำหรับเมธอด
% สมาชิก จับคู่กับประเภทพื้นฐาน (เช่น int, float, boolean หรืออื่นๆ)

ตัวอย่างวิธีใช้อักขระไวลด์การ์ดพิเศษมีดังนี้

  • หากคุณมีเมธอดหลายรายการที่มีชื่อเดียวกันซึ่งรับอินพุตเป็นประเภทดั้งเดิมที่แตกต่างกัน คุณสามารถใช้ % เพื่อเขียนกฎการเก็บรักษาที่จะเก็บรักษาเมธอดทั้งหมดไว้ได้ ตัวอย่างเช่น คลาส DataStore นี้มีเมธอด setValue หลายรายการ ดังนี้

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    กฎการเก็บรักษาต่อไปนี้จะเก็บรักษาวิธีการทั้งหมด

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • หากคุณมีหลายชั้นเรียนที่มีชื่อต่างกันเพียง 1 อักขระ ให้ใช้ ? เพื่อเขียนกฎการเก็บรักษาที่จะเก็บรักษาชั้นเรียนทั้งหมด เช่น หากคุณมีชั้นเรียนต่อไปนี้

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    กฎการเก็บรักษาต่อไปนี้จะเก็บคลาสทั้งหมดไว้

    -keep class com.example.models.UserV?
    
  • หากต้องการจับคู่คลาส Example และ AnotherExample (หากเป็นคลาสระดับรูท) แต่ไม่จับคู่ com.foo.Example ให้ใช้กฎการเก็บต่อไปนี้

    -keep class *Example
    
  • หากคุณใช้ * เพียงอย่างเดียว ระบบจะถือว่าเครื่องหมายนี้เป็นนามแฝงของ ** ตัวอย่างเช่น กฎการเก็บข้อมูลต่อไปนี้มีความหมายเหมือนกัน

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

กฎการเก็บรักษาแบบมีเงื่อนไข

นอกเหนือจากกฎการเก็บรักษามาตรฐานแล้ว คุณยังใช้กฎการเก็บรักษาแบบมีเงื่อนไขได้ ซึ่งจะมีผลก็ต่อเมื่อเป็นไปตามเงื่อนไขที่เฉพาะเจาะจงเท่านั้น คุณระบุกฎแบบมีเงื่อนไขได้ โดยใช้แฟล็ก -if กฎการเก็บรักษาที่ตามหลังแฟล็ก -if จะมีผลก็ต่อเมื่อ ข้อกำหนดของคลาสในแฟล็ก -if ตรงกัน

กฎการคงไว้แบบมีเงื่อนไขมีประโยชน์อย่างยิ่งเมื่อต้องจัดการกับไลบรารีหรือรูปแบบโค้ดที่ใช้การสะท้อน ซึ่งจะใช้กฎการคงไว้ก็ต่อเมื่อมีคลาสหรือสมาชิกบางรายการ หรือตรงกับรูปแบบ การใช้กฎแบบมีเงื่อนไขจะช่วย ลดขนาดแอปโดยป้องกันการเก็บรักษาโค้ดที่ไม่จำเป็น

ไวยากรณ์ทั่วไปสำหรับกฎการเก็บรักษาแบบมีเงื่อนไขมีดังนี้

-if <class_specification_if> <keep_rule>

หากข้อกำหนดของคลาสใน-ifเงื่อนไขมีไวลด์การ์ด (เช่น * หรือ **) ระบบจะบันทึกลำดับอักขระที่ตรงกับไวลด์การ์ด คุณสามารถอ้างอิงสตริงที่บันทึกไว้เหล่านี้ในกฎการเก็บรักษาที่ตามมาโดยใช้การอ้างอิงย้อนกลับ โดย <1> หมายถึงสตริงที่บันทึกโดยไวลด์การ์ดแรก <2> หมายถึงสตริงที่บันทึกโดยไวลด์การ์ดที่ 2 และอื่นๆ

ตัวอย่างเช่น คอมโพเนนต์ Jetpack Navigation จะสร้างคลาส NavArgs สำหรับการส่งอาร์กิวเมนต์ที่ปลอดภัยในการกำหนดประเภทระหว่างปลายทาง เมื่อใช้ผู้รับมอบสิทธิ์ NavArgsLazy ระบบจะใช้การสะท้อนเพื่อค้นหาและเรียกใช้เมธอด fromBundle แบบคงที่ในคลาส NavArgs ที่สร้างขึ้นเพื่อดีซีเรียลไลซ์อาร์กิวเมนต์ หากแอปใช้ NavArgs คุณจะต้องเก็บเมธอด fromBundle ไว้เฉพาะสำหรับคลาสที่ ใช้NavArgsอินเทอร์เฟซ

คุณสามารถใช้กฎการเก็บแบบมีเงื่อนไขเพื่อระบุว่าหากคลาสใดใช้ androidx.navigation.NavArgs R8 ควรเก็บเมธอด fromBundle ไว้สำหรับคลาสนั้นโดยเฉพาะ

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

ในตัวอย่างนี้ ** เป็นไวลด์การ์ดตัวแรกและตัวเดียวในเงื่อนไข -if โดยจะจับคู่กับชื่อคลาสของคลาสใดก็ได้ที่ใช้ androidx.navigation.NavArgs ระบบจะบันทึกสตริงที่ ** ตรงกัน (ในกรณีนี้คือชื่อคลาส) และคุณจะอ้างอิงสตริงดังกล่าวได้โดยใช้ <1> ในกฎที่ตามมา ดังนั้นกฎ -keepclassmembers จะมีผลกับคลาสที่ใช้ androidx.navigation.NavArgs ซึ่งตรงกับเงื่อนไข -if หาก R8 ไม่พบคลาสที่ใช้ NavArgs ระบบจะละเว้นกฎการเก็บรักษานี้

กรณีการใช้งานทั่วไปอีกกรณีหนึ่งคือไลบรารีการแปลงรูปแบบ JSON เป็นอนุกรม เช่น Gson หากคลาสโมเดลข้อมูลใช้คำอธิบายประกอบ @SerializedName ของ Gson ในฟิลด์ใดก็ตาม คุณจะใช้กฎแบบมีเงื่อนไขเพื่อปกป้องคลาสดังกล่าวและสมาชิกที่ Gson ต้องการสำหรับการสะท้อนได้

# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
    @com.google.gson.annotations.SerializedName <fields>;
    <init>(...);
}

การอ้างอิงย้อนกลับจะจับสตริง ซึ่งอาจเป็นสตริงย่อยของชื่อคลาสหากไวลด์การ์ดตรงกับชื่อเพียงบางส่วน เช่น หากคุณใช้ -if class com.example.*X*, R8 จะจับภาพสตริงย่อยก่อน X เป็น <1> และสตริงย่อยหลัง X เป็น <2> กฎต่อไปนี้ใช้เพื่อค้นหาชื่อคลาสที่มี X และเก็บคลาสที่เกี่ยวข้องไว้โดยแทนที่ X ด้วย Y

# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>

กฎการเก็บรักษาแบบมีเงื่อนไขสำหรับการสะท้อนความรู้สึก

Use Case ทั่วไปอย่างหนึ่งสำหรับกฎการเก็บแบบมีเงื่อนไขคือการจัดการการสะท้อน ซึ่งจะมีการเข้าถึงเมธอดหรือคลาสที่เฉพาะเจาะจงแบบไดนามิกในรันไทม์ ตัวอย่างเช่น หากไลบรารีใช้การสะท้อนเพื่อโต้ตอบกับโค้ดของคุณ คุณอาจต้อง เก็บสมาชิกบางรายไว้เท่านั้นหากใช้ฟีเจอร์ที่เฉพาะเจาะจงของไลบรารีนั้น

ไลบรารี Jetpack Navigation ใช้การสะท้อนโดยใช้NavArgsLazy delegate เพื่อเรียกใช้เมธอด fromBundle แบบคงที่ในคลาส NavArgs ที่สร้างขึ้นสำหรับ การส่งอาร์กิวเมนต์ที่ปลอดภัยตามประเภท เพื่อให้มั่นใจว่าวิธีนี้จะใช้เฉพาะกับNavArgs การติดตั้งใช้งานเท่านั้น ไม่ใช่ทุกคลาส Jetpack Navigation จึงมีกฎการเก็บแบบมีเงื่อนไขต่อไปนี้

# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
    public static ** fromBundle(android.os.Bundle);
}

กฎนี้จะเก็บ fromBundle ไว้เฉพาะในชั้นเรียนที่จำเป็นเท่านั้น แทนที่จะเก็บไว้ในทุกชั้นเรียนหรือกำหนดให้คุณระบุด้วยตนเองว่าชั้นเรียนใดที่จำเป็นต้องใช้

ตรวจสอบชื่อ Java ที่สร้างขึ้น

เมื่อเขียนกฎการเก็บรักษา คุณต้องระบุคลาสและประเภทอ้างอิงอื่นๆ โดยใช้ชื่อหลังจากที่คอมไพล์เป็น Java bytecode แล้ว (ดูตัวอย่างได้ที่ ข้อกำหนดของคลาสและประเภท) หากต้องการตรวจสอบชื่อ Java ที่สร้างขึ้นสำหรับโค้ดของคุณ ให้ใช้เครื่องมืออย่างใดอย่างหนึ่งต่อไปนี้ใน Android Studio

  • ตัววิเคราะห์ APK
  • เมื่อไฟล์ต้นฉบับ Kotlin เปิดอยู่ ให้ตรวจสอบไบต์โค้ดโดยไปที่เครื่องมือ > Kotlin > แสดงไบต์โค้ด Kotlin > ดีคอมไพล์