เครื่องมือและประสิทธิภาพ

กฎการเก็บรักษาที่ปลอดภัยในการกำหนดประเภทและการเพิ่มประสิทธิภาพ DEX

ลำโพง

เกี่ยวกับ

เกี่ยวกับการเสวนา

แอป Android มักจะจัดส่งโดยไม่ได้รับการเพิ่มประสิทธิภาพอย่างเหมาะสมเนื่องจากกฎการเก็บรักษาที่เข้มงวดเกินไป ซึ่งมักมาจากไลบรารีของบุคคลที่สาม ส่งผลให้ APK มีขนาดใหญ่เกินไป, Cold Start ช้าลง และประสิทธิภาพรันไทม์ลดลง

เกณฑ์ประสิทธิภาพใหม่ของ Google Play สำหรับการเพิ่มประสิทธิภาพ DEX ทำให้ระดับการเข้าถึงของแอปขึ้นอยู่กับการเพิ่มประสิทธิภาพโดยตรง

การพูดคุยนี้จะแนะนำเวิร์กโฟลว์ R8 สมัยใหม่เพื่อจัดการกับกฎที่มากเกินไป คุณจะได้เรียนรู้วิธีต่อไปนี้

  • ใช้เครื่องมือวิเคราะห์การกำหนดค่า R8 และทักษะเพื่อระบุโอเวอร์เฮดของกฎเดิม
  • เปลี่ยนจากกฎการเก็บรักษาไวลด์การ์ดแบบกว้างไปเป็นคำอธิบายประกอบที่สร้างขึ้นซึ่งมีความแม่นยำและปลอดภัยในการกำหนดประเภท
  • ป้องกันการถดถอยของการเพิ่มประสิทธิภาพด้วยเครื่องมือ Android CLI ใหม่

สรุป

  • Google Play บังคับใช้เกณฑ์คุณภาพแอปที่กำหนดให้เพิ่มประสิทธิภาพโค้ดมากกว่า 25% การปฏิบัติตามเกณฑ์เหล่านี้จะช่วยลดการใช้งานหน่วยความจำ พร้อมทั้งปรับปรุงความเร็วในการเริ่มต้นระบบและอัตรา ANR
  • เครื่องมือวิเคราะห์การกำหนดค่า R8 จะสร้างรายงาน HTML ที่แสดงกฎการเก็บรักษาที่เก็บโค้ดไว้ โดยจะไฮไลต์กฎที่ถูกรวมและกฎที่ไม่ได้ใช้ รวมถึงวัดศักยภาพในการเพิ่มประสิทธิภาพก่อนการคอมไพล์
  • กฎการเก็บรักษาที่กว้างเกินไป ซึ่งมักใช้เพื่อป้องกันข้อขัดข้องจากการสะท้อนหรือรับค่ามาจากไลบรารี จะบั่นทอนการเพิ่มประสิทธิภาพแอป กฎไวลด์การ์ดทั้งแพ็กเกจเพียงกฎเดียวสามารถเก็บคลาสที่ไม่ได้ใช้ไว้ได้หลายสิบคลาส
  • Jetpack Keep ใช้ Lint เพื่อตรวจจับการสะท้อนและสร้างคำอธิบายประกอบที่แม่นยำและกฎการเก็บแบบมีเงื่อนไขจากโค้ดโดยตรง โดยจะส่งออกกฎสำหรับผู้ใช้ทั่วไปมาตรฐานที่เข้ากันได้กับ R8 เวอร์ชันเก่า
  • Android CLI จะตรวจสอบ App Bundle และ AAR โดยใช้ตรรกะที่แน่นอนของ Google Play โดยไม่ต้องอัปโหลด โดยจะแจ้งกฎที่มีปัญหาและส่งออกรายงาน JSON สำหรับการทดสอบการถดถอยอัตโนมัติ

แหล่งข้อมูล

Gemini Notebook

หัวข้อ

  • dex-optimization
  • r8
  • keep-rules
  • jetpack-keep
  • android-cli
  • ประสิทธิภาพของแอป

ข้อความถอดเสียง

ข้อมูลเบื้องต้นเกี่ยวกับการเพิ่มประสิทธิภาพ DEX

  • สวัสดีทุกคน

  • ฉันชื่อ Breana จากทีม Android DevRel

  • และผม Chris Craik จากทีมประสิทธิภาพของ Android

  • ยินดีต้อนรับสู่กฎการเก็บรักษาที่ปลอดภัยในการกำหนดประเภทและการเพิ่มประสิทธิภาพ DEX

  • วันนี้เราจะมาพูดถึงความหมายของการเพิ่มประสิทธิภาพ DEX เหตุผลที่คุณควรให้ความสำคัญ และข้อมูลอัปเดตล่าสุดที่น่าสนใจ

  • เป็นเวลานานแล้วที่การเพิ่มประสิทธิภาพ DEX ถูกมองว่าเป็นสิ่งที่มีก็ดี

  • คุณจึงอาจกำหนดค่าเพียงครั้งเดียวแล้วปล่อยทิ้งไว้ หรืออาจกลับมาดูอีกครั้งเมื่อมีเวลาว่าง

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

  • Google Play ได้ประกาศข้อกำหนดใหม่ด้านคุณภาพแอปพร้อมเกณฑ์ประสิทธิภาพที่จะเริ่มบังคับใช้ในเดือนกุมภาพันธ์ เพื่อรักษาระบบนิเวศของ Android ให้มีประสิทธิภาพและเพื่อให้แอปของคุณมอบประสบการณ์การใช้งานระดับพรีเมียมต่อไป

  • ซึ่งรวมถึงเกณฑ์สำหรับการเพิ่มประสิทธิภาพโค้ด DEX

  • แอปต้องลดขนาด เพิ่มประสิทธิภาพ และปรับให้ยากต่อการอ่าน (Obfuscation) มากกว่า 25%

  • แล้วสิ่งนี้มีความหมายต่อคุณในฐานะนักพัฒนาแอป Android อย่างไร

  • โค้ด DEX ที่ไม่ได้รับการเพิ่มประสิทธิภาพไม่เพียงส่งผลเสียต่อประสบการณ์ของผู้ใช้เท่านั้น แต่ยังอาจส่งผลต่อระดับการมองเห็นและความสามารถในการเผยแพร่แอปใน Play Store ด้วย

  • อย่างไรก็ตาม คุณมาถูกที่แล้ว และเมื่อจบการพูดคุยนี้ คุณจะมีเครื่องมือที่ไม่เพียงแต่ช่วยให้คุณทำตามข้อกำหนดเหล่านี้ได้ แต่ยังช่วยให้คุณทำได้เกินกว่าข้อกำหนดด้วย

  • การเพิ่มประสิทธิภาพโค้ด DEX ทำได้ด้วย R8 ซึ่งเป็นเครื่องมือเพิ่มประสิทธิภาพแอปของ Android

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

  • และแม้ว่า DEX ที่เพิ่มประสิทธิภาพมาอย่างดีและ App Bundle จะใช้หน่วยความจำน้อยลง แต่ประโยชน์หลักๆ ในที่นี้ยังคงเกี่ยวข้องกับประสิทธิภาพ ซึ่งได้แก่ ANR ที่น้อยลง การเริ่มต้นระบบที่เร็วขึ้น และประสิทธิภาพรันไทม์ที่ดีขึ้น

  • และการเพิ่มประสิทธิภาพแอปจะให้ผลลัพธ์ที่ดีต่อแอปและผู้ใช้ของคุณอย่างแท้จริงและพิสูจน์ได้

  • ตัวอย่างเช่น การใช้โหมดเต็มรูปแบบของ R8 และปรับแต่งกฎการเก็บรักษาช่วยให้ Grab เพิ่มประสิทธิภาพได้ 78% ลดขนาดได้ 79% และสร้างความสับสนได้ 82% สำหรับแอปของตน

  • คุณอาจดูสไลด์นี้แล้วคิดว่า "โอเค ตัวเลขพวกนั้นค่อนข้างสูง แต่จริงๆ แล้วหมายความว่ายังไง"

  • ผลจากการปรับปรุงเหล่านี้เมื่อรวมกับโปรไฟล์พื้นฐานทำให้บริษัทลด ANR ลงได้ 32% และปรับปรุงการเริ่มต้นแอปได้ 35%

  • เมื่อแอปยอดนิยมเริ่มใช้ R8 เป็นครั้งแรก แอปเหล่านี้เห็นว่าขนาด DEX ลดลงโดยเฉลี่ย 38% และ ANR ที่ผู้ใช้รับรู้ลดลง 16%

  • และหากคุณใช้ R8 อยู่แล้ว เรามีข่าวดีมาบอก

  • สำหรับแอปที่เปิดใช้ R8 อยู่แล้ว และดำเนินการเพื่อปรับปรุงระดับการเพิ่มประสิทธิภาพ การลดขนาด และการสร้างความสับสนให้มากกว่า 50% จะเห็นว่าขนาด DEX ลดลงอีก 25% โดยเฉลี่ย เวลาเริ่มต้นลดลง 4% และ ANR ที่ผู้ใช้รับรู้ลดลงอีก 17% ซึ่งเป็นตัวเลขที่เพิ่มขึ้นจาก 16% ที่เราแสดงในสไลด์ก่อนหน้า

  • วันนี้เราจึงอยากแนะนำวิธีที่จะช่วยให้คุณบรรลุเป้าหมายตัวเลขเหล่านี้

  • เราจะพูดถึงเครื่องมือที่ช่วยเพิ่มประสิทธิภาพโค้ด DEX และเปลี่ยนจากกฎการเก็บรักษาที่กว้างเกินไปและกำหนดค่าไม่ถูกต้องเป็นกฎที่เฉพาะเจาะจงและแคบลง

  • ก่อนอื่น เราอยากพูดถึงด้วยว่า R8 มีมานานหลายปีแล้ว

  • และเราทราบดีว่าแม้ว่าการเพิ่มประสิทธิภาพจะมีผลอย่างมาก แต่ก็ยังต้องใช้ความระมัดระวังอย่างมากในการกำหนดค่า

  • การเรียกใช้แบบสะท้อนหมายความว่าอินพุตคาดการณ์ไม่ได้ และอาจทำให้เกิดข้อขัดข้องขณะรันไทม์

  • ซึ่งทำให้ทั้งทีมแอปและทีมไลบรารีในอดีตพยายามเพิ่มกฎการเก็บรักษาที่กว้างเกินไปเพื่อความปลอดภัย

  • เราทราบดีว่า 41% ของแอปยอดนิยมมีระดับการเพิ่มประสิทธิภาพต่ำกว่า 25% ตามที่ Google Play กำหนด แต่เราก็ทราบด้วยว่า 13% ของแอปยอดนิยมมีระดับการเพิ่มประสิทธิภาพสูงกว่าเกณฑ์ดังกล่าวถึง 3 เท่า

  • ซึ่งแอปที่สามารถบรรลุเป้าหมายนี้ได้มาจากอุตสาหกรรมและเส้นทางของผู้ใช้ที่หลากหลาย

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

  • และก่อนที่เราจะเจาะลึกเกี่ยวกับเครื่องมือและการปรับปรุงใหม่ๆ ที่จะช่วยในเรื่องนี้ เรายังต้องการไฮไลต์การปรับปรุง R8 อื่นๆ อีก 2-3 รายการจากปีที่ผ่านมาซึ่งช่วยให้การเพิ่มประสิทธิภาพนี้ง่ายขึ้นเล็กน้อยด้วย

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

  • ตอนนี้เครื่องมือวิเคราะห์การกำหนดค่า R8 ซึ่งเราจะพูดถึงในอีกสักครู่รองรับ AGP เวอร์ชันเก่าแล้ว

  • และยังรองรับ Bazel อย่างไม่เป็นทางการด้วย

  • และใน AGP 9.2 โครูทีนพื้นฐานจะทำงานเร็วขึ้น 2 เท่า

  • และจากการปรับปรุง R8 ที่ทำใน AGP 9.2 ทำให้ Tinder พบว่า ANR ลดลง 13% หลังจากอัปเกรดจากเวอร์ชัน 9.0.1

  • นอกจากนี้ AGP 9.3 ยังมาพร้อมกับ R8 API ที่ออกแบบใหม่ซึ่งมีการตั้งชื่อที่ชัดเจนขึ้น ค่าเริ่มต้นที่ง่ายขึ้นสำหรับกฎเริ่มต้นและการลดขนาดทรัพยากร รวมถึงการรองรับไดเรกทอรีต้นทาง AGP สำหรับกฎการเก็บรักษา

    android {
        buildTypes {
            release {
                optimization {
                    // no need to specify default
                    // rules and resource shrinking
                    enabled = true
                }
            }
        }
        ...
    }
    android {
        buildTypes {
            release {
                // Enables code optimizations.
                isMinifyEnabled = true
    
                // Enables resource shrinking.
                isShrinkResources = true
    
                proguardFiles(
                    // Default rules.
                    getDefaultProguardFile(
                        "proguard-android-optimize.txt"
                    ),
                    ... // list out each file with keep rules
                )
                ...
            }
        }
        ...
    }
  • การอัปเดตที่เราจะพูดถึงในวันนี้ยังเป็นการปรับปรุงประสบการณ์ของนักพัฒนาแอปด้วย

  • เราจะเริ่มต้นด้วยวิธีใช้ประโยชน์จากเครื่องมือวิเคราะห์การกำหนดค่า R8 และทักษะ R8 เพื่อระบุโอเวอร์เฮดของกฎเดิมและกำหนดแนวทางในการปรับปรุง

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

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

ภาพรวมของเครื่องมือวิเคราะห์การกำหนดค่า R8

  • ดังนั้น การเริ่มต้นด้วยเครื่องมือวิเคราะห์การกำหนดค่า R8 จึงเป็นจุดเริ่มต้นที่เหมาะอย่างยิ่งสำหรับการตรวจสอบการเพิ่มประสิทธิภาพ DEX

  • หากคุณไม่เคยใช้เครื่องมือนี้มาก่อน หน้าตาของเครื่องมือจะเป็นดังนี้

  • ซึ่งเป็นรายงาน HTML ที่แสดงข้อมูลเกี่ยวกับการกำหนดค่า R8 ของคุณ

  • รายงานนี้มีไว้สำหรับแอปของฉัน ซึ่งมีไลบรารีชื่อ Analytics Bloat

  • และอย่างที่คุณคาดเดาได้ นี่ไม่ใช่แอปจริงหรือไลบรารีจริง แต่จะไฮไลต์เป็นสีน้ำเงินที่ด้านบน

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

  • และจะคำนวณหลังจากที่ R8 ทำการลดขนาดครั้งแรก

  • นอกจากนี้ ยังวัดการเพิ่มประสิทธิภาพ ซึ่งเป็นเปอร์เซ็นต์ของโค้ดที่เปิดให้เพิ่มประสิทธิภาพทั้งโปรแกรม เช่น การแทรกเมธอดและการผสานคลาส

  • และการสร้างความสับสน ซึ่งเป็นเปอร์เซ็นต์ของสมาชิกที่สามารถสร้างความสับสนหรือเปลี่ยนชื่อเป็นตัวระบุที่สั้นลงได้

  • นอกจากนี้ เราขอแจ้งให้ทราบว่าเนื่องจากเครื่องมือวิเคราะห์การกำหนดค่า R8 จะคำนวณคะแนนเหล่านี้ก่อนการเพิ่มประสิทธิภาพและการคอมไพล์ DEX ขั้นสุดท้าย ค่าที่คุณเห็นในรายงานนี้สำหรับการเพิ่มประสิทธิภาพ การลดขนาด และการปรับให้ยากต่อการอ่าน (Obfuscation) จึงจะไม่ตรงกับค่าที่คุณเห็นใน Play Console ทุกประการ

  • เนื่องจาก Google Play จะวัดเอาต์พุต DEX สุดท้าย

  • และสาเหตุของความคลาดเคลื่อนนี้ก็เพื่อให้มั่นใจว่าเมื่อคุณกำลังพัฒนา คุณจะเข้าถึงรายงานความคิดเห็นในเครื่องได้อย่างรวดเร็ว

  • แต่คะแนนที่คุณเห็นในเครื่องมือวิเคราะห์การกำหนดค่า R8 ยังคงมีประโยชน์อย่างมากในการตรวจสอบศักยภาพในการเพิ่มประสิทธิภาพ DEX ก่อนที่จะอัปโหลดไปยัง Play Store

  • นอกจากนี้ รายงานยังมีตารางการวิเคราะห์กฎการเก็บรักษา ซึ่งเป็นมุมมองหลักที่คุณเห็นเมื่อเข้าสู่รายงาน

  • จากนั้น คุณสามารถคลิกเข้าไปในกฎใดก็ได้เพื่อดูจำนวนคลาส เมธอด และฟิลด์ที่กฎนั้นๆ เก็บไว้ เพื่อให้คุณเห็นว่ากฎใดเก็บข้อมูลไว้มากกว่าที่ควร

  • นอกจากนี้ยังมีกฎที่รวมอยู่ด้วย ซึ่งเป็นกฎที่ซูมเข้าที่นี่

  • และกฎเหล่านี้เป็นกฎที่ทับซ้อนกับกฎที่กว้างกว่า

  • และระบบจะไฮไลต์คีย์เวิร์ดเหล่านี้เพื่อให้คุณนำคีย์เวิร์ดแบบกว้างออกและเก็บเฉพาะคีย์เวิร์ดที่แคบกว่าไว้

  • นอกจากนี้ ยังแสดงกฎที่ไม่ได้ใช้ ซึ่งเป็นกฎที่ลบได้โดยไม่เสี่ยงต่อความเสถียรของแอป

  • ดังนั้น เครื่องมือวิเคราะห์การกำหนดค่าจึงได้รับการออกแบบมาเพื่อแก้ไขปัญหาบางอย่าง โดยเริ่มจากการสะสมกฎเดิม

  • ก่อนหน้านี้ การลบกฎเก่าค่อนข้างมีความเสี่ยงเนื่องจากไม่มีระดับการเข้าถึงต่อกฎ

  • ทีมจึงมักจะปล่อยไว้เฉยๆ หรือเพิ่มไวลด์การ์ดที่กว้างขึ้น

  • ประการที่ 2 คือช่วยให้คุณค้นพบจุดบอดของกฎไลบรารี

  • ดังนั้นเมื่อคุณเพิ่ม SDK ในอดีต จึงเป็นเรื่องยากที่จะบอกได้ว่ากฎที่รวมมากับไลบรารีนั้นกว้างเกินไปหรือไม่ หรือจำเป็นต้องใช้หรือไม่ เช่น ในเวอร์ชันที่ 10 หรือเวอร์ชันหลังจากนั้น

  • และเราจะพูดถึงไลบรารีเพิ่มเติมในอีกสักครู่

  • และประการที่ 3 คือช่วยให้คุณประเมินผลกระทบของการเพิ่มประสิทธิภาพได้

  • ก่อนหน้านี้ ไม่มีวิธีง่ายๆ ที่จะบอกได้ว่ากฎหนึ่งๆ เก็บรักษาชั้นเรียนไว้ 2 ชั้นเรียนหรือ 2,000 ชั้นเรียน

  • มาดูวิธีการทำงานของ R8 ในบริบทของแอปและไลบรารีที่เราดูไปก่อนหน้านี้กัน

  • ในที่นี้ฉันมีโมดูลแอป 1 รายการและไลบรารี 3 รายการ

  • และให้ความสนใจกับไลบรารี 3 ทางด้านขวา

  • นั่นคือไลบรารี Analytics Bloat ซึ่งมีกฎการเก็บรักษาทั้งแพ็กเกจ

  • ก่อนอื่น R8 จะระบุจุดแรกเข้าของรูทจากกฎการเก็บรักษาที่ผสานรวมทั้งหมด ซึ่งไฮไลต์เป็นสีเหลืองที่นี่

  • และโดยทั่วไปแล้ว ควรเป็นจุดแรกเข้าหลักของแอป เช่น คลาสในไฟล์ Manifest

  • และเนื่องจากกฎการเก็บไวลด์การ์ดของไลบรารี Analytics Bloat ทุกคลาสในไลบรารีจึงถือเป็นจุดแรกเข้า

  • จากนั้น R8 จะติดตามความสามารถในการเข้าถึงใน Call Graph จากรากเหล่านั้น

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

  • จากนั้น R8 จะทำการทรีเชคกิ้งและตัดโค้ดที่ไม่ได้อ้างอิงออก

  • ดังนั้น หลังจากกระบวนการ Tree Shaking หรือการลดขนาดครั้งแรก สิ่งที่เหลืออยู่ก็คือโค้ดเหล่านี้

  • และแอปของเรามีคะแนนการหดตัวอยู่ที่ 42.5% ซึ่งสูงกว่าเกณฑ์ 25% จาก Google Play แต่ก็ยังไม่ถึงจุดที่เราต้องการ

  • และสาเหตุหลักมาจากกฎการเก็บรักษาทั้งแพ็กเกจจากไลบรารี Analytics

  • ซึ่งทำให้มีชั้นเรียนเพียง 17 ชั้นเรียนจาก 40 ชั้นเรียนที่เหลือที่สามารถลดขนาดลงได้อีก

  • ประเด็นที่เราต้องการเน้นย้ำคือ กฎการเก็บรักษาที่ไม่ถูกต้องเพียงข้อเดียวอาจทำให้เกิดปัญหาใหญ่ในการเพิ่มประสิทธิภาพแอป

  • เมื่อทราบเช่นนี้แล้ว เรามาพูดถึงไลบรารีกันอย่างรวดเร็ว

  • ในปีที่ผ่านมา ทีมของเราได้ทุ่มเทอย่างมากในการทำความเข้าใจว่ากฎการเก็บรักษาของผู้บริโภคในคลังส่งผลต่อการเพิ่มประสิทธิภาพสำหรับระบบนิเวศอย่างไร

  • ไลบรารีจำนวนมากจะมาพร้อมกับกฎที่กว้างเกินไปเพื่อความเสถียร

  • หรืออาจระบุกฎกว้างๆ ไว้ในเอกสารประกอบสำหรับนักพัฒนาแอปเพื่อเพิ่มลงในแอปของตนเอง

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

  • เราพบกรณีที่ไลบรารีทั้งหมดได้รับการเก็บรักษาไว้โดยกฎการเก็บรักษาทั้งแพ็กเกจ เช่น ไลบรารี Bloat ของ Analytics และในกรณีของไลบรารีนั้น สิ่งนี้ส่งผลกระทบในวงกว้างต่อศักยภาพในการเพิ่มประสิทธิภาพของแอป

  • เรายังอยากพูดถึงด้วยว่าแม้แต่ SDK ของ Google เองก็ไม่รอดจากปัญหานี้ และไลบรารีของ Google มากกว่า 12 รายการได้แก้ไขกฎการเก็บรักษาที่กว้างเกินไปในปีที่ผ่านมา

  • ดังนั้นหากต้องการใช้เครื่องมือวิเคราะห์การกำหนดค่า เครื่องมือนี้จะผสานรวมเข้ากับ AGP 9.3 โดยตรง แต่ก็พร้อมใช้งานสำหรับเวอร์ชันเก่าด้วยหากคุณอัปเกรดเวอร์ชัน R8

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

  • และหากคุณใช้บิลด์ที่เผยแพร่ R8 แบบเต็ม ระบบจะสร้างรายงานโดยอัตโนมัติและดูได้ที่เส้นทางไฟล์นี้

  • ประโยชน์ของงานแบบสแตนด์อโลนนี้คือใช้เวลาในการสร้างน้อยกว่าบิลด์แบบเต็ม

กรณีศึกษา: การเพิ่มประสิทธิภาพ WHOOP

  • ทีม WHOOP ซึ่งอาจอยู่ที่นี่สักแห่งได้เรียกใช้เครื่องมือวิเคราะห์การกำหนดค่าและพบว่าการลดขนาดอยู่ที่ 74% การสร้างความสับสนอยู่ที่ 74% และการเพิ่มประสิทธิภาพอยู่ที่ 0

  • และอย่างที่คุณคาดเดาได้ ทีมรู้สึกประหลาดใจมากที่เห็นมูลค่าการเพิ่มประสิทธิภาพนี้

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

  • ซึ่งส่งผลให้เพิ่มประสิทธิภาพได้ 55% และลดขนาด APK ได้ 4%

  • เมื่อใช้ร่วมกับโปรไฟล์พื้นฐานแล้ว พวกเขาสามารถปรับปรุงเวลาเริ่มต้นแบบ Cold Start ได้ 38% และลดเฟรมที่ค้างมากเกินไปได้ 28%

  • หน้าจอทางด้านซ้ายไม่มีโปรไฟล์พื้นฐาน ส่วนหน้าจอทางด้านขวามีโปรไฟล์พื้นฐาน และอย่างที่คุณเห็น การเริ่มต้นแอปจะเร็วขึ้นเมื่อเพิ่มโปรไฟล์พื้นฐาน

  • Let that play one more time.

  • เยี่ยมเลย

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

  • ดังนั้นเพื่อเร่งกระบวนการให้เร็วขึ้น เราจึงมีทักษะ R8 Analyzer

    $ android skills list
    $ android skills add r8-analyzer
  • คุณสามารถขอให้ Agent วิเคราะห์การกำหนดค่า R8 ของคุณได้ และทักษะนี้ไม่เพียงแต่จะสร้างรายงานเครื่องมือวิเคราะห์การกำหนดค่า R8 เท่านั้น แต่ยังชี้ให้เห็นกฎที่มีผลมากที่สุดด้วย

  • นี่คือตัวอย่างเอาต์พุต และเพื่อระบุรายละเอียดบางอย่าง โปรดทราบว่าคะแนนการเพิ่มประสิทธิภาพ 42% หมายความว่า 52%-- 58% ของฐานของโค้ดไม่สามารถเพิ่มประสิทธิภาพโดย R8 ได้

  • และหากเราดูที่ส่วนกฎการเก็บรักษาโดยเฉพาะ กฎการเก็บรักษาทั้งแพ็กเกจสำหรับไลบรารี Analytics Bloat จะป้องกันไม่ให้เพิ่มประสิทธิภาพฐานของโค้ด 22% และ Skill ยังแนะนำให้เราปรับแต่งกฎการเก็บรักษานี้ด้วย เนื่องจากเรารู้อยู่แล้วว่ากฎนี้กว้างเกินไป

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

ความท้าทายของการใช้การสะท้อนในการเพิ่มประสิทธิภาพ DEX

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

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

  • ขอบคุณ Breana

  • เอาล่ะ มาพูดถึงที่มาที่ไปกันก่อนดีกว่า

  • ด้วยไลบรารี Analytics ที่มีขนาดใหญ่เกินไปนี้ พร้อมด้วยกฎการเก็บรักษาทั้งหมดเหล่านี้ ซึ่งก็คือด้วยกฎการเก็บรักษานี้ที่เก็บทุกอย่างไว้ ปัญหาหลักที่เราพบคือการสะท้อน

  • เรามาดูโค้ดแบบสะท้อนเพื่อเป็นการทบทวนกันสักเล็กน้อย

    fun createWithReflection(className: String): MyObject {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as MyObject
    }
  • นี่คือฟังก์ชันการสร้างอินสแตนซ์ออบเจ็กต์ที่เพียงแค่ขยายออบเจ็กต์หนึ่งๆ และทำเช่นนั้นแบบสะท้อนกลับ

  • ข้อเสียของวิธีนี้ในบริบทของการเพิ่มประสิทธิภาพ DEX คือจะทำให้การเรียกแบบคงที่ทั้งหมดเหล่านี้ล่าช้าไปจนถึงรันไทม์

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

  • สถานการณ์เหล่านี้จะทำให้การเพิ่มประสิทธิภาพ Dex เสียไปโดยค่าเริ่มต้น ดังนั้นโดยปกติแล้วคุณจะต้องเพิ่มกฎ Keep

  • ซึ่งคุณควรหลีกเลี่ยงปัญหานี้

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

  • แต่ก็ไม่ใช่ทั้งหมดใช่ไหม

  • เรายังคงต้องใช้การสะท้อนในบางกรณี

  • มาเจาะลึกกันว่าเหตุการณ์นี้เกิดขึ้นได้อย่างไรในไลบรารีที่บวมนี้

  • ตัวอย่างหนึ่งที่พบบ่อยคือทรัพยากร Dependency ที่ไม่บังคับ

    // Problem 1 - optional dependency
    fun tryInitCrashReporter(context: Context): EventTransport? {
       return runCatching {
           Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
               .getDeclaredConstructor(Context::class.java)
               .newInstance(context) as? EventTransport
       }.getOrNull()
    }
    
    // Problem 2 - dynamically instantiate subclass
    fun createEventFilter(className: String): EventFilter {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as? EventFilter
    }
    
    // Problem 3 - serialization
    fun serializeEvent(event: UserActionEvent): JSONObject {
       val json = JSONObject()
       for (field in event.javaClass.declaredFields) {
           field.isAccessible = true
           json.put(field.name, field.get(event))
       }
       return json
    }
  • ในที่นี้ เรากำลังขยายประเภทที่เฉพาะเจาะจงซึ่งเข้าถึงไม่ได้ใน classpath ของไลบรารี ดังนั้นเราจึงต้องดำเนินการด้วยสตริง

  • เรากำลังอ้างอิงถึงคลาสนี้โดยเฉพาะซึ่งอาจมีหรือไม่มีอยู่ในขณะที่สร้างไลบรารี ดังนั้นไลบรารีนี้จึงใช้การสะท้อนเพื่อค้นหาสมาชิกภายในไลบรารีที่เกี่ยวข้องอย่างสมเหตุสมผล

  • อีกกรณีหนึ่งที่เรามีคือการสร้างอินสแตนซ์คลาสย่อยแบบไดนามิก

  • ดังนั้น โดยเฉพาะอย่างยิ่งหากเป็นสิ่งที่มาจากไฟล์ Manifest หรือสิ่งที่ส่งมาจากฝั่งเซิร์ฟเวอร์ เราจะแจ้งให้แอปในเครื่องทราบว่า "เฮ้ โปรดขยายสิ่งนี้ สร้างสิ่งนี้ในภายหลัง"

  • และหมวดหมู่ที่ 3 ที่เราเห็นที่นี่คือการทำให้เป็นอนุกรมที่น่าอาย

  • คือฉันมีการจัดรูปแบบ JSON ที่เกิดขึ้นโดยการทำซ้ำผ่านสมาชิกทั้งหมด

  • ซึ่งเป็นเรื่องที่น่ารังเกียจอย่างยิ่ง แต่คุณก็รู้ว่าบางครั้งเรื่องแบบนี้ก็ยังคงมีอยู่

  • แต่ก็ไม่ได้แก้ไขทันทีที่จำเป็นเสมอไป

  • แต่เรามาพูดถึงผลลัพธ์ของเรื่องนี้กันดีกว่า

  • เมื่อการเรียกใช้แบบสะท้อนเหล่านี้เพิ่มขึ้นเรื่อยๆ คุณก็จะมีแรงจูงใจมากขึ้นที่จะทำสิ่งที่ง่าย และเพิ่มประสิทธิภาพสำหรับประสบการณ์ของนักพัฒนาแอปมากกว่าประสบการณ์ของผู้ใช้

  • ซึ่งก็รู้สึกแย่มาก ฉันเลยจะเพิ่มสิ่งที่ต้องทำ แล้วทุกอย่างก็จะเรียบร้อย

    # TODO: clean this up
    -keep com.example.analytics.bloat.** { * };
  • สักวันหนึ่งฉันจะกลับมาแก้ไขให้ถูกต้อง

  • เรามาพูดถึงสิ่งที่เราควรทำ สิ่งที่เราควรทำอย่างน้อยก่อนวันนี้ วิธีเขียนกฎการเก็บรักษาที่เหมาะสมที่นี่

    fun createEventFilter(className: String): EventFilter {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as? EventFilter
    }
  • เอาล่ะ เตรียมตัวให้พร้อม

  • ดังนั้น ส่วนแรกที่นี่คือเราต้องการเก็บคลาสเฉพาะที่เราแสดงผลจากฟังก์ชันการสะท้อนนี้

    ### Keep the EventFilter
    -keep class com.example.analytics.bloat.EventFilter {
        *
    }
  • แต่เดี๋ยวก่อน

  • รู้ไหมว่า

  • ซึ่งไม่ถูกต้อง

  • นี่คือคลาส Kotlin ฉันต้องตรวจสอบว่านี่เป็นประเภทสตริง ขอใส่ KT ลงไปก่อน

    ### Keep the actual EventFilter
    -keep class com.example.analytics.bloat.EventFilterKt {
        *
    }
  • และที่จริงแล้ว สิ่งสำคัญที่นี่คือฉันไม่ได้แสดงผล EventFilter แต่ฉันแสดงผลคลาสย่อยของ EventFilter ดังนั้นฉันจึงต้องเก็บคลาสที่ขยาย EventFilter ไว้เพื่อให้สามารถขยายคลาสนี้ได้อย่างถูกต้อง

    ### Keep the EventFilter subclasses
    -keep class ** extends com.example.analytics.bloat.EventFilterKt {
        *
    }
  • และจริงๆ แล้ว หากฉันต้องการทำสิ่งนี้ให้มีประสิทธิภาพมากขึ้น ตอนนี้ก็ทำได้แล้ว แต่เพื่อให้มีประสิทธิภาพมากขึ้น ในฐานะนักพัฒนาไลบรารีที่ดี ฉันควรทำอะไรเพิ่มเติมอีก

  • ฉันควรพูดว่า -keepclasseswithmembers และเก็บเฉพาะคลาสที่มีเครื่องมือสร้าง <init> นี้ที่ฉันใช้เพื่อขยายออบเจ็กต์

    ### Keep the EventFilter subclasses, if they have this constructor
    -keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
       void <init>();
    }
  • และถ้าอยากให้ดีจริงๆ ผมจะเพิ่ม allowaccessmodification เพื่อให้เพิ่มประสิทธิภาพได้มากขึ้นใน API ทั้งหมดที่กฎ Keep นี้กำหนดเป้าหมายไว้ เพราะผมไม่อยากป้องกันการเพิ่มประสิทธิภาพเพิ่มเติมจาก R8 ผมอยากเป็นพลเมืองที่ดี

    ### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
    -keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
       void <init>();
    }
  • และขั้นตอนสุดท้ายที่แทบไม่มีใครทำเพราะเป็นงานที่เพิ่มเข้ามา แต่เหมาะอย่างยิ่งสำหรับไลบรารี คือการทำให้เป็นแบบมีเงื่อนไข

    ### -if makes this conditional, critical for libraries
    -if class com.example.analytics.bloat.EventToolsKt {
        class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
    }
    ### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
    -keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
        void <init>();
    }
  • ในทางแนวคิด คุณไม่จำเป็นต้องเก็บคลาสย่อยทั้งหมดไว้ เว้นแต่คุณจะเรียกใช้ฟังก์ชันนี้จริงๆ และนั่นคือสิ่งที่คำสั่ง if นี้ทำที่ด้านบนของกฎ Keep

  • แต่คุณก็รู้ว่าผมสามารถขอให้ยกมือได้ แต่ผมรู้ว่าแทบไม่มีใครทำแบบนี้ใช่ไหม

  • เช่น การเขียนโค้ดจริงๆ และทำให้เป็นแบบมีเงื่อนไข

  • แล้วถ้าเราทำให้สิ่งนี้ดีขึ้นอีกสักหน่อยล่ะ

  • ก่อนอื่นมาดูที่ See, Like, Okay กัน

  • บางทีอาจมีวิธีอื่นในการดำเนินการนี้

  • เราได้พูดคุยกันไปแล้วว่าจะเป็นอย่างไรหากฉันเขียนกฎการเก็บรักษาที่แคบและเขียนด้วยตนเอง

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

  • ใต้สิ่งต่างๆ มีการเปลี่ยนแปลงเกิดขึ้น

  • และเรามีทางเลือกอื่น

  • เรามีคำอธิบายประกอบ เช่น @Keep

  • ตอนนี้ @Keep อยู่ในแพ็กเกจคำอธิบายประกอบ AndroidX มาเป็นเวลานานแล้ว แต่จริงๆ แล้วปัจจุบันไม่แนะนำให้ใช้

    @androidx.annotation.Keep
    class EventFilter { … }
    
    @androidx.annotation.Keep
    class SizeBasedEventFilter { … }
    
    @androidx.annotation.Keep
    class DropLogsEventFilter { … }
  • ซึ่งไม่ใช่โซลูชันที่ยอดเยี่ยมสำหรับนักพัฒนาไลบรารี และจริงๆ แล้วเราก็แบนวิธีนี้เป็นการภายในด้วย เพราะแม้ว่าจะดูแลรักษาได้ง่ายกว่า แต่ก็ไม่ยืดหยุ่นเลย และไม่มีการเพิ่มประสิทธิภาพบางอย่างที่ผมพูดถึงก่อนหน้านี้

  • ก็ทำไม่ได้อยู่แล้ว

  • ซึ่งไม่อนุญาตให้ทำการวิเคราะห์เว็บไซต์การโทร

  • ทีนี้ ถ้าเราดูที่กรณีเฉพาะที่เรามีก่อนหน้านี้ "อ๋อ ฉันก็แค่ใส่ @Keep ไว้ใน EventFilter ของฉัน ฉันก็สบายแล้วใช่ไหม"

    @Keep
    class EventFilter { … }
  • อืมมมม ไม่ได้ล่ะ

  • @Keep ไม่ได้กำหนดเป้าหมายคลาสย่อยจริงๆ แต่จะกำหนดเป้าหมายเฉพาะคลาสนั้นเท่านั้น

    @Keep
    class EventFilter { … }
    
    @Keep
    class SizeBasedEventFilter { … }
    
    @Keep
    class DropLogsEventFilter { … }
  • ดังนั้นจริงๆ แล้วฉันต้องกำหนดเป้าหมายคลาสย่อยทั้งหมด และตอนนี้ฉันกำลังเขียนคำอธิบายประกอบ @Keep มากกว่าที่ฉันจะเขียนด้วยกฎ Keep ข้อเดียวที่เขียนยาก

  • ดังนั้นทั้ง 2 วิธีนี้จึงมีปัญหาใช่ไหม

  • เนื่องจากคุณอาจลืมได้ง่าย ทราบข้อมูลช้า และโดยค่าเริ่มต้นแล้วจะไม่ขึ้นอยู่กับผู้โทร

  • โดยเฉพาะอย่างยิ่งสำหรับไลบรารี เราต้องการวิธีที่ดีกว่า

  • เราต้องการให้มีวิธีที่ง่ายขึ้นในการรับสิ่งต่างๆ ที่พร้อมใช้งานทันที

  • และปัญหาใหญ่ประการหนึ่งที่เราเห็นคือสิ่งเหล่านี้อยู่ห่างไกลจากการสะท้อนที่เรากำลังป้องกันอยู่จริงๆ

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

ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep

  • เรามาเริ่มพูดถึง Jetpack Keep ซึ่งเป็นไลบรารีและปลั๊กอินใหม่จาก AndroidX ที่ออกแบบมาเพื่อช่วยแก้ปัญหานี้โดยเฉพาะกันดีกว่า

  • ดังนั้น เพื่อใช้-- เป้าหมายทั้งหมดของ Jetpack Keep คือการเริ่มต้นด้วยการสร้างกฎ Keep จากคำอธิบายประกอบ แต่ยิ่งไปกว่านั้นคือการสร้างคำอธิบายประกอบโดยตรงจากการสะท้อนที่มีอยู่ในฐานของโค้ดอยู่แล้ว

  • การตั้งค่าจึงง่ายมาก

    plugins {
       id("com.android.library")
       id("androidx.annotation.keep")
    }
    
    dependencies {
        implementation("androidx.annotation.keep:keep")
        ...
    }
  • ซึ่งเป็นปลั๊กอิน Gradle และไลบรารีคำอธิบายประกอบ

  • แต่เรามาดูวิธีการทำงานจริงของโค้ดนี้ในเวลาบิลด์กัน

  • ดังนั้น หากฉันมีตัวอย่างก่อนหน้านี้ที่ฉันกำลังขยายคลาสย่อยของประเภทที่รู้จัก ฉันต้องทำอย่างไร

  • ฉันแค่เรียกใช้ lintFix

    ./gradlew analyticsbloat:lintFix
  • Lint จะวิเคราะห์การสะท้อนทั้งหมดที่เกิดขึ้นภายในเมธอดนี้ และแจ้งว่า "คุณต้องใช้คำอธิบายประกอบนี้"

    @UsesReflectionToConstruct(
       classConstant = EventFilter::class,
       parameterTypes = []
    )
    fun createEventFilter(className: String): EventFilter {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as? EventFilter
    }
  • คำอธิบายประกอบนี้แสดงให้เห็นอย่างชัดเจนว่าการสะท้อนภายในสายนี้ทำอะไรอยู่

  • ซึ่งระบุว่า "ฉันกำลังขยายคลาสประเภทนี้"

  • ในกรณีนี้ EventFilter เป็นคลาสที่รู้จัก

  • ฉันไม่ได้ใช้สตริงเพื่ออ้างอิงเนื่องจากไม่จำเป็นต้องใช้

  • ซึ่งจะช่วยเพิ่มความปลอดภัย และฉันยังเข้ารหัส parameterTypes เพื่อให้ระบุได้อย่างชัดเจนว่ากำลังเก็บคอนสตรัคเตอร์ใดไว้

  • แต่การดำเนินการนี้ทำอะไรได้บ้าง

  • เช่น ข้อมูลนี้จะช่วยให้เราสร้าง R8 ได้อย่างไร

  • ระบบจะสร้างกฎการเก็บรักษา และหากคุณเห็นก่อนหน้านี้ นี่คือกฎการเก็บรักษาที่เหมาะสมแบบเดียวกับที่เรามีก่อนหน้านี้

    # Generated Keep Rule
    
    -if class com.example.analytics.bloat.EventTools {
        class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
    }
    -keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
        void <init>();
    }
  • โดยจะกำหนดเป้าหมายไปยังคลาสและคลาสย่อยเดียวกันทุกประการ และขึ้นอยู่กับจุดสะท้อนนี้

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

  • ฉันไม่จำเป็นต้องเก็บไฟล์กฎการเก็บรักษาแยกต่างหากไว้ที่อื่น

  • วิธีนี้จะช่วยให้คุณจัดการกฎการเก็บรักษาได้ง่ายขึ้นมาก

  • ลองย้อนกลับไปดูตัวอย่างทรัพยากร Dependency ที่ไม่บังคับ

    @UsesReflectionToConstruct(
       className = "com.example.analytics.bloat.crash.CrashReporterTransport",
       parameterTypes = [Context::class]
    )
    fun tryInitCrashReporter(context: Context): EventTransport? {
       return runCatching {
           Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
               .getDeclaredConstructor(Context::class.java)
               .newInstance(context) as? EventTransport
       }.getOrNull()
    }
  • ตอนนี้ คลาสไม่ได้อยู่ในคลาสพาธ แต่ฉันยังคงอ้างอิงคลาสนั้นได้

  • ฉันใช้สตริง แต่สำหรับสิ่งอื่นๆ อย่างประเภทพารามิเตอร์จะอ้างอิงตามการอ้างอิงคลาส ฉันจึงทำเช่นนั้น

  • และเราจะสร้างกฎการเก็บรักษาที่เหมาะสมสำหรับกรณีนี้อีกครั้ง

    # Generated Keep Rule
    
    -if class com.example.analytics.bloat.CrashConnection {
        class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
    }
    -keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
        void <init>(android.content.Context);
    }
  • ซึ่งเป็นแบบมีเงื่อนไข เฉพาะเจาะจง และตรงเป้าหมาย รวมถึงช่วยให้เพิ่มประสิทธิภาพได้ทุกเมื่อที่เป็นไปได้ในคลาสที่กำหนดเป้าหมายเหล่านั้น

  • นี่คือการสะท้อนฟิลด์ และในกรณีนี้ ฉันกำลังทำซ้ำฟิลด์ทั้งหมดภายในออบเจ็กต์ แต่ในที่นี้ เราก็จัดการได้ดีด้วยการกำหนดเป้าหมายฟิลด์ทั้งหมดด้วยโอเปอเรเตอร์ดาวนี้

    // TODO: migrate to Kotlinx Serialization or Moshi codegen.
    @UsesReflectionToAccessField(
       classConstant = UserActionEvent::class,
       fieldName = "*"
    )
    fun serializeEvent(event: UserActionEvent): JSONObject {
       val json = JSONObject()
       for (field in event.javaClass.declaredFields) {
           field.isAccessible = true
           json.put(field.name, field.get(event))
       }
       return json
    }
  • ซึ่งช่วยให้ฉันกำหนดเป้าหมายฟิลด์ทั้งหมดได้ และดูสิ มันสร้างกฎการเก็บรักษาที่ตรงกับที่คุณคาดหวังไว้สำหรับเรื่องนี้ ซึ่งเป็นภาพสะท้อนที่โชคร้ายอย่างไม่น่าเชื่อซึ่งไม่ควรอยู่ในแอปของฉัน แต่ฉันจะกลับมาล้างข้อมูลนั้น ฉันสัญญา

    # Generated Keep Rule
    
    -if class com.example.analytics.bloat.serialization.EventSerializer {
        class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
    }
    -keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
        <fields>;
    }
  • แล้ว Jetpack Keep ทำอะไรได้บ้าง

  • ซึ่งช่วยลดการดูแลรักษากฎการเก็บรักษาได้อย่างมาก

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

  • นอกจากนี้ยังให้ความแน่นอนของ Lint ด้วย

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

  • ฉันจะใช้ฟีเจอร์นี้ได้ไหม

  • แล้วการขึ้นต่อกันในที่นี้คืออะไร

  • ปลั๊กอินและ Lint ต้องใช้ AGP 8.7

  • ซึ่งน่าจะครอบคลุมเกือบทุกคน

  • แต่ก็มีความยืดหยุ่นสูงมากในเรื่องตำแหน่งที่คุณจะใช้ได้

  • คุณใช้ฟีเจอร์นี้ภายในแอปได้

  • คุณสามารถใช้ฟีเจอร์นี้ภายในไลบรารี Android, AAR, ไลบรารี KMP และ JAR ซึ่งในกรณีนี้จะต้องมีการเชื่อมต่อด้วยตนเองเล็กน้อย

  • แต่ฟีเจอร์หลักในที่นี้คือฟีเจอร์นี้ปลอดภัยที่จะใช้ในฐานะนักพัฒนาไลบรารี

  • แม้ว่าจะมีคนใช้ R8 เวอร์ชันเก่าหรือแม้แต่เครื่องมือของบุคคลที่สามที่ใช้ไลบรารีของคุณ เราก็ยังคงส่งออกกฎผู้ใช้มาตรฐานจริงเพื่อให้ทุกคนยังคงใช้คุณได้และไม่ต้องกังวลเกี่ยวกับการใช้รูปแบบใหม่ที่เข้ากันไม่ได้

การวิเคราะห์แอปด้วย Android CLI

  • หลังจากที่ได้พูดถึงสิ่งที่ Jetpack Keep สามารถทำได้เพื่อช่วยคุณเพิ่มประสิทธิภาพและช่วยให้คุณกังวลน้อยลงเกี่ยวกับการปกป้องการเรียกใช้โค้ดแบบสะท้อนแล้ว เรามาพูดถึงฟีเจอร์ใหม่ที่เราเพิ่งเปิดตัวใน Android CLI เพื่อตรวจสอบอีกครั้งในขั้นตอนการเผยแพร่ครั้งล่าสุดกัน

  • Breana ได้พูดถึงก่อนหน้านี้ว่ามีเครื่องมือที่ Play Findings จะบอกข้อมูลเกี่ยวกับข้อมูลการปรับขนาด ขนาด DEX และเปอร์เซ็นต์การเพิ่มประสิทธิภาพ

  • ตอนนี้เราได้เริ่มเปิดให้ใช้งานผ่าน Android CLI ด้วยเช่นกัน

  • โดยใช้ตรรกะเดียวกันกับ Play Store ดังนั้นคุณจึงสามารถวิเคราะห์ Bundle ใน CLI ได้โดยไม่ต้องอัปโหลด Bundle ไปยัง Play Store

    $ android analyze <mybundle.aab>
    $ android analyze <mybundle.apk>
  • และคุณจะได้รับข้อมูลเดียวกันนี้ที่รายงานถึงคุณโดยตรงและทันทีพร้อมการวิเคราะห์ในพื้นที่

  • คุณจะเห็นได้จากกรณีนี้ว่าแอปนี้ได้รับคำเตือนว่ามีอัตราการเพิ่มประสิทธิภาพต่ำมาก และยังได้รับแจ้งว่า "นี่คือขนาด DEX ของคุณ และนี่คือเปอร์เซ็นต์การเพิ่มประสิทธิภาพ" เหมือนกับที่ Play Store จะบอกคุณ

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

    {
      "optimization": {
        "bundleAnalysisResult": {
          "agpVersion": "9.3.2",
          "r8Json": {
            "version": "9.3.16",
            "options": {
              "isRepackageClassesEnabled": true,
              "isProguardCompatibilityModeEnabled": false
            },
            "stats": {
              "shrinkingRatio": 0.2306,
              "optimizationRatio": 0.2351,
              "obfuscationRatio": 0.2322
            },
            ...
          }
        "mappingObfuscationRatio": 0.21631891,
        "dexHeuristicObfuscationRatio": 0.25118626,
        "uncompressedDexSizeBytes": "16198152"
      }
  • เราจะเห็นตัวเลขเดียวกันนี้ปรากฏอยู่ด้วย

  • นี่ไม่ใช่ฟีเจอร์เดียวของ android analyze

  • android analyze ยังทำงานในไลบรารีได้ด้วย

    $ android analyze <library.aar>
    $ android analyze <library.jar>
  • และอย่างที่ Breana กล่าวไว้ มีปัญหามากมายเกี่ยวกับไลบรารีที่ทำงานร่วมกับการจัดส่งซึ่งมีกฎการเก็บรักษาที่ไม่เหมาะสมอยู่ภายใน

  • เรามาใช้เครื่องมือนี้เพื่อดูปัญหาเหล่านี้กัน

  • ดังนั้นเมื่อฉันเรียกใช้เครื่องมือนี้กับ AAR ที่มีอยู่ ฉันจะเห็นว่าไลบรารีนี้มีปัญหาหลายอย่างที่ไฮไลต์ไว้ที่ด้านบนสุด

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

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

  • โค้ดที่อันตรายมาก

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

  • คุณจะเห็นว่าเราแสดงอัตราส่วนการเก็บรักษาด้วย

  • วิธีนี้เป็นวิธีที่ดีในการพิจารณาคุณภาพโดยรวมของคลัง

  • หากฉันเพิ่มประสิทธิภาพไลบรารีแต่ละรายการแบบสแตนด์อโลนโดยสมบูรณ์ ไม่มีข้อมูลอ้างอิง ด้วย R8 ฉันควรเห็นว่ามีขนาด 0 ไบต์ใช่ไหม

  • ซึ่งควรจะว่างเปล่าเนื่องจากไม่มีการเก็บรักษาข้อมูลใดๆ ไว้ภายใน

  • ซึ่งนั่นเป็นสิ่งที่เหมาะสมที่สุด

  • ในกรณีนี้ ไม่ใช่

  • คุณจะเห็นขนาด R8 และ D8 โดยตรง

  • และเรายังไฮไลต์กฎการเก็บรักษาที่แย่ที่สุดบางข้อเพื่อให้คุณเห็นได้ชัดเจนยิ่งขึ้นด้วย

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

    {
      "optimization": {
        "libraryAnalysisResult": {
          "dex": {
            "d8CompileResult": {
              "dexSizeBytes": 4523352
            },
            "r8CompileResult": {
              "dexSizeBytes": 3266596,
              "configuration": {
                "usedPackageWideKeepRules": [
                  {
                    "source": "-keep class com.example.analytics.bloat.** { *; }",
                    "keptItemCount": 125579,
                  },
                  ...
                ],
                "unusedPackageWideKeepRules": [
                  {
                    "source": "-keep class some.other.library.** { *; }",
                  },
                  ...
                ]
              },
              ...
    }
  • ดังนั้น หากพบปัญหาเกี่ยวกับไลบรารี โปรดลองอัปเดตไลบรารีก่อน

  • อย่างที่ Breana ได้กล่าวไปก่อนหน้านี้ เราได้แก้ไขไลบรารีของ Google มากกว่า 12 รายการเพื่อแก้ไขกฎการเก็บรักษาที่น่าเสียดาย และยังมีไลบรารีอื่นๆ ที่ได้รับการแก้ไขแล้ว แต่แอปที่ใช้ไลบรารีเหล่านั้นยังไม่ได้รับการอัปเดต

  • นี่เป็นขั้นตอนแรกที่ยอดเยี่ยม

  • ประการที่ 2 คุณจะมองหาปัญหาและติดต่อนักพัฒนาไลบรารีได้ และอาจแนบรายงานเครื่องมือวิเคราะห์ที่บอกว่า "คุณเก็บข้อมูลไว้มากเกินไป

  • โปรดช่วยฉันเพิ่มประสิทธิภาพแอป"

สรุปประเด็นสำคัญและแหล่งข้อมูล

  • สรุปก็คือ เราได้พูดถึงเครื่องมือวิเคราะห์การกำหนดค่า R8 ซึ่งช่วยให้คุณดูทุกสิ่งที่เกิดขึ้นในบิลด์ R8 ของคุณ ทุกสิ่งที่บอกว่าอะไรเพิ่มประสิทธิภาพได้และอะไรเพิ่มประสิทธิภาพไม่ได้

  • เราได้พูดถึง Jetpack Keep ซึ่งเป็นไลบรารีใหม่ที่ช่วยให้คุณเขียนกฎ Keep ได้โดยอัตโนมัติโดยไม่ต้องทำอะไรเลย

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

  • หากต้องการดูข้อมูลเพิ่มเติม โปรดดูเอกสารประกอบสำหรับ R8, เครื่องมือวิเคราะห์การกำหนดค่า และ Jetpack Keep

  • และมีเอกสารประกอบเกี่ยวกับ Android CLI ที่พูดถึงคำสั่ง analyze และวิธีใช้

  • ขอบคุณมากครับ

รหัส

ข้อมูลโค้ด

ข้อมูลโค้ด Kotlin Script ที่ 05:15 ใน "ข้อมูลเบื้องต้นเกี่ยวกับการเพิ่มประสิทธิภาพ DEX"
android {
    buildTypes {
        release {
            optimization {
                // no need to specify default
                // rules and resource shrinking
                enabled = true
            }
        }
    }
    ...
}
ข้อมูลโค้ด Kotlin Script ที่ 05:15 ใน "ข้อมูลเบื้องต้นเกี่ยวกับการเพิ่มประสิทธิภาพ DEX"
android {
    buildTypes {
        release {
            // Enables code optimizations.
            isMinifyEnabled = true

            // Enables resource shrinking.
            isShrinkResources = true

            proguardFiles(
                // Default rules.
                getDefaultProguardFile(
                    "proguard-android-optimize.txt"
                ),
                ... // list out each file with keep rules
            )
            ...
        }
    }
    ...
}
ตัวอย่าง Shell ที่ 13:19 ใน "กรณีศึกษา: การเพิ่มประสิทธิภาพ WHOOP"
$ android skills list
$ android skills add r8-analyzer
ข้อมูลโค้ด Kotlin ที่นาที 14:41 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
fun createWithReflection(className: String): MyObject {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as MyObject
}
ข้อมูลโค้ด Kotlin ที่นาที 15:40 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
// Problem 1 - optional dependency
fun tryInitCrashReporter(context: Context): EventTransport? {
   return runCatching {
       Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
           .getDeclaredConstructor(Context::class.java)
           .newInstance(context) as? EventTransport
   }.getOrNull()
}

// Problem 2 - dynamically instantiate subclass
fun createEventFilter(className: String): EventFilter {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as? EventFilter
}

// Problem 3 - serialization
fun serializeEvent(event: UserActionEvent): JSONObject {
   val json = JSONObject()
   for (field in event.javaClass.declaredFields) {
       field.isAccessible = true
       json.put(field.name, field.get(event))
   }
   return json
}
Shell snippet at 16:52 in "Challenges of reflection in DEX optimization"
# TODO: clean this up
-keep com.example.analytics.bloat.** { * };
ข้อมูลโค้ด Kotlin ที่ 16:59 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
fun createEventFilter(className: String): EventFilter {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as? EventFilter
}
Shell snippet ที่ 17:07 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
    *
}
Shell snippet ที่ 17:15 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
    *
}
Shell snippet ที่ 17:19 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
    *
}
Shell snippet ที่ 17:40 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
   void <init>();
}
Shell snippet ที่ 17:50 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
   void <init>();
}
Shell snippet ที่ 18:03 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
### -if makes this conditional, critical for libraries
-if class com.example.analytics.bloat.EventToolsKt {
    class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
}
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
    void <init>();
}
ข้อมูลโค้ด Kotlin ที่นาที 18:57 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
@androidx.annotation.Keep
class EventFilter { … }

@androidx.annotation.Keep
class SizeBasedEventFilter { … }

@androidx.annotation.Keep
class DropLogsEventFilter { … }
ข้อมูลโค้ด Kotlin ที่ 19:22 ใน "ความท้าทายของการใช้ Reflection ในการเพิ่มประสิทธิภาพ DEX"
@Keep
class EventFilter { … }
ข้อมูลโค้ด Kotlin ที่นาที 19:29 ใน "ความท้าทายของการสะท้อนในการเพิ่มประสิทธิภาพ DEX"
@Keep
class EventFilter { … }

@Keep
class SizeBasedEventFilter { … }

@Keep
class DropLogsEventFilter { … }
ข้อมูลโค้ด Kotlin Script ที่นาที 20:38 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
plugins {
   id("com.android.library")
   id("androidx.annotation.keep")
}

dependencies {
    implementation("androidx.annotation.keep:keep")
    ...
}
Shell snippet ที่ 20:54 ใน "Introduction to Jetpack Keep"
./gradlew analyticsbloat:lintFix
ข้อมูลโค้ด Kotlin ที่นาที 20:55 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
@UsesReflectionToConstruct(
   classConstant = EventFilter::class,
   parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as? EventFilter
}
ตัวอย่าง Shell ที่นาที 21:25 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
# Generated Keep Rule

-if class com.example.analytics.bloat.EventTools {
    class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
}
-keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
    void <init>();
}
ข้อมูลโค้ด Kotlin ที่ 21:52 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
@UsesReflectionToConstruct(
   className = "com.example.analytics.bloat.crash.CrashReporterTransport",
   parameterTypes = [Context::class]
)
fun tryInitCrashReporter(context: Context): EventTransport? {
   return runCatching {
       Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
           .getDeclaredConstructor(Context::class.java)
           .newInstance(context) as? EventTransport
   }.getOrNull()
}
Shell snippet ที่ 22:05 ใน "Introduction to Jetpack Keep"
# Generated Keep Rule

-if class com.example.analytics.bloat.CrashConnection {
    class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
    void <init>(android.content.Context);
}
ข้อมูลโค้ด Kotlin ที่ 22:16 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
// TODO: migrate to Kotlinx Serialization or Moshi codegen.
@UsesReflectionToAccessField(
   classConstant = UserActionEvent::class,
   fieldName = "*"
)
fun serializeEvent(event: UserActionEvent): JSONObject {
   val json = JSONObject()
   for (field in event.javaClass.declaredFields) {
       field.isAccessible = true
       json.put(field.name, field.get(event))
   }
   return json
}
Shell snippet ที่ 22:27 ใน "ข้อมูลเบื้องต้นเกี่ยวกับ Jetpack Keep"
# Generated Keep Rule

-if class com.example.analytics.bloat.serialization.EventSerializer {
    class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
    <fields>;
}
Shell snippet ที่ 24:22 ใน "Analyzing apps with Android CLI"
$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>
ข้อมูลโค้ด JSON ที่ 24:52 ใน "การวิเคราะห์แอปด้วย Android CLI"
{
  "optimization": {
    "bundleAnalysisResult": {
      "agpVersion": "9.3.2",
      "r8Json": {
        "version": "9.3.16",
        "options": {
          "isRepackageClassesEnabled": true,
          "isProguardCompatibilityModeEnabled": false
        },
        "stats": {
          "shrinkingRatio": 0.2306,
          "optimizationRatio": 0.2351,
          "obfuscationRatio": 0.2322
        },
        ...
      }
    "mappingObfuscationRatio": 0.21631891,
    "dexHeuristicObfuscationRatio": 0.25118626,
    "uncompressedDexSizeBytes": "16198152"
  }
Shell snippet ที่ 25:17 ใน "Analyzing apps with Android CLI"
$ android analyze <library.aar>
$ android analyze <library.jar>
ข้อมูลโค้ด JSON ที่ 26:46 ใน "การวิเคราะห์แอปด้วย Android CLI"
{
  "optimization": {
    "libraryAnalysisResult": {
      "dex": {
        "d8CompileResult": {
          "dexSizeBytes": 4523352
        },
        "r8CompileResult": {
          "dexSizeBytes": 3266596,
          "configuration": {
            "usedPackageWideKeepRules": [
              {
                "source": "-keep class com.example.analytics.bloat.** { *; }",
                "keptItemCount": 125579,
              },
              ...
            ],
            "unusedPackageWideKeepRules": [
              {
                "source": "-keep class some.other.library.** { *; }",
              },
              ...
            ]
          },
          ...
}