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

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

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


-<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

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

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

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

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

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

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

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

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

ตัวเลือก Keep ลดขนาดคลาส ปิดบังคลาส ลดจำนวนสมาชิก ซ่อนสมาชิก
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

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

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

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

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

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

คุณต้องระบุคลาส (รวมถึงอินเทอร์เฟซ คลาส Enum และคลาส Annotation) ใน ทุกกฎการเก็บ คุณสามารถจำกัดกฎตามคำอธิบายประกอบได้โดย ระบุคลาสหลักหรืออินเทอร์เฟซที่ใช้ หรือโดยการระบุตัวแก้ไขการเข้าถึง สำหรับคลาส ต้องระบุคลาสทั้งหมด รวมถึงคลาสจากเนมสเปซ 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

หากกฎการเก็บมีคำอธิบายประกอบมากกว่า 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

กฎการเก็บรักษาต่อไปนี้จะเก็บรักษาคลาสทั้งหมดที่ใช้ 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คลังคำอธิบายประกอบ ใช้กลไกนี้

เมธอด

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

[<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);

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

-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 จะสร้างคลาส NavArgs สำหรับ การส่งอาร์กิวเมนต์ที่ปลอดภัยต่อประเภทระหว่างปลายทาง เมื่อใช้ NavArgsLazy delegate จะใช้การสะท้อนเพื่อค้นหาและเรียกใช้เมธอด 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 > แยก