วิธีการกำหนดค่ากฎการทำ Obfuscation ด้วย ProGuard สำหรับ Android SDK อย่างปลอดภัย

opoinstall
2026-11-11
5 min read

วิธีแยก Mobile SDK ออกจากการทำ Obfuscation ของ Android ProGuard? การแยก Android SDK ออกจากการทำ Obfuscation ต้องอาศัยการตรวจสอบว่า Library นั้นๆ มีการรวม Consumer ProGuard rules มาด้วยหรือไม่, การเพิ่มคำสั่ง -keep และ -keepclassmembers แบบเจาะจงสำหรับจุดเชื่อมต่อที่มีการเรียกผ่าน Reflection, Callback interface, JNI bridges และ Data models ตลอดจนการตรวจสอบสัญลักษณ์ที่ถูกเก็บรักษาผ่านรายงาน R8 diagnostic และการตรวจสอบ DEX โดยตรง เมื่อทีมวิศวกร Android เปิดใช้งานการลดขนาดโค้ด (Code shrinking) และการปรับแต่ง (Optimization) สำหรับ Build รุ่นใช้งานจริง (Production release) ตัว Compiler R8 จะลบ Bytecode ที่ไม่ได้ใช้งานและเปลี่ยนชื่อคลาส, ฟิลด์, และเมธอดให้กลายเป็นตัวระบุสั้นๆ หาก Software Development Kit (SDK) สำหรับการระบุแหล่งที่มาของการติดตั้งบนมือถือมีการใช้งาน Dynamic reflection หรือ JNI callbacks ที่ไม่ได้รับการป้องกันจากการทำ Tree-shaking และการเปลี่ยนชื่อ ผลลัพธ์คือฟังก์ชันการทำ Deferred deep linking, การ Deserialize โมเดล JSON และการทำ Channel-package build อาจทำงานผิดพลาดโดยไม่มีการแจ้งเตือนในสภาพแวดล้อมจริง แม้ว่าจะผ่านการทดสอบทุกกรณีใน Debug build ที่ไม่ได้ทำ Minified ก็ตาม OpoInstall ซึ่งเป็นแพลตฟอร์มสำหรับการระบุแหล่งที่มาของการติดตั้ง (Attribution) และ Deep linking บนมือถือ ได้จัดเตรียมไฟล์ Android SDK Archive และเวิร์กโฟลว์สำหรับ Channel-package ไว้ให้ เพื่อให้ทีมวิศวกรสามารถกำหนดค่าและตรวจสอบความถูกต้องภายใน R8-minified release pipeline ได้อย่างราบรื่น

ใน Toolchain ของ Android Gradle Plugin (AGP) รุ่นปัจจุบัน ไฟล์ Keep-rule (proguard-rules.pro ใน DSL แบบดั้งเดิม หรือ src/<variant>/keepRules/*.keep ใน AGP 9.3 ขึ้นไป) จะทำหน้าที่เป็นข้อตกลงในการกำหนดค่าสำหรับ Compiler R8 ของ Google การแยก Third-party Android SDK ออกจากการทำ Obfuscation อย่างปลอดภัยหมายถึงการคงไว้เพียงจุดเชื่อมต่อ Public API, Constructor ที่ถูกเรียกแบบ Reflective, Callback interface, JNI upcalls และฟิลด์ของโมเดลที่จำเป็นขณะรันไทม์เท่านั้น โดยหลีกเลี่ยงการใช้กฎ Keep ครอบคลุมทั้งแพ็คเกจแบบกว้างเกินไป หรือการกดปิดคำเตือนโดยไม่ได้ตรวจสอบ

คำศัพท์ คำนิยาม เอนทิตีที่เกี่ยวข้อง วัตถุประสงค์ของการค้นหา
Android SDK Library ภาษา Java หรือ Kotlin ที่ถูกคอมไพล์แล้วและรวมอยู่ในแอป Android เพื่อให้บริการความสามารถเฉพาะทาง เช่น การระบุแหล่งที่มาของการติดตั้ง (Attribution) classes.dex Bytecode Navigational / Transactional
ProGuard / R8 เครื่องมือในขณะ Build ของ Android ที่ใช้ลดขนาดโค้ด, ปรับแต่ง Bytecode และทำ Obfuscation โดยใช้กฎ ProGuard proguard-rules.pro Technical / Informational
Consumer ProGuard Rules กฎ Obfuscation ที่รวมอยู่ใน .aar (/proguard.txt) หรือ .jar (/META-INF/proguard/) ซึ่งจะถูกผสานเข้ากับ R8 โดยอัตโนมัติ consumerProguardFiles Technical / Troubleshooting
R8 Diagnostic Reports ไฟล์เอาต์พุต (mapping.txt, seeds.txt, usage.txt, configuration.txt) ที่แสดงรายละเอียดการเปลี่ยนชื่อสัญลักษณ์, Keep seeds ที่ตรงกัน และโค้ดที่ถูกตัดออก Release Build Audit Technical / Troubleshooting

ทำไมการทำ Obfuscation ด้วย R8 และ ProGuard ถึงทำให้ Android SDK Attribution Callbacks ทำงานผิดพลาด

การปรับแต่งด้วย Android R8 จะรักษาชื่อสัญลักษณ์ SDK ที่จำเป็นไว้ระหว่างการคอมไพล์รุ่น Release

กับดักระหว่าง Debug กับ Release: ทำไม Attribution ถึงทำงานในการทดสอบแต่ล้มเหลวใน Production

ปัญหาหนึ่งที่พบบ่อยที่สุดในการทำวิศวกรรม Release สำหรับการดึงดูดผู้ใช้งานบนมือถือ คือเมื่อฟังก์ชัน Deferred deep linking (handleDeepLink) และการกู้คืนพารามิเตอร์การติดตั้ง (getInstallParam) ของแอปพลิเคชันทำงานได้ดีในระหว่าง QA testing บน Debug builds แต่กลับส่ง Payload ว่างเปล่าหรือหยุดทำงานในส่วน Callback ทันทีที่แอปถูกคอมไพล์เป็น APK หรือ Android App Bundle (.aab) สำหรับ Release

สาเหตุหลักอยู่ที่ความแตกต่างของ Build-type ที่กำหนดใน Gradle โดยปกติแล้ว debug build type จะปิดการทำงานของ Code shrinking เพื่อเร่งความเร็วในการทำ Incremental builds เมื่อวิศวกรคอมไพล์ variant แบบ release โดยเปิดใช้งานการปรับแต่ง R8 ตัว Android Gradle Plugin จะเรียกใช้ Android R8 Compiler เพื่อทำการแปลง Bytecode 3 ขั้นตอนทั้งในตัวแอปและ Library ภายนอกที่เชื่อมโยงอยู่:

  • Code Shrinking (Tree Shaking): R8 จะวิเคราะห์แผนผังการเรียกใช้งาน (Call graph) โดยเริ่มจากจุดเริ่มต้นที่ประกาศไว้ (เช่น คอมโพเนนต์ใน AndroidManifest.xml และ -keep seeds ที่ชัดเจน) และตัดคลาส, Constructor, เมธอด หรือฟิลด์ใดๆ ที่ดูเหมือนไม่สามารถเข้าถึงได้จากการวิเคราะห์แบบสถิตออกไป
  • การปรับแต่ง Bytecode และการแก้ไขการเข้าถึง (Access Modification): R8 จะทำการ Inlining เมธอด, ลบพารามิเตอร์ที่ไม่ได้ใช้, รวมคลาสเข้าด้วยกัน และเมื่อใช้ร่วมกับคำสั่ง -allowaccessmodification ในการตั้งค่าการปรับแต่งมาตรฐานของ Android จะมีการแก้ไขสถานะการเข้าถึงสมาชิก (เช่น เปลี่ยนจาก private หรือ Package-private เป็น public ระหว่างการทำ Inlining) เพื่อลดขนาด Dalvik Executable (.dex) และเพิ่มความเร็วในการรันไทม์
  • การทำ Obfuscation (การเปลี่ยนชื่อและการจัดโครงสร้างแพ็คเกจใหม่): R8 จะเปลี่ยนชื่อคลาส, แพ็คเกจ, ฟิลด์ และเมธอดที่เหลืออยู่ให้เป็นตัวระบุขนาดเล็ก (เช่น a.a.b()) และอาจจัดกลุ่มคลาสที่ถูก Obfuscate เข้าไปในลำดับชั้นแพ็คเกจที่แบนราบขึ้น เพื่อลดภาระของ String-pool ภายใน classes.dex

โหมด Full Mode ของ R8 ถูกเปิดใช้งานเป็นค่าเริ่มต้นตั้งแต่ Android Gradle Plugin (AGP) 8.0 หากเทียบกับโหมด ProGuard compatibility แบบเดิม โหมด R8 Full Mode จะใช้การปรับแต่งที่เข้มงวดกว่าอย่างมาก โดยจะลบ Constructor ที่ไม่มีอาร์กิวเมนต์ (<init>()) ที่ไม่ถูกเรียกใช้งานทิ้ง, เปลี่ยนแปลง Modifier การเข้าถึงสมาชิก และตัด Metadata attributes ออก เว้นแต่คลาสและสมาชิกนั้นจะถูกเก็บรักษาไว้โดยกฎ Keep ที่ตรงกัน สำหรับบริบทพื้นฐานเกี่ยวกับโครงสร้างไบนารี .aar และมาตรฐานการจัดซื้อ SDK โปรดดูคำแนะนำเพิ่มเติมของเราได้ที่ ทำความเข้าใจความหมายของ Mobile SDK และโปรโตคอลการเติบโตทางเทคนิค และ วิธีเลือก Software Development Kit เพื่อการทำ Attribution ที่แม่นยำ

โหมดความล้มเหลว 6 ประการใน Attribution SDKs ที่ผ่านการทำ Obfuscation

Android SDK reflection callbacks models และ JNI ต้องการกฎการเก็บรักษา (Keep rules) ของ R8 ที่ตรงจุด

เนื่องจากการวิเคราะห์ Call-graph แบบสถิตไม่สามารถคาดการณ์การค้นหาแบบรันไทม์ (Dynamic runtime lookups) ได้ Android SDK ที่ไม่มีการป้องกันอาจเกิดโหมดความล้มเหลว 6 ประการภายใต้ R8 Full Mode วิศวกรต้องแยกแยะโหมดเหล่านี้อย่างระมัดระวัง เนื่องจากแต่ละโหมดต้องการเป้าหมายการป้องกันด้วย ProGuard ที่เฉพาะเจาะจง:

โหมดความล้มเหลวขณะรันไทม์ สาเหตุหลักของ R8 Bytecode เป้าหมายการป้องกันที่จำเป็น
1. การค้นหาคลาสด้วย Reflection ล้มเหลว (ClassNotFoundException) R8 เปลี่ยนชื่อ, จัดแพ็คเกจใหม่ หรือตัดคลาสที่โหลดแบบไดนามิกผ่าน String name ด้วย Class.forName() คงไว้ซึ่งคลาสเป้าหมายและตัวระบุเดิมโดยใช้ -keep class ...
2. การสร้างออบเจกต์ด้วย Reflection ล้มเหลว (NoSuchMethodException: <init>) ใน R8 Full Mode แม้คลาสจะถูกเก็บไว้ แต่ Constructor เริ่มต้นที่ไม่มีอาร์กิวเมนต์ (<init>()) อาจถูกตัดออกหากไม่มีการเรียกใช้งานโดยตรง คง Constructor signature (<init>();) ไว้ภายในบล็อก -keep ของคลาสนั้น
3. ความล้มเหลวของ Dynamic Callback และการแก้ไขการเข้าถึง R8 ลบหรือเปลี่ยนชื่อเมธอดใน Callback interface หรือเปลี่ยน Modifier การเข้าถึงสมาชิกที่ถูกเรียกใช้ผ่าน getDeclaredMethod() / getDeclaredField() คงเมธอดของ Callback interface ไว้ (-keep interface ... { <methods>; }) รวมถึงสมาชิกที่ถูกเรียกผ่าน Reflection
4. การสูญเสีย Metadata ของ Generic Signature ใน Callback listeners Generics ใน Java จะถูกทำ Type erasure ในขั้นตอนคอมไพล์ แต่ Bytecode Signature attributes จะเก็บ Metadata ของ Generics ไว้สำหรับการทำ Reflection (ResultCallBack<OpoData>) R8 Full Mode จะตัด Signature metadata ออกหากไม่ได้รับคำสั่งให้คงไว้ เพิ่ม -keepattributes Signature นอกเหนือจาก การคงไว้ทั้ง Callback interface แบบ Generic และคลาสโมเดลเป้าหมาย
5. ความล้มเหลวในการ Deserialize โมเดลข้อมูล JSON R8 ตัดคลาสโมเดลออก (หากถูกสร้างแบบสะท้อนกลับเท่านั้น) หรือเปลี่ยนชื่อฟิลด์เป็น a, b, c ทำให้ Parser ของ JSON ที่ใช้ Reflection ส่งค่ากลับเป็น Null หรือฟิลด์ว่าง ใช้ -keep class ... หากโมเดลถูกสร้างแบบไดนามิก หรือ -keepclassmembers หากคลาสนั้นถูกเก็บไว้ผ่านการอ้างอิงโดยตรงแล้ว
6. การลบ JNI Downcall และ Native-to-Java Upcall (UnsatisfiedLinkError / NoSuchMethodError) กฎ Android เริ่มต้นจะรักษาชื่อใน Java native downcalls ที่ยังคงอยู่ แต่ไม่ป้องกันไม่ให้เมธอด RegisterNatives ที่ไม่ได้ใช้งานถูกลดขนาด และมองไม่เห็นโค้ด C/C++ .so ที่เรียกกลับมายัง Java ผ่าน JNI (FindClass, GetMethodID) สั่ง -keep แบบเจาะจงสำหรับ native declarations ที่ลงทะเบียนแบบไดนามิก และคลาส, Constructor, เมธอดของ Java ที่ถูกเรียกผ่าน JNI upcalls จาก C/C++
[Android Release Build: R8 Full Mode Enabled]
                      |
                      v
     [R8 ประเมินกฎที่กำหนดไว้ทั้งของโฮสต์และ Library]
                      |
      +---------------+---------------+
      |                               |
      v (กฎไม่ครบถ้วน / ไม่ตรงกัน)  v (ใช้ Scoped Keep Rules + Attributes)
[สัญลักษณ์ SDK ถูกตัดหรือเปลี่ยนชื่อ] [จุดเริ่มต้น SDK และโมเดลที่จำเป็นถูกเก็บรักษาไว้]
- ลบ () ที่เรียกผ่าน Reflection    - ยืนยันได้ใน -printseeds (seeds.txt)
- JNI Upcall / ฟิลด์โมเดลสูญหาย   - ตรวจสอบได้ใน Raw DEX (apkanalyzer dex packages)
- Generic Signature Metadata หาย - การทำ Attribution ใน Release ตรงกับการทำงานของ Debug

กฎสำหรับผู้ใช้งาน (Consumer Rules) ใน AAR Bundles เทียบกับการ Import JAR แบบแมนนวล

วิธีที่ไลบรารี AAR และ JAR รวมกฎ Consumer ProGuard เข้ามาด้วย

Archieve ประเภท Android AAR และ JAR จะสนับสนุนกฎสำหรับผู้ใช้งานไปยังการตั้งค่า R8

ก่อนที่จะเพิ่มกฎการยกเว้นด้วยตนเองให้กับแอปพลิเคชันของคุณ ให้ตรวจสอบก่อนว่า Third-party Android SDK นั้นมีกฎ R8 ในตัวหรือไม่ ตามที่ระบุไว้ใน Google Android Developers Guide เกี่ยวกับการปรับแต่ง Library, AGP และ R8 รองรับการค้นหากฎของผู้ใช้งาน (Consumer rules) โดยอัตโนมัติ ทั้งในรูปแบบ .aar และ .jar รวมถึงกฎ R8 ที่กำหนดเป้าหมายตามเวอร์ชัน:

รูปแบบการกระจาย SDK ตำแหน่งของ Consumer Rule ที่ฝังอยู่ พฤติกรรมการ Build ของ AGP / R8
Android Archive (.aar) /proguard.txt ที่ Root ของ AAR (ผ่าน consumerProguardFiles), หรือกฎที่กำหนดเป้าหมายเวอร์ชันภายใน /classes.jar ใต้ /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ จะถูกสกัดและรวมเข้ากับการตั้งค่า R8 ของแอปโฮสต์โดยอัตโนมัติในขณะคอมไพล์
Java Archive (.jar) พร้อมกฎที่รวมมาด้วย /META-INF/proguard/*.pro หรือ /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ (รวมถึงเวอร์ชัน r8, r8-from-<X>, และ r8-upto-<Y>) AGP และ R8 จะตรวจพบและนำไปใช้โดยอัตโนมัติตามเวอร์ชันของ R8 compiler ที่ใช้งาน
Standalone .jar หรือ .aar ที่ไม่มีกฎรวมมาด้วย ไม่มีกฎอยู่ในไฟล์ Archive ไม่มีการป้องกันจาก Consumer rule ดังนั้นให้เพิ่มกฎ Keep ในระดับโฮสต์เฉพาะเมื่อจำเป็นต้องรักษาค่าสัญลักษณ์ที่เข้าถึงแบบไดนามิกผ่าน Reflection, JNI, การทำ Serialization เป็นต้น

(หมายเหตุเกี่ยวกับกฎ Gson Serialization: ตามที่บันทึกไว้ใน Gson Troubleshooting Guide สำหรับ ProGuard และ R8, Gson เวอร์ชัน 2.11.0 ขึ้นไปจะรวมกฎ R8 สำหรับ TypeToken และฟิลด์ที่ทำ Annotation ด้วย @SerializedName ไว้ให้ กฎเหล่านี้ไม่ได้ช่วยยกเลิกความจำเป็นในการจำกัดการเข้าถึงผ่าน Reflection สำหรับโมเดล, การจัดเตรียม Constructor ที่ไม่มีอาร์กิวเมนต์ที่เข้ากันได้, หรือการทดสอบเส้นทางการ Serialization และ Deserialization ทั้งหมดใน Minified release builds)

การตรวจสอบ SDK Archive ที่ดาวน์โหลดมาก่อนเริ่มเขียนกฎ

การจับคู่กฎ ProGuard และ R8 นั้นไวต่อตัวอักษรพิมพ์เล็ก-ใหญ่และเส้นทางแพ็คเกจอย่างเคร่งครัด สำหรับ SDK เวอร์ชันต่างๆ หรือรูปแบบการกระจายในแต่ละภูมิภาค Namespace ของแพ็คเกจอาจแตกต่างกัน หากคุณคัดลอกกฎจากบทเรียนเก่าๆ ที่การพิมพ์ตัวอักษรของแพ็คเกจไม่ตรงกับไฟล์ .class จริงใน Archive ของคุณ R8 จะจับคู่คลาสได้เป็นศูนย์

ก่อนแก้ไขกฎ Keep ของคุณ ให้ดาวน์โหลดแพ็คเกจ SDK เป้าหมาย (เช่น Android SDK v2.1.0 จาก ศูนย์ดาวน์โหลด Mobile SDK) และตรวจสอบกฎที่ฝังอยู่ (ทั้งใน /proguard.txt และภายใน classes.jar) พร้อมกับลำดับชั้นไฟล์ .class ที่แท้จริง:

# 1. ตรวจสอบ .aar archive สำหรับกฎ consumer root (proguard.txt) และสกัด classes.jar เพื่อตรวจสอบกฎใน META-INF
unzip -l opoinstall-android-sdk.aar
unzip -p opoinstall-android-sdk.aar proguard.txt
unzip -p opoinstall-android-sdk.aar classes.jar > temp_classes.jar
jar tf temp_classes.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

# 2. หากใช้ Standalone .jar archive ให้ตรวจสอบกฎ META-INF ที่ฝังอยู่และเส้นทาง .class ที่เป็นตัวพิมพ์เล็ก-ใหญ่ที่แท้จริง
jar tf libs/OpoInstall_SDK.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"

การกำหนดค่า proguard-rules.pro สำหรับ Attribution Android SDKs

หลีกเลี่ยง Anti-patterns การใช้ Keep กฎแบบกว้างและการกดปิดคำเตือนแบบเหมาเข่ง

เมื่อเขียนกฎเอง วิศวกรต้องปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดของ Google ในหัวข้อ การเลือกว่าจะเก็บอะไรไว้ (R8 Keep Rules) เพื่อหลีกเลี่ยงการปิดการปรับแต่งโดยไม่จำเป็น:

  • เลือกใช้กฎแบบเจาะจงมากกว่า Anti-pattern แบบแพ็คเกจกว้างๆ เช่น -keep class package.** { *; }: กฎแบบครอบคลุมทั้งแพ็คเกจจะบังคับให้ R8 ต้องเก็บและข้ามการปรับแต่งทุกคลาสตัวช่วยภายในแพ็คเกจนั้น ให้กำหนดกฎของคุณให้เจาะจงเฉพาะคลาส, Constructor (<init>();), Callback interfaces, เป้าหมาย JNI และฟิลด์โมเดลที่เข้าถึงแบบไดนามิกขณะรันไทม์ (หรือใช้ @UnconditionallyKeep annotation ของ Jetpack ในโค้ดที่คุณควบคุม)
  • แยกความแตกต่างระหว่าง -keep และ -keepclassmembers บน Data Models: -keepclassmembers จะเก็บรักษาเฉพาะฟิลด์หรือเมธอดที่ระบุไว้ *ถ้า* คลาสที่มีข้อมูลเหล่านั้นถูกเก็บไว้ใน Call graph หากโมเดลข้อมูล (เช่น OpoData) ถูกสร้างขึ้นผ่าน Reflection เท่านั้น หรืออ้างอิงผ่าน Generic types ที่ถูกทำ Type erasure ให้ใช้ -keep class ... เพื่อไม่ให้ R8 ลบคลาสออก
  • ทำความเข้าใจข้อจำกัดของ -keepclasseswithmembernames สำหรับ JNI: กฎการปรับแต่งมาตรฐานของ Android รวมกฎ -keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; } ไว้ ตามนิยามในเอกสาร R8 Keep Rules ของ Google และ ProGuard Manual ว่าด้วยกฎ Keep, -keepclasseswithmembernames มีค่าเท่ากับ -keepclasseswithmembers,allowshrinking ซึ่งเป็นการรักษาสมาชิกของคลาสและชื่อที่ตรงกันหากพวกมันรอดพ้นจากขั้นตอนการ Shrinking แต่ ไม่ ได้ป้องกันไม่ให้คลาสหรือเมธอด native ที่ไม่มีการอ้างอิงถูกลบออกในขั้นตอนการ Shrinking หากเมธอด native ถูกลงทะเบียนแบบไดนามิกผ่าน RegisterNatives โดยไม่มีจุดเรียกใช้งานโดยตรงใน Java หรือหากโค้ด C/C++ เรียกใช้เมธอด Java ผ่าน JNI upcalls คุณต้องใช้กฎ -keep หรือ -keepclassmembers ที่ชัดเจนตามวิธีการที่คลาสเป้าหมายถูกสร้างขึ้น
  • หลีกเลี่ยงคำสั่ง -dontwarn แบบเหมาเข่งที่ไม่ได้ตรวจสอบ: -dontwarn ไม่ได้ให้การป้องกันใดๆ จากการ Shrinking หรือการเปลี่ยนชื่อ แต่เป็นการระงับคำเตือนเกี่ยวกับอ้างอิงที่แก้ไขไม่ได้ ซึ่งอาจซ่อนความผิดพลาดรันไทม์จำพวก NoClassDefFoundError ให้เพิ่ม -dontwarn แบบเฉพาะเจาะจงก็ต่อเมื่อ Log การ Build ของคุณรายงานถึงคลาส compile-only ที่หายไปและคุณได้ตรวจสอบแล้วว่าคลาสนั้นเป็นทางเลือกขณะรันไทม์
คำสั่ง ProGuard / R8 ไวยากรณ์รูปแบบยาว การ Shrinking (การลบ) การ Obfuscation (การเปลี่ยนชื่อ) ขอบเขตที่แนะนำสำหรับวิศวกร
-keep class ... -keep ถูกป้องกัน (คลาสและสมาชิกที่ระบุถูกเก็บไว้) ถูกป้องกัน (คงชื่อเดิมไว้) จุดเชื่อมต่อ SDK สาธารณะ, Callback interfaces, และคลาสที่ถูกสร้างขึ้นไดนามิกผ่าน Reflection หรือ JNI (รวม <init>(...);)
-keepclassmembers class ... -keepclassmembers เก็บสมาชิกไว้ เฉพาะเมื่อ คลาสถูกเก็บไว้ ป้องกันการเปลี่ยนชื่อสมาชิกที่ตรงกัน คงไว้ซึ่ง <fields>; หรือเมธอด JNI upcall บนคลาสที่ถูกสร้างออบเจกต์โดยตรงใน Java/Kotlin อยู่แล้ว
-keepclasseswithmembernames -keepclasseswithmembers,allowshrinking อนุญาตหากไม่มีการอ้างอิง (allowshrinking) ถูกป้องกันหากคลาสและสมาชิกอยู่รอดจาก Shrinking การคงชื่อสำหรับการทำ JNI downcall มาตรฐาน (native <methods>;) เมื่อโค้ด Java เรียกใช้เมธอด native โดยตรง
-keepnames class ... -keep,allowshrinking อนุญาตหากไม่มีการอ้างอิง (allowshrinking) ป้องกันการเปลี่ยนชื่อหากคลาสถูกเก็บไว้ การคงชื่อคลาสที่อ่านได้สำหรับ Stack traces โดยไม่บังคับให้คลาสที่ไม่ได้ใช้งานรวมอยู่ใน classes.dex
-keepattributes ... -keepattributes เก็บ Metadata บนรายการที่ถูกเก็บไว้ N/A (Bytecode attribute) คงไว้ซึ่ง Signature (generics), InnerClasses, EnclosingMethod, และ *Annotation* บนคลาสที่ระบุให้เก็บไว้อย่างชัดเจน
-dontwarn <specific_class> -dontwarn ไม่มีผลต่อการ Shrinking ไม่มีผลต่อการเปลี่ยนชื่อ ระงับคำเตือนขณะคอมไพล์ เฉพาะ สำหรับ Dependency แบบ compile-only ที่ได้รับการตรวจสอบแล้วว่าเป็นทางเลือก

การกำหนดค่า Gradle ทั่วทั้ง AGP 8.x และ AGP 9.3+ Toolchains

Android Gradle Plugin 9.3 ได้เปิดตัวบล็อก optimization DSL แบบย่อและไดเร็กทอรี keepRules (ไฟล์ .keep) เฉพาะ variant ขณะที่ AGP 8.x ถึง 9.2 (และคอนฟิกแบบเดิมที่ยังรองรับใน AGP 9.3+) ใช้ isMinifyEnabled และ proguardFiles วิศวกรควรแยกออบเจกต์คอนฟิกทั้งสามประเภทนี้ให้ชัดเจน:

Configuration Object ตำแหน่งของไฟล์หรือ DSL บทบาททางสถาปัตยกรรมในการ Build ด้วย R8
Legacy DSL Keep Rules (AGP 8.x–9.2 & Legacy 9.3+) app/proguard-rules.pro (อ้างอิงผ่าน proguardFiles(...)) ประกาศกฎ Keep ของโฮสต์ควบคู่ไปกับ getDefaultProguardFile("proguard-android-optimize.txt")
New Optimization DSL Keep Rules (AGP 9.3+) app/src/main/keepRules/*.keep หรือ app/src/<variant>/keepRules/*.keep ประกาศไฟล์ .keep เมื่อใช้ optimization { enable = true } (กฎแพลตฟอร์ม Android เริ่มต้นจะถูกนำไปใช้โดยอัตโนมัติ; Legacy DSL ยังรองรับ .keep source sets)
R8 Diagnostic Report Directives -printseeds, -printusage, -printconfiguration ในไฟล์การตั้งค่า R8 ที่ตรวจสอบแล้ว (proguard-rules.pro) สั่งให้ R8 ออกรายงาน Audit ไปยัง app/build/outputs/mapping/<buildVariant>/
// ตัวเลือก A: AGP 8.x - 9.2 Legacy DSL (รองรับใน AGP 9.3+ ด้วย) — app/build.gradle.kts
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

// ตัวเลือก B: AGP 9.3+ Simplified Optimization DSL — app/build.gradle.kts
// วางกฎ Keep คลาส/สมาชิกไว้ที่ app/src/main/keepRules/sdk-attribution.keep
android {
    buildTypes {
        release {
            optimization {
                enable = true
            }
        }
    }
}

ตัวอย่าง Scoped Keep Rules และคำสั่ง Diagnostic

เนื่องจากเอกสารสาธารณะและหน้า FAQ เก่าของ OpoInstall มีการอ้างถึงคำนำหน้าแพ็คเกจที่แตกต่างกัน (com.opoinstall.api.** เทียบกับ com.fm.OpoInstall.** หรือ io.OpoInstall.**) กฎที่แสดงด้านล่างนี้จึงเป็นเพียง ภาพตัวอย่างเท่านั้น ไม่ใช่กฎสำหรับการใช้งานจริงที่ยืนยันแล้วสำหรับ OpoInstall SDK v2.1.0 ให้ตรวจสอบเส้นทางคลาสที่แท้จริงและกฎผู้ใช้งานที่ฝังอยู่ใน Archive เวอร์ชัน v2.1.0 ของคุณก่อน และอ้างอิงเอกสารทางเทคนิคของ OpoInstall (ดูบทความ Software Development Kits มีส่วนขับเคลื่อนการทำ Attribution และการเติบโตของมือถืออย่างไร สำหรับโค้ด Java lifecycle เบื้องหลัง):

# ไฟล์: app/src/main/keepRules/sdk-attribution.keep (หรือ app/proguard-rules.pro)
# สำคัญ: เป็นเพียงตัวอย่างเท่านั้น ไม่ใช่ชุดกฎการใช้งานจริงสำหรับ OpoInstall SDK v2.1.0
# ตรวจสอบเส้นทางคลาสและแพ็คเกจแบบตรงตัวอักษรพิมพ์เล็ก-ใหญ่ภายใน Archive v2.1.0 ที่คุณดาวน์โหลดมา
# โดยใช้ `jar tf` หรือ `apkanalyzer` และตรวจสอบ `configuration.txt` สำหรับกฎผู้ใช้งานเดิม

# ---------------------------------------------------------------------------
# 1. เก็บ Bytecode Attributes ไว้บนคลาสที่สั่ง Keep โดยชัดเจน
# จำเป็นเพื่อให้ Callback interface แบบ Generic (เช่น ResultCallBack<OpoData>)
# ยังคงรักษา Metadata ของ Parameterized type ไว้ใน R8 Full Mode
# ---------------------------------------------------------------------------
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*

# ---------------------------------------------------------------------------
# 2. กฎสำหรับจุดเริ่มต้น SDK, Callback interface และ Data Model
# ---------------------------------------------------------------------------

# เก็บจุดเชื่อมต่อ Public SDK และ Constructor/เมธอดที่ถูกเรียกด้วย Reflection:
-keep public class com.opoinstall.api.OpoInstall {
    <init>(...);
    public <methods>;
}

# เก็บอินเทอร์เฟซ Callback listener และเมธอด Callback ของมัน:
-keep public interface com.opoinstall.api.ResultCallBack {
    public <methods>;
}

# สถานการณ์โมเดล A: หากโมเดล OpoData / OpoError ถูกสร้างแบบไดนามิกผ่าน Reflection
# หรืออ้างอิงเฉพาะผ่าน Generic ที่ถูกทำ Type erasure ให้ใช้ -keep เพื่อเก็บทั้งคลาสและสมาชิก:
-keep class com.opoinstall.api.OpoData {
    <init>(...);
    <fields>;
    public <methods>;
}
-keep class com.opoinstall.api.OpoError {
    <init>(...);
    <fields>;
    public <methods>;
}

# สถานการณ์โมเดล B: หากคลาสโมเดลถูกอ้างอิงใน Call graph ของคุณอยู่แล้ว
# และต้องการปกป้องเฉพาะฟิลด์จากการเปลี่ยนชื่อ ให้ใช้ -keepclassmembers:
# -keepclassmembers class com.opoinstall.api.OpoData { <fields>; }

# ---------------------------------------------------------------------------
# 3. การปกป้อง JNI Downcall และ Native-to-Java Upcall (รูปแบบ JNI ตัวอย่าง)
# ---------------------------------------------------------------------------
# กรณี JNI A: หากโค้ด Java/Kotlin สร้างออบเจกต์คลาส JNI bridge โดยตรงอยู่แล้ว
# ให้ใช้ -keepclassmembers,includedescriptorclasses สำหรับเมธอด C/C++ upcall และคงไว้ซึ่ง
# คลาสโมเดลพารามิเตอร์/Constructor ที่ถูกสร้างโดยโค้ด native:
-keepclassmembers,includedescriptorclasses class com.example.sdk.internal.JniBridge {
    public void onNativeAttributionEvent(com.example.sdk.internal.NativeEventData);
}
-keep class com.example.sdk.internal.NativeEventData {
    <init>(...);
}

# กรณี JNI B: หากโค้ด native C/C++ มีการค้นหาหรือสร้างคลาส Java แบบไดนามิก
# ผ่าน FindClass / NewObject หรือลงทะเบียนเมธอด native ผ่าน RegisterNatives
# โดยไม่มีจุดเรียกใช้งานโดยตรงใน Java ให้ใช้ -keep เพื่อป้องกันการลบคลาสและเมธอด:
-keep,includedescriptorclasses class com.example.sdk.internal.DynamicNativeBridge {
    <init>(...);
    public native <methods>;
    public void onNativeCallback(java.lang.String);
}
# ไฟล์: app/proguard-rules.pro (คำสั่งสำหรับ Diagnostic Output)
# วางแฟล็กรายงาน R8 diagnostic ไว้ในไฟล์คอนฟิก R8 ที่ตรวจสอบแล้วของคุณ
# เส้นทางจะอ้างอิงจากไดเร็กทอรีทำงานของโมดูล (`app/`)
# ปรับชื่อไดเร็กทอรีสำหรับ Product flavors ตามความเหมาะสม (เช่น build/outputs/mapping/prodRelease/)
-printseeds build/outputs/mapping/release/seeds.txt
-printusage build/outputs/mapping/release/usage.txt
-printconfiguration build/outputs/mapping/release/configuration.txt

การแก้ไขปัญหาความขัดแย้งของ Obfuscation และการทำ Hardening ใน Android Channel Packages

การแยกขั้นตอน R8 Compilation, APK Alignment, การเซ็นลายเซ็น และ Channel Packages

สำหรับการแจกจ่าย Android นอก Google Play ตัว OpoInstall รองรับการส่งออก Android Channel Packages แบบเฉพาะเจาะจง โดยจะนำ Base APK ที่นักพัฒนาอัปโหลดขึ้นมา แล้วสร้าง Channel-specific APKs ที่มี Metadata การติดตาม channelCode ที่แยกต่างหาก

เมื่อแก้ไขปัญหาสถิติของ Channel ที่หายไปหรือความล้มเหลวในการติดตั้งใน Channel packages ที่ส่งออก วิศวกรต้องแยกการตรวจสอบความถูกต้องของ Release สองเกณฑ์อิสระออกจากกัน ได้แก่ ความถูกต้องของลายเซ็น APK แบบเข้ารหัส (Cryptographic APK signature) และ ความสมบูรณ์ของ Metadata ของช่องทางจำหน่าย (Vendor channel metadata):

  1. Build-Time R8 Code Shrinking: R8 จะทำงานภายในขั้นตอน Gradle build pipeline เมื่อคอมไพล์ Java/Kotlin bytecode เป็น classes.dex *ก่อนที่* Base APK จะถูกแพ็คและเซ็นชื่อ R8 จะไม่ทำงานบน APK ที่ส่งออกไปแล้ว
  2. Android APK Alignment (zipalign) และการเซ็นชื่อทางรหัส (apksigner): APK สำหรับ Release ทั่วไปที่สร้างโดย AGP จะถูกจัดเรียงโดยอัตโนมัติ อย่างไรก็ตาม หาก Pipeline ของคุณมีการทำ Post-processing หรือการเสริมความแข็งแกร่ง (Reinforcement) ของบุคคลที่สามก่อนทำการเซ็นชื่อ คุณต้องทำการจัดเรียง APK ใหม่ตาม ข้อมูลจำเพาะของ Android zipalign และ ข้อมูลจำเพาะของ Android apksigner zipalign จะจัดเรียงข้อมูลที่ไม่ได้บีบอัดบนขอบเขต 4-byte และหาก APK มีไฟล์ Native .so ที่ไม่บีบอัด ทีมงานควรใช้ zipalign -P 16 -f -v 4 infile.apk outfile.apk เพื่อให้เป็นไปตามข้อกำหนดการจัดเรียงหน้า 16 KB และตรวจสอบการจัดเรียงด้วย zipalign -c -P 16 -v 4 โดย zipalign ต้อง ถูกเรียกใช้งานก่อนการเซ็นชื่อด้วย apksigner เสมอ เพราะการแก้ไข APK หลังจากเซ็นชื่อจะทำให้ลายเซ็นเข้ารหัสแบบ V2/V3 ทั้ง APK นั้นเป็นโมฆะ
  3. การตรวจสอบ Metadata และลายเซ็นของ Channel เฉพาะผู้จำหน่าย: การเรียกใช้เครื่องมือเสริมความปลอดภัย APK (Reinforcement/Packing) หรือการ Re-packaging หลังจาก ส่งออก Channel APK จากคอนโซล OpoInstall อาจทำลาย Metadata ของ Channel ที่ฝังอยู่หรือทำให้ลายเซ็น APK เป็นโมฆะ หาก Parser ของ Channel-package ของบุคคลที่สามต้องการรูปแบบลายเซ็น APK เฉพาะ (เช่น การเปิดใช้งาน V1 JAR signing ควบคู่ไปกับ V2/V3 full-APK signing) ให้ตรวจสอบข้อกำหนดนั้นโดยตรงกับเอกสารประกอบในคอนโซล OpoInstall หรือฝ่ายสนับสนุนของผู้จำหน่ายก่อนทำการแก้ไขค่าคอนฟิกการเซ็นชื่อ และอย่าถือว่าการเซ็นชื่อแบบ V1 เป็นข้อกำหนดสากลของระบบปฏิบัติการ Android

ลำดับของ Release Pipeline ที่แนะนำสำหรับ Android Channel Packages

เพื่อรักษาลายเซ็น APK ที่ถูกต้อง, การปรับแต่ง Bytecode ด้วย R8 และ Metadata ของช่องทางจำหน่ายที่ครบถ้วน ให้ดำเนินการตามลำดับขั้นตอนนี้ใน Release pipeline ของคุณ:

  1. Compile และ Minify Base APK ด้วย R8: เรียกใช้ ./gradlew :app:assembleRelease พร้อมกฎ Keep ที่ตรวจสอบแล้วเพื่อให้ R8 ปรับแต่ง classes.dex ในขั้นตอนการ Build
  2. ดำเนินการ Pre-upload Hardening, Alignment และเซ็นชื่อให้เสร็จสิ้น: หากทีมรักษาความปลอดภัยใช้เครื่องมือเสริมความแข็งแกร่งของบุคคลที่สาม ให้ใช้กับ Base APK, เรียก zipalign -P 16 -f -v 4 (หากต้องจัดเรียงใหม่ก่อนเซ็นชื่อ), ตรวจสอบการจัดเรียงด้วย zipalign -c -P 16 -v 4, แล้วจึงเซ็นชื่อ Base APK นั้น
  3. อัปโหลด Base APK และตรวจสอบ Channel APKs ที่ส่งออกอย่างอิสระ: อัปโหลด Base APK ที่เซ็นชื่อและ Obfuscate เรียบร้อยแล้วไปยังคอนโซล OpoInstall, ส่งออก Channel APKs และตรวจสอบทั้ง ความถูกต้องของลายเซ็น (apksigner verify --verbose) และการระบุแหล่งที่มาของช่องทางจำหน่ายบนอุปกรณ์จริงก่อนที่จะแจกจ่ายสู่สาธารณะ
[Recommended Channel Package Pipeline]:
Source Code -> Gradle R8 Minification -> (Optional Pre-Upload Hardening + zipalign -P 16 + Signing) -> Upload Base APK to Console -> Export Channel APK -> Verify Both Signature (apksigner) & Channel Callback

การตรวจสอบสัญลักษณ์ SDK ที่ถูกเก็บรักษาโดยใช้รายงาน R8 และ APK Analyzer

การใช้ Standalone R8 Configuration Analyzer ใน AGP 9.3+

ใน Android Gradle Plugin 9.3 ขึ้นไป คุณไม่จำเป็นต้องรอให้คอมไพล์ไฟล์ Release APK หรือ App Bundle ทั้งหมดเพื่อตรวจสอบว่ากฎการเก็บรักษาของคุณผสานและจับคู่กันอย่างไร AGP 9.3+ มีงาน Gradle เฉพาะชื่อ analyze<Variant>R8Config (เช่น ./gradlew :app:analyzeReleaseR8Config) ซึ่งจะประเมินกฎการเก็บรักษาของผู้ใช้งานและของโฮสต์อย่างรวดเร็ว และแสดงผล Diagnostic เพื่อให้วิศวกรสามารถปรับปรุงกฎ .keep ได้ก่อนที่จะเริ่มการ Build รุ่น Release จริง

การตรวจสอบไขว้ระหว่าง configuration.txt, seeds.txt, usage.txt และ mapping.txt

รายงาน R8 และการตรวจสอบ DEX แบบ Raw ยืนยันความสมบูรณ์ของ Channel APK ในรุ่น Release

อย่าทึกทักเอาเองว่ากฎ -keep ทำงานได้เพียงเพราะ ./gradlew :app:assembleRelease ทำงานเสร็จโดยไม่มีข้อผิดพลาด ในการตรวจสอบการตั้งค่าของคุณอย่างละเอียดถี่ถ้วน ให้ตรวจสอบไฟล์ Diagnostic ของ R8 ทั้ง 4 ไฟล์ที่สร้างขึ้นใน app/build/outputs/mapping/<buildVariant>/ (ใช้ -whyareyoukeeping class <ClassName> เมื่อดีบั๊กหาสาเหตุว่าทำไมสัญลักษณ์เฉพาะจึงถูกเก็บไว้):

  • configuration.txt: บรรจุชุดกฎที่สมบูรณ์ซึ่งถูกนำไปใช้โดย R8 โดยรวมเอาทั้งกฎ Keep ของโฮสต์, กฎมาตรฐานของแพลตฟอร์ม Android, กฎ AAPT2 และกฎ /proguard.txt หรือ /META-INF/ ที่ฝังอยู่ใน Library .aar และ .jar ที่เชื่อมโยงอยู่
  • seeds.txt: แสดงรายการคลาส, Constructor (<init>()), เมธอด และฟิลด์ทั้งหมดที่ตรงกับกฎ -keep ที่ใช้งานอยู่ โปรดทราบว่าการปรากฏใน seeds.txt เป็นเพียงการยืนยันไวยากรณ์ของกฎ แต่ต้องนำไปตรวจสอบร่วมกับการตรวจสอบ DEX และการทดสอบรันไทม์
  • usage.txt: แสดงรายการโค้ดที่ตายแล้ว (Dead code) ซึ่ง R8 ตัดออกจาก APK ในขั้นตอน Tree-shaking ตรวจสอบให้แน่ใจว่าไม่มีคลาส SDK, Constructor ที่เรียกแบบ Reflection, หรือเมธอด JNI upcall ใดๆ ที่คุณต้องการ ปรากฏอยู่ใน usage.txt
  • mapping.txt: บันทึกแผนผังการแปลตัวระบุจากชื่อก่อนและหลัง Obfuscation สำหรับคลาสและสมาชิกที่ถูกเก็บไว้ โปรดทราบว่าการที่ไม่มีชื่อปรากฏใน mapping.txt ไม่ได้พิสูจน์ว่าคลาสนั้นถูกเก็บไว้ในชื่อเดิม มันอาจถูกลบออกไปโดยสิ้นเชิงในขั้นตอน Shrinking (และไปปรากฏใน usage.txt แทน) ให้ตรวจสอบ mapping.txt เทียบกับ seeds.txt, usage.txt และไฟล์ DEX ที่คอมไพล์แล้วเสมอ

การตรวจสอบตัวระบุ DEX แบบ Raw เทียบกับสถานะรายงาน Deobfuscated ด้วย apkanalyzer

เมื่อใช้เครื่องมืออย่างเป็นทางการของ Google อย่าง Google Android Developers APK Analyzer CLI เพื่อตรวจสอบ APK รุ่น Release ของคุณ ให้แยกแยะระหว่าง ตัวระบุ DEX ที่คอมไพล์แบบ Raw และ ชื่อที่ถูก Deobfuscate ด้วย Mapping:

  • การตรวจสอบชื่อ DEX แบบ Raw (โดยไม่ใช้ --proguard-mappings): เมื่อคุณส่งอาร์กิวเมนต์ --proguard-mappings mapping.txt ไปยัง apkanalyzer เครื่องมือจะแปลชื่อ DEX ที่ถูก Obfuscate (เช่น a.b.c) กลับเป็นชื่อต้นฉบับสำหรับการแสดงผล ดังนั้นการเห็นชื่อคลาสต้นฉบับเมื่อเปิดใช้งาน --proguard-mappings ไม่ได้ พิสูจน์ว่าสัญลักษณ์นั้นรอดพ้นจากการเปลี่ยนชื่อใน classes.dex ให้เรียกใช้ apkanalyzer dex packages โดยไม่ใช้ --proguard-mappings ก่อน เนื่องจากกระบวนการ repackaging ของ R8 อาจย้ายคลาสไปสู่โครงสร้างแพ็คเกจใหม่ อย่าพึ่งพาการ grep แพ็คเกจเพียงอย่างเดียว ให้ตรวจสอบโครงสร้างคลาสใน DEX โดยตรง, ยืนยันชื่อคลาสในจุดเชื่อมต่อ Reflection, ตรวจสอบชื่อเมธอด JNI และ Descriptor signature พร้อมตรวจสอบเทียบกับ mapping.txt เพื่อยืนยันว่าเมธอดและฟิลด์ที่จำเป็นไม่ได้ถูกเปลี่ยนชื่อ
  • การตรวจสอบสถานะการเก็บรักษาของ R8 (โดยใช้ --proguard-mappings, --proguard-seeds และ --proguard-usages): ในรอบที่สอง ให้ส่งไฟล์รายงาน R8 ทั้งหมดเพื่อสร้างสถานะการเก็บรักษา (Flag: k สำหรับถูกเก็บรักษาโดย Seed rule, x สำหรับถูกลบระหว่าง Shrinking, r สำหรับมีการอ้างอิง, และ d สำหรับมีการกำหนดไว้) ในทุกแพ็คเกจ (P), คลาส (C), เมธอด (M) และฟิลด์ (F):
# เรียกใช้จากไดเร็กทอรีรากของโปรเจกต์ Android (ปรับเส้นทาง variant สำหรับผลิตภัณฑ์, เช่น prodRelease)

# 1. (AGP 9.3+) รันเครื่องมือวิเคราะห์ R8 configuration เพื่อตรวจสอบกฎเบื้องต้น จากนั้น build release APK
./gradlew :app:analyzeReleaseR8Config
./gradlew :app:assembleRelease

# 2. ตรวจสอบการตั้งค่า R8 ที่ผสานแล้ว, seeds ที่ตรงกัน, และรายงาน usage ที่ถูกตัดออก
grep -i -C 3 "opoinstall" app/build/outputs/mapping/release/configuration.txt
grep -i "opoinstall" app/build/outputs/mapping/release/seeds.txt
grep -i "opoinstall" app/build/outputs/mapping/release/usage.txt

# 3A. ตรวจสอบตัวระบุ DEX ที่คอมไพล์แบบ RAW (โดยไม่ใช้ --proguard-mappings)
# ตรวจสอบโครงสร้าง DEX ทั้งหมดและตรวจสอบ mapping.txt เผื่อกรณีที่ R8 ทำการ repackage คลาส
apkanalyzer dex packages app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 3B. ตรวจสอบสถานะการเก็บรักษาของ R8 (k = kept, x = removed, r = referenced, d = defined) พร้อมไฟล์รายงาน
apkanalyzer dex packages \
  --proguard-mappings app/build/outputs/mapping/release/mapping.txt \
  --proguard-seeds app/build/outputs/mapping/release/seeds.txt \
  --proguard-usages app/build/outputs/mapping/release/usage.txt \
  app/build/outputs/apk/release/app-release.apk | grep -i "opoinstall"

# 4. ตรวจสอบการจัดเรียงหน้า 16 KB (หากมีไฟล์ .so อยู่) และลายเซ็นเข้ารหัสของ APK
zipalign -c -P 16 -v 4 app/build/outputs/apk/release/app-release.apk
apksigner verify --verbose app/build/outputs/apk/release/app-release.apk

ตารางแก้ไขปัญหาการทำ Attribution ใน Release Build

การวินิจฉัยความผิดพลาดของ Attribution และ Deep-link ที่ทำงานผิดปกติใน Android Builds ที่ Minified

เมื่อแก้ไขปัญหาความคลาดเคลื่อนของ Attribution ที่เกิดขึ้นเฉพาะใน Release builds ที่ผ่านการ Minified แล้ว ให้ใช้ตารางวินิจฉัยด้านล่างเพื่อแยกแยะสาเหตุหลัก:

อาการที่พบใน Release สาเหตุหลักของ R8 / Build ขั้นตอนการตรวจสอบ การแก้ไข
Callback getInstallParam หรือ handleDeepLink ไม่ทำงานใน Release build คลาสที่เป็นจุดเชื่อมต่อ SDK, Constructor <init>() ที่ถูกเรียกผ่าน Reflection หรือเมธอด ResultCallBack ถูกตัดออกหรือเปลี่ยนชื่อเนื่องจากความไม่ตรงกันของแพ็คเกจ ตรวจสอบ seeds.txt และ usage.txt; ตรวจสอบเอาต์พุต apkanalyzer dex packages (แบบไม่ใช้ --proguard-mappings) และ mapping.txt ตรวจสอบไฟล์ .jar / .aar ด้วย jar tf เพื่อหาเส้นทางคลาสที่ถูกต้อง (พิมพ์เล็ก-ใหญ่) และเพิ่มกฎ -keep แบบเจาะจงรวมถึง <init>(...);
Callback ทำงาน แต่พารามิเตอร์ของ OpoData กลับว่างเปล่าหรือเป็น null OpoData ถูกป้องกันด้วย -keepclassmembers เท่านั้น (ทำให้ตัวคลาสถูกตัดออกได้) หรือสูญเสีย Metadata ของ Signature ของ Generics ตรวจสอบ usage.txt และ mapping.txt สำหรับ OpoData และตรวจสอบ configuration.txt สำหรับ -keepattributes Signature ใช้ -keep class ... (หาก OpoData ถูกสร้างผ่าน Reflection) พร้อม <init>(...); <fields>; และเก็บ -keepattributes Signature ไว้
UnsatisfiedLinkError หรือ Callback หายไปเมื่อ SDK โหลด Library .so การเรียกใช้ RegisterNatives แบบไดนามิกถูก Shrink ออก หรือเมธอดเป้าหมายของการ Upcall จาก C/C++ ไป Java ถูก R8 เปลี่ยนชื่อ ตรวจสอบ Logcat เพื่อดูสัญลักษณ์ JNI ที่ขาดหาย และตรวจสอบ apkanalyzer dex packages (Raw) กับ mapping.txt ของคลาส Java เป้าหมาย ใช้ -keep (หรือ -keepclassmembers หากคลาส Bridge ถูกสร้างโดยตรงใน Java) พร้อม includedescriptorclasses บนเป้าหมาย JNI upcall และ RegisterNatives
Android channel package ที่ส่งออกไปติดตั้งไม่ได้หรือทำสถิติ Channel หาย การแก้ไข APK หลังการส่งออกทำให้ลายเซ็น APK เป็นโมฆะหรือ Metadata ของ Channel ที่ฝังไว้ถูกเขียนทับ รัน zipalign -c -P 16 -v 4 และ apksigner verify --verbose บน Channel APK ที่ส่งออก และทดสอบบนอุปกรณ์จริง ดำเนินการ Minification, Hardening, Alignment และเซ็นชื่อทุกอย่างบน Base APK ก่อน อัปโหลดไปยังคอนโซล; ตรวจสอบทั้งลายเซ็นและการทำ Attribution หลังส่งออก

คำถามที่พบบ่อย (FAQ)

ฉันจะแยก Mobile SDK ออกจากการทำ Obfuscation ด้วย Android ProGuard และ R8 ได้อย่างไร?
ขั้นตอนแรก ให้ตรวจสอบไฟล์ Archive ของคุณ ไม่ว่าจะเป็น `.aar` (`/proguard.txt` และ `classes.jar/META-INF/com.android.tools/`) หรือ `.jar` (`/META-INF/proguard/` และ `/META-INF/com.android.tools/`) และตรวจสอบ `configuration.txt` ว่ามีกฎ Consumer ProGuard ฝังมาด้วยหรือไม่ หากจำเป็นต้องใช้กฎที่เขียนเอง ให้เพิ่มกฎ `-keep` และ `-keepclassmembers` แบบเจาะจง โดยเล็งไปที่คลาสจุดเชื่อมต่อของ SDK (ให้ระวังตัวอักษรพิมพ์เล็ก-ใหญ่), Constructor ที่ทำงานผ่าน Reflection (`<init>(...);`), Callback interfaces, เป้าหมาย JNI และ `<fields>;` ของโมเดล ควบคู่ไปกับ `-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*`
ฉันจะตรวจสอบได้อย่างไรว่าคลาสของ Android SDK ถูกยกเว้นจากการทำ Obfuscation ด้วย R8 แล้ว?
เปิดใช้งาน `-printseeds`, `-printusage` และ `-printconfiguration` ในการตั้งค่า R8 ของคุณ (หรือรัน `./gradlew :app:analyzeReleaseR8Config` ใน AGP 9.3+) จากนั้นทำการ Build Release APK ตรวจสอบว่าคลาสและ Constructor ของ SDK เป้าหมายปรากฏอยู่ใน `seeds.txt` และไม่ปรากฏใน `usage.txt` ขั้นถัดมา ให้รัน `apkanalyzer dex packages app-release.apk` **โดยไม่ใช้** `--proguard-mappings` แล้วตรวจสอบเทียบกับ `mapping.txt` เพื่อยืนยันว่าสัญลักษณ์ DEX ที่คอมไพล์แบบ Raw ยังคงรักษาชื่อและ Signature ดั้งเดิมไว้โดยไม่ถูก Obfuscate จากนั้นให้รัน `apkanalyzer` อีกครั้งโดยเพิ่ม `--proguard-mappings`, `--proguard-seeds` และ `--proguard-usages` เพื่อตรวจสอบสถานะ Flags การเก็บรักษา (`k`, `x`, `r`, `d`)
เหตุใดสถิติของ Android channel package จึงหยุดทำงานหลังจากผ่านการ Hardening หรือขั้นตอนหลังการประมวลผล APK?
เมื่อคุณส่งออก Android channel package จากคอนโซล Attribution ข้อมูล Metadata สำหรับระบุช่องทางจำหน่ายจะถูกฝังลงไปใน APK ที่ส่งออก หากคุณรันเครื่องมือเสริมความแข็งแกร่ง (Reinforcement), การ Packing, หรือเครื่องมือเซ็นลายเซ็นใหม่บน Channel APK นั้น ขั้นตอนหลังการประมวลผลอาจทำลายลายเซ็นเข้ารหัสของ APK หรือเขียนทับ Metadata ของ Channel ที่ฝังอยู่โดยผู้จำหน่าย ควรดำเนินการขั้นตอนการทำ R8 Minification, Hardening, `zipalign` และการเซ็นลายเซ็นบน Base APK ของคุณให้เสร็จสมบูรณ์ *ก่อน* ที่จะอัปโหลดไปยังคอนโซล และตรวจสอบ Channel APK ที่ส่งออกมาด้วยคำสั่ง `apksigner verify --verbose`

สรุปและรายการตรวจสอบ (Checklist) สำหรับ Release Build

การเข้าใจวิธีแยก Mobile SDK ออกจากการทำ Obfuscation ของ Android ProGuard และ R8 เป็นสิ่งสำคัญอย่างยิ่งในการป้องกันความล้มเหลวของ Attribution โดยไม่ทราบสาเหตุเมื่อเปลี่ยนจากการทดสอบแบบ Debug ไปสู่รุ่น Release ด้วยการตรวจสอบกฎที่รวมมากับ Library (Consumer rules), การเขียนกฎ -keep แบบเจาะจงที่ตรงกับเส้นทางคลาสที่ถูกต้องใน SDK Archive ของคุณ, การเคารพลำดับการ Build สำหรับ Android channel packages และการตรวจสอบไฟล์ Diagnostic ของ R8 เทียบกับ Raw DEX bytecode วิศวกร Android จะสามารถรักษาการทำ Code shrinking ได้อย่างเต็มที่โดยไม่ส่งผลกระทบต่อฟังก์ชัน Deferred deep linking หรือการกู้คืนพารามิเตอร์

ก่อนจะเผยแพร่ Android release build ที่ทำ Minified ไปยัง Google Play หรือส่งออก Channel packages ให้ดำเนินการตามรายการตรวจสอบ 4 ขั้นตอนนี้:

  • ตรวจสอบ SDK Archive และการตั้งค่าที่ผสานรวม: ตรวจสอบ SDK Archive ที่ดาวน์โหลดมา (เช่น v2.1.0) ด้วย jar tf หรือ unzip -l เพื่อดูเส้นทางคลาสและกฎของผู้ใช้งาน และรัน ./gradlew :app:analyzeReleaseR8Config (ใน AGP 9.3+) หรือทบทวนไฟล์ configuration.txt

  • ตรวจสอบรายงาน R8 และตัวระบุ DEX แบบ Raw: ยืนยันว่าจุดเชื่อมต่อ SDK ที่จำเป็น, Constructor <init>() ที่เรียกแบบ Reflection, ฟิลด์โมเดล OpoData, เมธอด ResultCallBack และ JNI signatures ปรากฏใน seeds.txt, ไม่มีใน usage.txt, และคงชื่อเดิมไว้ในเอาต์พุต Raw apkanalyzer dex packages (รันโดยไม่ใช้ --proguard-mappings) รวมถึงใน mapping.txt

  • บังคับใช้ Minification ก่อนอัปโหลดและตรวจสอบลายเซ็นของ Channel APK: ดำเนินการ R8 Minification, การทำ Hardening ของบุคคลที่สาม, zipalign (zipalign -c -P 16 -v 4) และการเซ็นลายเซ็น APK บน Base APK ให้เสร็จสิ้น ก่อนอัปโหลดเพื่อส่งออก Android channel packages และรัน apksigner verify --verbose บนแพ็คเกจที่ส่งออกเสมอ

  • ดำเนินการทดสอบ Smoke Test บนอุปกรณ์จริงสำหรับรุ่น Release: ติดตั้ง Release APK ที่เซ็นชื่อและทำ Minified แล้วบนอุปกรณ์จริง และตรวจสอบทั้งการติดตั้งพารามิเตอร์ H5 (getInstallParam) และการเรียกใช้งาน (handleDeepLink)

ทีมวิศวกรสามารถลงทะเบียนแอปพลิเคชันและอัปโหลด Base APK ที่ทำ Obfuscation แล้วบน OpoInstall developer console เพื่อทดสอบการส่งออก Channel-package และตรวจสอบการกู้คืนพารามิเตอร์ในรุ่น Release

เอกสารอ้างอิงที่เกี่ยวข้อง

Share this article

Keep Discovering

ข้อเสนอ Safe Browsing บน Chrome Incognito? สิ่งที่โค้ดเปิดเผย

ข้อเสนอ Safe Browsing บน Chrome Incognito? สิ่งที่โค้ดเปิดเผย

การเปลี่ยนแปลงใน Chromium เสนอระบบ Safe Browsing แบบเรียลไทม์สำหรับโหมดไม่ระบุตัวตน สำรวจสิ่งที่โค้ด CL 8766497 เปิดเผย สถานะที่ถูกยกเลิก และผลกระทบต่อการเปลี่ยนเส้นทางบนมือถือ

Google ปล่อย Android 17 QPR2 แล้ว ทีมพัฒนาแอปควรทดสอบอะไรบ้าง

Google ปล่อย Android 17 QPR2 แล้ว ทีมพัฒนาแอปควรทดสอบอะไรบ้าง

Google ปล่อยอัปเดต Android 17 QPR2 สำหรับอุปกรณ์ Pixel ค้นพบสิทธิ์การติดตั้งแอปจากแหล่งภายนอก (Sideloading) แบบใหม่ การควบคุมการแจ้งเตือน และสิ่งที่ทีมพัฒนาแอปควรทดสอบ

JD ทดลองช็อปปิ้งผ่านหนังสั้น? สรุปข้อมูลที่ยืนยันได้ในขณะนี้

JD ทดลองช็อปปิ้งผ่านหนังสั้น? สรุปข้อมูลที่ยืนยันได้ในขณะนี้

JD ทดลองช็อปปิ้งผ่านหนังสั้นผ่านมินิโปรแกรมอิสระ ค้นหาข้อมูลที่ได้รับการยืนยัน สิ่งที่ยังไม่ได้รับการตรวจสอบ และความหมายต่อการเติบโตของแอปพลิเคชัน