Alat & Performa

Aturan keep yang aman dari jenis dan pengoptimalan DEX

Speaker

Tentang

Tentang konferensi

Aplikasi Android sering kali dikirimkan dalam kondisi kurang optimal karena aturan keep yang terlalu konservatif, sering kali dari library pihak ketiga, sehingga menyebabkan APK membengkak, cold start lebih lambat, dan performa runtime menurun.

Dengan batas performa baru Google Play untuk pengoptimalan DEX, visibilitas aplikasi kini bergantung langsung pada upaya untuk selalu mengikuti perkembangan pengoptimalan.

Pembahasan ini memperkenalkan alur kerja R8 modern untuk mengatasi aturan yang berlebihan. Anda akan mempelajari cara:

  • Gunakan penganalisis dan keahlian konfigurasi R8 untuk mengidentifikasi overhead aturan lama.
  • Transisi dari aturan keep karakter pengganti yang luas ke anotasi yang dihasilkan, aman dari jenis, dan akurat.
  • Cegah regresi pengoptimalan dengan alat Android CLI baru.

Ringkasan

  • Google Play menerapkan nilai minimum kualitas aplikasi yang memerlukan pengoptimalan kode lebih dari 25%. Memenuhi persyaratan ini akan mengurangi penggunaan memori sekaligus meningkatkan kecepatan startup dan rasio ANR.
  • Penganalisis Konfigurasi R8 menghasilkan laporan HTML yang menunjukkan aturan keep mana yang mempertahankan kode. Alat ini menyoroti aturan yang tercakup dan tidak digunakan serta mengukur potensi pengoptimalan sebelum kompilasi.
  • Aturan keep yang terlalu luas, yang sering diperkenalkan untuk mencegah error refleksi atau diwarisi dari library, akan merusak pengoptimalan aplikasi. Satu aturan karakter pengganti di seluruh paket dapat menyimpan puluhan class yang tidak digunakan.
  • Jetpack Keep menggunakan lint untuk mendeteksi refleksi dan membuat anotasi yang akurat serta aturan keep bersyarat langsung dari kode. Alat ini mengekspor aturan konsumen standar yang kompatibel dengan versi R8 yang lebih lama.
  • Android CLI memeriksa app bundle dan AAR menggunakan logika Google Play yang tepat tanpa upload. Alat ini menandai aturan yang bermasalah dan mengekspor laporan JSON untuk pengujian regresi otomatis.

Resource

Gemini Notebook

Topik

  • dex-optimization
  • r8
  • keep-rules
  • jetpack-keep
  • android-cli
  • performa-aplikasi

Transkrip

Pengantar pengoptimalan DEX

  • Halo, semuanya!

  • Nama saya Breana, saya anggota tim DevRel Android.

  • Dan saya Chris Craik, saya di tim Performa Android.

  • Selamat datang di aturan keep yang aman dari jenis dan pengoptimalan DEX.

  • Hari ini, kita akan membahas apa itu pengoptimalan DEX, mengapa Anda harus peduli, dan juga beberapa update terbaru yang menarik.

  • Sejak lama, pengoptimalan DEX dianggap sebagai sesuatu yang hanya sekadar bagus untuk dimiliki.

  • Jadi, mungkin Anda akan mengonfigurasinya sekali, lalu membiarkannya, atau mungkin Anda akan melakukannya saat memiliki waktu khusus.

  • Namun, dengan industri seluler yang menghadapi kendala pasokan hardware yang signifikan yang mengubah ketersediaan memori perangkat, DEX yang dioptimalkan dengan baik sangat penting agar aplikasi Anda menggunakan lebih sedikit memori, memulai lebih cepat, mengurangi ANR, serta meningkatkan performa rendering dan run-time secara keseluruhan.

  • Untuk menjaga ekosistem Android yang sehat dan memastikan aplikasi Anda terus memberikan pengalaman premium, Google Play mengumumkan persyaratan kualitas aplikasi baru dengan nilai minimum performa yang akan diberlakukan pada bulan Februari.

  • Hal ini mencakup nilai minimum untuk pengoptimalan kode DEX.

  • Aplikasi harus mencapai di atas 25% untuk penyingkatan, pengoptimalan, dan obfuscation.

  • Jadi, apa artinya bagi Anda sebagai developer Android?

  • Kode DEX yang tidak dioptimalkan tidak hanya berdampak negatif pada pengalaman pengguna, tetapi juga dapat memengaruhi visibilitas dan kemampuan publikasi aplikasi Anda di Google Play Store.

  • Namun, Anda berada di tempat yang tepat, dan di akhir diskusi ini, Anda akan memiliki alat untuk tidak hanya memenuhi, tetapi juga melampaui persyaratan ini.

  • Pengoptimalan kode DEX dapat dicapai dengan R8, pengoptimal aplikasi Android.

  • Meskipun R8 tidak diwajibkan secara ketat jika pengoptimal aplikasi lain dapat membuat aplikasi Anda melampaui batas ini, R8 adalah jalur yang direkomendasikan dan diintegrasikan langsung ke toolchain Android.

  • Meskipun app bundle DEX yang dioptimalkan dengan baik menggunakan lebih sedikit memori, manfaat utamanya tetap terkait dengan performa: lebih sedikit ANR, startup lebih cepat, dan performa run-time yang lebih baik.

  • Mengoptimalkan aplikasi Anda akan memberikan hasil yang nyata, terbukti, dan positif untuk aplikasi dan pengguna Anda.

  • Misalnya, dengan mengadopsi mode penuh R8 dan menyempurnakan aturan keep, Grab dapat mengoptimalkan 78%, menyusutkan 79%, dan meng-obfuscate 82% aplikasinya.

  • Anda mungkin melihat slide ini dan berpikir, "Oke, angka-angka itu cukup tinggi, tetapi apa artinya?"

  • Sebagai hasil dari peningkatan ini, yang dikombinasikan dengan Profil Dasar Pengukuran, mereka mengurangi ANR sebesar 32% dan meningkatkan waktu mulai sebesar 35%.

  • Saat pertama kali mengadopsi R8, aplikasi teratas mengalami penurunan rata-rata ukuran DEX sebesar 38% dan penurunan ANR yang dirasakan pengguna sebesar 16%.

  • Dan jika Anda sudah menggunakan R8, kami punya kabar baik untuk Anda.

  • Untuk aplikasi yang telah mengaktifkan R8, lalu mengambil langkah-langkah untuk lebih meningkatkan tingkat pengoptimalan, penyingkatan, dan obfuscation hingga lebih dari 50%, mereka akan melihat, rata-rata, penurunan ukuran DEX sebesar 25%, penurunan waktu sistem dimulai sebesar 4%, dan penurunan ANR yang dirasakan pengguna sebesar 17%, yang merupakan tambahan dari 16% yang kami tunjukkan di slide sebelumnya.

  • Jadi, hari ini kami ingin memandu Anda tentang cara mencapai angka seperti ini.

  • Kami akan membahas alat yang dapat membantu Anda mengoptimalkan kode DEX dan beralih dari aturan keep yang terlalu luas dan salah dikonfigurasi ke aturan yang spesifik dan sempit.

  • Pertama, kami juga ingin menyebutkan bahwa R8 telah ada selama bertahun-tahun.

  • Kami juga menyadari bahwa meskipun telah memungkinkan pengoptimalan yang berdampak tinggi, konfigurasi ini masih memerlukan banyak perhatian.

  • Panggilan reflektif berarti input tidak dapat diprediksi, dan dapat menyebabkan error run-time.

  • Hal ini telah menggoda tim aplikasi dan tim library di masa lalu untuk menambahkan aturan keep yang terlalu luas hanya untuk bermain aman.

  • Kami tahu bahwa 41% aplikasi teratas berada di bawah tingkat pengoptimalan 25% yang ditentukan oleh Google Play, tetapi kami juga tahu bahwa 13% aplikasi teratas sudah mencapai tiga kali lipat dari nilai minimum tersebut.

  • Dan ini adalah aplikasi dari berbagai industri dan perjalanan pengguna yang mampu mencapai angka ini.

  • Skor pengoptimalan yang tinggi dapat dicapai untuk aplikasi Anda, asalkan Anda memiliki alat yang tepat dan visibilitas yang tepat untuk mengelola aturan keep Anda sendiri dan aturan yang diwarisi dari library.

  • Sebelum membahas alat dan peningkatan baru yang akan membantu proses ini, kami juga ingin menyoroti beberapa peningkatan R8 lainnya dari tahun lalu yang juga membuat pengoptimalan ini sedikit lebih mudah.

  • Pertama, template aplikasi baru menyertakan cakupan paket, yang berguna untuk memastikan tingkat pengoptimalan minimum sejak awal.

  • Configuration Analyzer R8, yang akan kita bahas sebentar lagi, kini didukung di AGP versi lama.

  • Fitur ini juga didukung secara tidak resmi untuk Bazel.

  • Dan di AGP 9.2, primitif coroutine menjadi dua kali lebih cepat.

  • Berkat peningkatan R8 yang dilakukan di AGP 9.2, Tinder mengalami penurunan ANR sebesar 13% setelah mengupgrade dari versi 9.0.1.

  • AGP 9.3 juga menghadirkan R8 API yang didesain ulang dengan penamaan yang lebih jelas, default yang lebih sederhana untuk aturan default dan penyingkatan resource, serta dukungan direktori sumber AGP untuk aturan keep.

    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
                )
                ...
            }
        }
        ...
    }
  • Update yang akan kita bahas hari ini juga merupakan peningkatan pada pengalaman developer.

  • Kita akan mulai dengan cara memanfaatkan R8 Configuration Analyzer dan keahlian R8 untuk mengidentifikasi overhead aturan lama dan menentukan jalur peningkatan.

  • Kemudian, Chris akan membahas cara beralih dari aturan keep karakter pengganti yang luas ke anotasi baru yang tepat, aman dari jenis, dan dibuat otomatis.

  • Terakhir, kita akan melihat cara menggunakan alat validasi CLI baru untuk memastikan pengoptimalan yang agresif tanpa menyebabkan error.

Ringkasan penganalisis konfigurasi R8

  • Jadi, dimulai dengan R8 Config Analyzer, ini adalah tempat yang ideal untuk memulai penyelidikan pengoptimalan DEX Anda.

  • Jika Anda belum pernah menggunakan alat ini, berikut tampilannya.

  • Laporan ini adalah laporan HTML yang memvisualisasikan informasi tentang konfigurasi R8 Anda.

  • Laporan ini ditujukan untuk aplikasi saya, yang menyertakan library bernama Analytics Bloat.

  • Seperti yang dapat Anda tebak, ini bukan aplikasi atau library sungguhan, tetapi ditandai dengan warna biru di bagian atas.

  • Config Analyzer mengukur hal berikut: penyingkatan, yang merupakan persentase class, metode, dan kolom yang tidak dicegah oleh aturan keep agar tidak disingkat oleh R8.

  • Dan ini dihitung setelah R8 melakukan proses penyingkatan pertamanya.

  • Metrik ini juga mengukur pengoptimalan, yaitu persentase kode yang terbuka untuk pengoptimalan seluruh program seperti inline metode dan penggabungan class.

  • Dan obfuscation, yang merupakan persentase anggota yang dapat di-obfuscate atau diganti namanya menjadi ID yang lebih pendek.

  • Perlu diketahui juga bahwa karena R8 Config Analyzer menghitung skor ini sebelum pengoptimalan akhir dan kompilasi DEX, nilai yang Anda lihat dalam laporan ini untuk pengoptimalan, penyingkatan, dan obfuscation tidak akan sama persis dengan nilai yang Anda lihat di Konsol Play.

  • Hal ini karena Google Play mengukur output DEX akhir.

  • Alasan perbedaan ini adalah untuk memastikan bahwa saat mengembangkan, Anda dapat mengakses feedback loop lokal yang cepat.

  • Namun, skor yang Anda lihat di R8 Config Analyzer masih sangat berguna untuk memvalidasi potensi pengoptimalan DEX sebelum Anda mengupload ke Google Play Store.

  • Laporan ini juga menyertakan tabel Analisis Aturan Penyimpanan, yang merupakan tampilan utama yang Anda lihat saat membuka laporan.

  • Dari sana, Anda dapat mengklik aturan mana pun dan melihat jumlah pasti class, metode, dan kolom yang dipertahankan oleh aturan tertentu sehingga Anda dapat melihat aturan mana yang mempertahankan lebih banyak dari yang seharusnya.

  • Aturan ini juga memiliki aturan yang tercakup, yang diperbesar di sini.

  • Aturan ini tumpang-tindih dengan aturan yang lebih luas.

  • Kata kunci ini ditandai agar Anda dapat menghapus kata kunci yang luas dan hanya mempertahankan kata kunci yang lebih spesifik.

  • Alat ini juga menampilkan aturan yang tidak digunakan, yaitu aturan yang dapat dihapus tanpa risiko terhadap stabilitas aplikasi Anda.

  • Jadi, Config Analyzer dirancang untuk mengatasi beberapa masalah, dimulai dengan akumulasi aturan lama.

  • Sebelumnya, tanpa visibilitas per aturan, menghapus aturan lama agak berisiko.

  • Jadi, tim akan membiarkannya atau hanya menambahkan karakter pengganti yang lebih luas.

  • Kedua, solusi ini membantu Anda menemukan titik buta aturan library.

  • Jadi, saat Anda menambahkan SDK, biasanya sulit untuk mengetahui apakah aturan yang dipaketkan dengan library tersebut terlalu luas atau bahkan diperlukan, misalnya, 10 versi atau lebih kemudian.

  • Kita juga akan membahas lebih lanjut tentang library sebentar lagi.

  • Ketiga, fitur ini membantu Anda menilai dampak pengoptimalan.

  • Sebelumnya, tidak ada cara mudah untuk mengetahui apakah suatu aturan mempertahankan dua class atau 2.000 class.

  • Mari kita lihat cara kerja R8 dalam konteks aplikasi saya dan library yang kita lihat sebelumnya.

  • Jadi, di sini saya memiliki modul aplikasi dan tiga library.

  • Dan perhatikan baik-baik library ketiga di sebelah kanan.

  • Library tersebut adalah Analytics Bloat, yang menyertakan aturan keep di seluruh paket.

  • Pertama, R8 mengidentifikasi titik entri root dari semua aturan keep yang digabungkan, yang ditandai dengan warna kuning di sini.

  • Dan umumnya, ini harus menjadi titik entri utama aplikasi, seperti class dalam manifes.

  • Dan karena aturan keep karakter pengganti library Analytics Bloat, setiap class dalam library diperlakukan sebagai titik entri.

  • Selanjutnya, R8 melacak keterjangkauan di seluruh grafik panggilan dari root tersebut.

  • Setiap class yang dapat dijangkau dari root ditandai untuk dipertahankan dengan warna hijau, sedangkan class yang tidak direferensikan diklasifikasikan sebagai kode mati dan ditandai dengan warna merah di sini.

  • R8 kemudian melakukan tree shaking dan menghapus kode mati yang tidak direferensikan.

  • Jadi, setelah tree shaking ini, atau proses penyingkatan pertama, inilah yang tersisa.

  • Aplikasi kita memiliki skor penyusutan sebesar 42,5%, yang berada di atas batas 25% dari Google Play, tetapi masih belum sesuai dengan yang kita inginkan.

  • Hal ini sebagian besar disebabkan oleh aturan keep seluruh paket dari library analisis.

  • Dengan demikian, hanya 17 dari 40 class yang tersisa yang dapat diperkecil lebih lanjut.

  • Poin yang ingin kami sampaikan di sini adalah bahwa satu aturan keep yang buruk dapat menyebabkan masalah besar bagi pengoptimalan aplikasi Anda.

  • Dengan mempertimbangkan hal ini, mari kita fokus pada library.

  • Selama setahun terakhir, tim kami telah banyak berinvestasi dalam memahami pengaruh aturan keep konsumen library terhadap pengoptimalan ekosistem.

  • Banyak library akan dikirimkan dengan aturan yang terlalu luas demi stabilitas.

  • Atau, mereka juga akan menentukan aturan umum dalam dokumentasi mereka agar developer aplikasi dapat menambahkannya sendiri ke aplikasi mereka.

  • Namun, pengiriman dengan aturan yang luas tidak diperlukan untuk stabilitas, dan berdampak negatif pada performa, yang biayanya terutama ditanggung oleh Anda sebagai developer aplikasi.

  • Kami telah melihat kasus saat seluruh library dipertahankan oleh aturan mempertahankan di seluruh paket seperti library bloatware analisis, dan dalam kasus library tersebut, hal ini berdampak luas pada potensi pengoptimalan aplikasi saya.

  • Kami juga ingin menyampaikan bahwa SDK kami sendiri di Google tidak kebal terhadap hal ini, dan lebih dari selusin library Google telah memperbaiki aturan keep yang terlalu luas dalam setahun terakhir.

  • Jadi, jika Anda ingin menggunakan penganalisis konfigurasi, fitur ini terintegrasi langsung ke AGP 9.3, tetapi juga tersedia untuk versi yang lebih lama jika Anda mengupgrade versi R8.

  • Jadi, menjalankan perintah ini akan menghasilkan laporan HTML yang kami tunjukkan, yang menampilkan aturan keep, dampak pada penyingkatan, pengoptimalan, obfuscation untuk setiap aturan dan aplikasi Anda secara keseluruhan.

  • Dan jika Anda menjalankan build rilis R8 lengkap, laporan akan otomatis dibuat dan dapat ditemukan di jalur file ini.

  • Manfaat tugas mandiri ini adalah memerlukan waktu lebih sedikit untuk dibuat daripada build lengkap.

Studi kasus: Pengoptimalan WHOOP

  • Tim di WHOOP, yang mungkin ada di sini, menjalankan penganalisis konfigurasi dan mendapatkan penyusutan sebesar 74%, obfuscation sebesar 74%, dan pengoptimalan sebesar 0.

  • Seperti yang dapat Anda bayangkan, tim sangat terkejut melihat nilai pengoptimalan ini.

  • Visibilitas dari penganalisis konfigurasi membantu mereka menemukan bahwa build mereka menggunakan file ProGuard lama yang menonaktifkan pengoptimalan. Jadi, dengan beralih ke versi terbaru, mereka dapat mengaktifkan pengoptimalan untuk aplikasi mereka.

  • Hal ini menghasilkan pengoptimalan sebesar 55% dan pengurangan ukuran APK sebesar 4%.

  • Dikombinasikan dengan Profil Dasar Pengukuran, mereka meningkatkan waktu cold start sebesar 38%, dan mengurangi frame yang berhenti berlebihan sebesar 28%.

  • Layar di sebelah kiri adalah tanpa Profil Dasar Pengukuran, dan layar di sebelah kanan adalah dengan Profil Dasar Pengukuran. Seperti yang Anda lihat, startup aplikasi lebih cepat dengan penambahan Profil Dasar Pengukuran.

  • Mari kita putar sekali lagi.

  • Keren!

  • Link ini akan mengarahkan Anda ke panduan untuk penganalisis konfigurasi, dan meninjau laporan secara manual adalah hal yang bagus, tetapi seperti yang dapat Anda bayangkan, bergantung pada status konfigurasi R8 aplikasi Anda, hal ini dapat memakan banyak waktu.

  • Jadi, untuk mempercepat prosesnya, kita memiliki keterampilan R8 Analyzer.

    $ android skills list
    $ android skills add r8-analyzer
  • Anda dapat meminta agen menganalisis konfigurasi R8, dan keahlian ini tidak hanya akan membuat laporan penganalisis konfigurasi R8, tetapi juga mengarahkan Anda ke aturan yang paling berdampak.

  • Berikut contoh outputnya, dan untuk menunjukkan beberapa detail, output ini mencatat skor pengoptimalan 42%, yang berarti 52%-- 58% codebase tidak dapat dioptimalkan oleh R8.

  • Dan jika kita melihat bagian aturan keep secara khusus, aturan keep di seluruh paket untuk library bloat analisis mencegah 22% codebase dioptimalkan, dan skill ini juga merekomendasikan agar kita menyaring aturan keep ini karena, seperti yang sudah kita ketahui, aturan ini terlalu luas.

  • Jadi, sekarang Anda dapat mendiagnosis penyebab bloatware aplikasi dan pemblokiran pengoptimalan DEX menggunakan penganalisis konfigurasi R8 dan keahliannya.

Tantangan refleksi dalam pengoptimalan DEX

  • Namun, setelah menemukan aturan keep yang terlalu luas, bagaimana cara menggantinya dengan aman tanpa menimbulkan error yang tidak terduga?

  • Saya akan menyerahkannya kepada Chris, yang akan menunjukkan cara beralih dari aturan keep lama ke anotasi keep baru yang dibuat otomatis dan aman dari jenis.

  • Terima kasih, Breana.

  • Baiklah, mari kita bahas bagaimana kita sampai di sini, ya?

  • Dengan library analisis yang membengkak ini, dengan semua aturan keep yang-- dengan aturan keep yang pada dasarnya menyimpan semuanya, masalah inti yang kita hadapi adalah refleksi.

  • Jadi, mari kita lihat sedikit kode reflektif sebagai pengingat di sini.

    fun createWithReflection(className: String): MyObject {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as MyObject
    }
  • Ini adalah fungsi pembuatan instance objek yang hanya meng-inflate objek tertentu, dan melakukannya secara reflektif.

  • Kelemahan dari hal ini, dalam konteks pengoptimalan DEX, adalah bahwa semua panggilan statis ini tertunda hingga runtime.

  • Hanya dengan melihat kode, bergantung pada asal nama class ini, pengoptimal R8 kami tidak dapat melihat apa yang terjadi di sini.

  • Ini adalah situasi yang merusak pengoptimalan Dex secara default, sehingga biasanya Anda harus menambahkan aturan Keep.

  • Sekarang, idealnya Anda hanya menghindari hal ini, bukan?

  • Idealnya, kita menghindari refleksi sepenuhnya, terutama sekarang pada tahun 2026 saat kita memiliki alat codegen yang luar biasa untuk serialisasi, untuk injeksi dependensi, yang dapat sepenuhnya menghilangkan kategori masalah ini.

  • Tapi, itu bukan semuanya, kan?

  • Kita masih memerlukan refleksi dalam kasus tertentu.

  • Jadi, mari kita pelajari bagaimana hal ini terjadi di library yang membengkak ini.

  • Jadi, berikut adalah salah satu contoh spesifik, dan ini adalah contoh yang umum, yaitu dependensi opsional.

    // 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
    }
  • Di sini, kita meng-inflate jenis tertentu yang tidak dapat diakses di classpath library, jadi kita harus melakukannya dengan string.

  • Hal ini merujuk pada class tertentu yang mungkin ada atau tidak ada pada saat library dibuat, sehingga sangat wajar jika library ini menggunakan refleksi untuk menemukan anggota di dalam library terkait.

  • Kasus lain yang kita miliki di sini adalah membuat instance subclass secara dinamis.

  • Jadi, terutama jika itu adalah sesuatu yang berasal dari file manifes atau sesuatu yang dikirim dari sisi server, kita memberi tahu aplikasi lokal, "Hei, tolong inf- inflate hal ini, buat hal ini nanti."

  • Lalu, kategori ketiga yang kita lihat di sini adalah serialisasi yang memalukan.

  • Maksud saya, saya memiliki serialisasi JSON yang terjadi dengan melakukan iterasi melalui semua anggota.

  • Hal ini sangat menjijikkan, tetapi, Anda tahu, hal semacam ini terkadang masih ada.

  • Tidak selalu diperbaiki pada saat yang diperlukan.

  • Namun, mari kita bahas apa hasil dari hal ini, bukan?

  • Seiring bertambahnya panggilan reflektif ini, Anda akan makin termotivasi untuk melakukan hal yang mudah, dan mengoptimalkan pengalaman developer daripada pengalaman pengguna.

  • Maksud saya, rasanya tidak enak, jadi saya akan menambahkan tugas, dan semuanya akan baik-baik saja.

    # TODO: clean this up
    -keep com.example.analytics.bloat.** { * };
  • Suatu hari nanti, saya akan datang dan memperbaikinya, bukan?

  • Jadi, mari kita bahas apa yang seharusnya kita lakukan, setidaknya sebelum hari ini, cara menulis aturan Keep yang ideal di sini.

    fun createEventFilter(className: String): EventFilter {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as? EventFilter
    }
  • Oke, bersiaplah.

  • Jadi, bagian pertama di sini adalah kita ingin mempertahankan class tertentu yang kita tampilkan dari fungsi reflektif ini.

    ### Keep the EventFilter
    -keep class com.example.analytics.bloat.EventFilter {
        *
    }
  • Sebenarnya, tidak, tunggu.

  • Tahu tidak?

  • Sebenarnya, jawaban ini salah.

  • Ini adalah class Kotlin, saya perlu memastikan bahwa class ini diketik dengan string, jadi saya akan memasukkan KT ke dalamnya.

    ### Keep the actual EventFilter
    -keep class com.example.analytics.bloat.EventFilterKt {
        *
    }
  • Dan sebenarnya, hal penting di sini adalah saya tidak benar-benar menampilkan EventFilter, saya sebenarnya menampilkan subclass EventFilter, jadi saya perlu mempertahankan class yang memperluas EventFilter agar dapat meng-inflate ini dengan benar.

    ### Keep the EventFilter subclasses
    -keep class ** extends com.example.analytics.bloat.EventFilterKt {
        *
    }
  • Dan sebenarnya, jika saya ingin melakukannya dengan lebih efisien, cara itu akan berhasil, tetapi untuk membuatnya lebih efisien, sebagai developer library yang baik, apa yang harus saya lakukan lebih lanjut?

  • Maksudnya, saya harus memberi tahu -keepclasseswithmembers, dan hanya menyimpan class yang memiliki konstruktor <init> tertentu yang saya gunakan untuk meng-inflate objek.

    ### Keep the EventFilter subclasses, if they have this constructor
    -keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
       void <init>();
    }
  • Dan sebenarnya, jika saya ingin benar-benar baik, saya akan menambahkan allowaccessmodification untuk memungkinkan pengoptimalan lebih lanjut terjadi pada semua API yang ditargetkan oleh aturan Keep ini karena, Anda tahu, saya tidak ingin mencegah pengoptimalan lebih lanjut dari R8, saya ingin mencoba dan menjadi warga negara yang baik.

    ### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
    -keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
       void <init>();
    }
  • Lalu, langkah terakhir yang hampir tidak dilakukan siapa pun, karena memerlukan sedikit pekerjaan tambahan, tetapi bagus untuk library, adalah membuatnya bersyarat.

    ### -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>();
    }
  • Sekarang, secara konseptual, Anda tidak perlu menyimpan semua subclass tersebut kecuali jika Anda benar-benar memanggil fungsi ini, dan itulah yang dilakukan pernyataan if ini di bagian atas aturan Keep.

  • Namun, Anda tahu, saya bisa meminta Anda mengacungkan tangan, tetapi saya tahu hampir tidak ada yang melakukan ini, bukan?

  • Misalnya, benar-benar menulisnya, menjadikannya bersyarat.

  • Jadi, bagaimana jika kita bisa membuatnya, Anda tahu, sedikit lebih baik?

  • Mari kita lihat dulu, seperti, oke.

  • Mungkin ada cara lain untuk melakukannya.

  • Jadi, kami membahas, bagaimana jika saya menulis aturan Keep yang sempit dan ditulis secara manual?

  • Secara teknis, hal ini mungkin saja dilakukan, tetapi sulit untuk ditulis, sulit untuk dibaca, dan sangat sulit untuk dikelola karena ada banyak bagian kecil yang diketik secara string di dalamnya yang mudah rusak jika terjadi sesuatu di bawahnya.

  • Di bawah sesuatu, di bawahnya, ada perubahan.

  • Dan kita punya alternatif.

  • Kita memiliki anotasi seperti @Keep.

  • Sekarang, @Keep sudah lama ada dalam paket anotasi AndroidX, tetapi sebenarnya agak tidak disarankan saat ini.

    @androidx.annotation.Keep
    class EventFilter { … }
    
    @androidx.annotation.Keep
    class SizeBasedEventFilter { … }
    
    @androidx.annotation.Keep
    class DropLogsEventFilter { … }
  • Ini bukan solusi yang bagus untuk developer library, dan sebenarnya dilarang secara internal, karena meskipun lebih mudah dikelola, solusi ini sangat tidak fleksibel, dan tidak memiliki beberapa pengoptimalan yang saya bicarakan sebelumnya.

  • LLM tidak dapat melakukannya.

  • Analisis ini tidak memungkinkan analisis situs panggilan tersebut.

  • Sekarang, jika kita melihat kasus tertentu yang kita miliki sebelumnya, "Oh, saya bisa menempatkan @Keep di EventFilter, saya baik-baik saja, bukan?"

    @Keep
    class EventFilter { … }
  • Tidak ada.

  • @Keep tidak benar-benar menargetkan subclass, melainkan hanya menargetkan class tersebut.

    @Keep
    class EventFilter { … }
    
    @Keep
    class SizeBasedEventFilter { … }
    
    @Keep
    class DropLogsEventFilter { … }
  • Jadi, sebenarnya saya perlu menargetkan semua subclass, dan sekarang saya benar-benar menulis lebih banyak anotasi @Keep daripada yang seharusnya saya lakukan dengan satu aturan Keep yang sulit ditulis.

  • Jadi, keduanya memiliki masalah, bukan?

  • Pengecualian ini mudah dilupakan, Anda terlambat mengetahuinya, dan secara default tidak bergantung pada pemanggil.

  • Khususnya untuk library, kami menginginkan cara yang lebih baik.

  • Kami ingin ada cara yang lebih mudah untuk mendapatkan hal-hal yang tepat secara langsung.

  • Salah satu masalah besar yang kami lihat di sini adalah hal-hal ini berada jauh dari refleksi yang sebenarnya kami lindungi.

  • Pada akhirnya, solusi yang kita inginkan adalah sesuatu yang berada tepat di, Anda tahu, kode refleksi yang bermasalah di sini yang sebenarnya meng-inflate hal-hal yang ditargetkan.

Pengantar Jetpack Keep

  • Jadi, mari kita mulai membahas Jetpack Keep, library dan plugin baru dari AndroidX yang dirancang untuk membantu mengatasi masalah ini.

  • Jadi, untuk menggunakan-- jadi seluruh tujuan Jetpack Keep adalah memulai dengan membuat aturan keep dari anotasi, tetapi bahkan lebih jauh dari itu, membuat anotasi langsung dari refleksi yang sudah ada di codebase Anda.

  • Jadi, penyiapannya sangat sederhana.

    plugins {
       id("com.android.library")
       id("androidx.annotation.keep")
    }
    
    dependencies {
        implementation("androidx.annotation.keep:keep")
        ...
    }
  • Ini adalah plugin Gradle, ini adalah library anotasi.

  • Namun, mari kita lihat bagaimana hal ini benar-benar berjalan-- berjalan pada waktu build.

  • Jadi, jika saya memiliki contoh sebelumnya di mana saya meng-inflate subclass dari jenis yang diketahui, apa yang harus saya lakukan?

  • Saya baru saja menjalankan lintFix.

    ./gradlew analyticsbloat:lintFix
  • Lint di sini menganalisis semua refleksi yang terjadi di dalam metode ini, dan mengatakan, "Hei, Anda memerlukan anotasi ini."

    @UsesReflectionToConstruct(
       classConstant = EventFilter::class,
       parameterTypes = []
    )
    fun createEventFilter(className: String): EventFilter {
       return Class.forName(className)
           .getDeclaredConstructor()
           .newInstance() as? EventFilter
    }
  • Anotasi ini secara tepat mengekspresikan apa yang dilakukan refleksi di dalam panggilan ini.

  • Ini berarti, "Saya meng-inflate class jenis ini".

  • Dalam kasus khusus ini, EventFilter adalah class yang diketahui.

  • Saya tidak menggunakan string untuk mereferensikannya karena saya tidak perlu melakukannya.

  • Itu adalah keamanan tambahan, dan-- Saya bahkan mengenkode parameterTypes sehingga menyatakan dengan tepat konstruktor mana yang dipertahankan.

  • Namun, apa fungsinya?

  • Misalnya, bagaimana hal ini memengaruhi build R8 akhir?

  • Nah, alat ini menghasilkan aturan keep, dan, Anda tahu, jika Anda melihat sebelumnya, ini adalah aturan keep ideal yang sama persis dengan yang kita miliki sebelumnya.

    # 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>();
    }
  • Model ini menargetkan kelas yang sama persis, subclass, dan bergantung pada titik refleksi ini.

  • Dan saya tidak perlu mengelola-- Saya tidak perlu mengkhawatirkan anotasi semua subclass.

  • Saya tidak perlu menyimpan file aturan keep terpisah di tempat lain.

  • Ini adalah cara yang jauh lebih mudah untuk mengelola aturan penyimpanan Anda.

  • Mari kita lihat kembali contoh dependensi opsional tersebut.

    @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()
    }
  • Sekarang, di sana, class tidak ada di jalur class, tetapi saya masih dapat mereferensikannya.

  • Saya melakukannya hanya dengan menggunakan string, tetapi semua hal lainnya, jenis parameter, dapat direferensikan oleh referensi class, jadi saya melakukannya.

  • Sekali lagi, kita membuat aturan keep yang ideal untuk kasus ini.

    # 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);
    }
  • Fitur ini bersifat kondisional, spesifik, dan ditargetkan, serta memungkinkan pengoptimalan jika memungkinkan pada class yang ditargetkan tersebut.

  • Berikut adalah refleksi pada kolom, dan dalam kasus khusus ini, saya melakukan iterasi pada semua kolom di dalam objek, tetapi di sini, sekali lagi, kami menanganinya dengan baik dengan mengatakan targetkan semua kolom dengan operator bintang ini.

    // 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
    }
  • Hal ini memungkinkan saya menargetkan semua kolom, dan lihat, ini menghasilkan aturan keep yang tepat yang Anda harapkan untuk, Anda tahu, refleksi yang sangat tidak menguntungkan yang seharusnya tidak ada di aplikasi saya, tetapi, hei, saya akan kembali dan membersihkannya, saya benar-benar berjanji.

    # 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>;
    }
  • Jadi, apa fungsi Jetpack Keep?

  • Hal ini mengurangi pemeliharaan aturan penyimpanan secara drastis.

  • Untuk semua hal yang dapat Anda buat secara otomatis, hal ini akan menghilangkannya dari area fokus dan area risiko Anda untuk mempertahankan aturan keep.

  • Alat ini juga memberi Anda determinisme lint.

  • Fitur ini tidak hanya memperbaiki masalah saat Anda melakukan iterasi dan mencoba bermigrasi, tetapi juga mendeteksi refleksi baru dan perubahan reflektif setiap kali kode berubah, dan keamanan tersebut sangatlah penting.

  • Dapatkah saya menggunakan fitur ini?

  • Jadi, apa saja dependensi di sini?

  • Plugin dan lint memerlukan AGP 8.7.

  • Seharusnya hampir semua orang.

  • Namun, fitur ini juga sangat fleksibel terkait tempat Anda dapat menggunakannya.

  • Anda dapat menggunakannya di dalam aplikasi.

  • Anda dapat menggunakannya di dalam library Android, AAR, library KMP, dan JAR, dengan sedikit pengaitan manual dalam hal ini.

  • Namun, fitur utamanya di sini adalah aman digunakan sebagai developer library.

  • Meskipun seseorang menggunakan versi R8 lama atau bahkan alat pihak ketiga yang menggunakan library Anda, kami tetap mengekspor aturan konsumen standar yang sebenarnya sehingga siapa pun masih dapat menggunakan Anda dan tidak perlu khawatir menggunakan semacam format baru yang tidak kompatibel.

Menganalisis aplikasi dengan Android CLI

  • Jadi, setelah membahas apa yang dapat dilakukan Jetpack Keep untuk membantu Anda mengoptimalkan dan mengurangi kekhawatiran dalam melindungi panggilan kode reflektif, mari kita bahas fitur baru yang baru-baru ini kami luncurkan di Android CLI untuk memeriksa kembali tahap rilis terakhir.

  • Breana sebelumnya menyebutkan bahwa ada alat di Play Findings yang akan memberi tahu Anda informasi tentang ukuran, ukuran DEX, dan persentase pengoptimalan.

  • Nah, sekarang kami mulai menyediakannya juga melalui Android CLI.

  • Alat ini menggunakan logika yang sama persis dengan Google Play Store, sehingga tanpa harus mengupload app bundle ke Google Play Store, Anda dapat meminta CLI menganalisis app bundle Anda.

    $ android analyze <mybundle.aab>
    $ android analyze <mybundle.apk>
  • Anda juga akan mendapatkan informasi yang sama persis yang dilaporkan kepada Anda secara langsung dan segera dengan analisis lokal.

  • Anda dapat melihat di sini, dalam kasus khusus ini, aplikasi ini mendapatkan peringatan karena memiliki tingkat pengoptimalan yang sangat rendah, dan juga diberi tahu, "Hei, ini ukuran DEX Anda, dan ini persentase pengoptimalan Anda", persis seperti yang akan disampaikan Google Play Store kepada Anda.

  • Namun, di sini kami juga menyediakan output JSON, sehingga jika Anda ingin menulis skrip untuk mengotomatiskan pengujian regresi terhadap penurunan drastis nilai-nilai ini, karena, sekali lagi, ini adalah angka yang diterapkan Play, Anda ingin melacaknya, Anda dapat melakukannya dengan sangat mudah menggunakan nilai JSON ini-- laporan JSON ini.

    {
      "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"
      }
  • Di sini kita melihat angka yang sama persis juga ada.

  • Sekarang, ini bukan satu-satunya fitur android analyze.

  • android analyze juga dapat berjalan di library.

    $ android analyze <library.aar>
    $ android analyze <library.jar>
  • Dan seperti yang disebutkan Breana, ada banyak masalah dengan library yang berjalan dengan aturan keep yang tidak menguntungkan di dalamnya.

  • Jadi, mari kita gunakan ini untuk melihat beberapa masalah tersebut.

  • Jadi, saat saya menjalankan ini di AAR tertentu yang saya miliki, saya dapat melihat, hei, library ini memiliki sejumlah masalah yang baru saja disorot di bagian paling atas.

  • Library ini memiliki rasio penyimpanan yang tinggi, yang berarti library ini menyimpan terlalu banyak dengan aturan penyimpanan yang tidak terbatas.

  • Library ini berisi aturan keep yang luas yang mencegahnya dioptimalkan, dan ada beberapa angka di bawah yang memberi tahu Anda seberapa buruk hal itu, dan library ini juga berisi aturan keep yang, mungkin yang paling menakutkan dari semua ini, tidak menargetkan apa pun, yang mungkin berarti library ini menargetkan orang lain dengan cara yang tidak terbatas.

  • Code smell yang sangat berbahaya.

  • Jadi, Anda dapat memeriksa semua itu di AAR, terutama sebagai penayang library, ini adalah cara yang bagus untuk memastikan bahwa Anda tidak mengirimkan aturan keep yang buruk, dan bahwa Anda tidak membiarkannya bocor saat seseorang yang mungkin tidak terlalu ahli dalam menulis aturan keep sedang mengerjakan library.

  • Di sini, Anda dapat melihat bahwa kami juga menghasilkan rasio pertahankan.

  • Ini adalah cara yang baik untuk memikirkan kualitas keseluruhan galeri foto Anda.

  • Jika saya hanya mengoptimalkan satu library sepenuhnya secara mandiri, tanpa referensi, dengan R8, saya akan melihatnya menjadi nol byte, bukan?

  • Seharusnya pada dasarnya kosong karena tidak ada yang dipertahankan di dalamnya.

  • Itu akan ideal.

  • Dalam hal ini, tidak.

  • Anda dapat melihat ukuran R8 dan D8 secara langsung.

  • Bahkan, kami juga menyoroti beberapa aturan keep yang paling buruk agar lebih terlihat oleh Anda.

  • Formatnya juga JSON, jadi sekali lagi, baik Anda adalah developer aplikasi yang mencoba membatasi update library atau developer library yang mencoba mencegah regresi besar dalam kualitas library, Anda dapat melakukan analisis yang sama dan mencegah masalah ini terjadi dengan skrip sederhana yang menggabungkan 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.** { *; }",
                  },
                  ...
                ]
              },
              ...
    }
  • Jadi, jika Anda menemukan masalah pada library, pertama-tama, coba update library tersebut.

  • Seperti yang disebutkan Breana sebelumnya, kami telah memperbaiki lebih dari selusin library Google untuk memperbaiki beberapa aturan keep yang sangat disayangkan dan ada library lain di luar sana yang telah diperbaiki, hanya saja tidak diupdate oleh aplikasi yang menggunakannya.

  • Ini adalah langkah pertama yang sangat bagus.

  • Kedua, Anda tahu, Anda dapat mencari masalah dan menghubungi developer library dan, Anda tahu, mungkin melampirkan laporan penganalisis yang memberi tahu mereka, "Hei, Anda menyimpan terlalu banyak.

  • Bantu saya mengoptimalkan aplikasi saya."

Poin-poin penting dan referensi

  • Jadi, sebagai rangkuman, kita telah membahas R8 Config Analyzer, yang membantu Anda melihat semua yang terjadi dalam build R8, semua yang menginformasikan apa yang dapat dioptimalkan dan apa yang tidak.

  • Kita telah membahas Jetpack Keep, library baru yang membantu Anda menulis aturan keep secara otomatis tanpa perlu repot.

  • Dan Android CLI, alat baru untuk membantu Anda memeriksa dan memastikan, memeriksa ulang langkah terakhir, dan memastikan Anda, agen Anda, engineer junior di tim Anda tidak merusak apa pun untuk konsumen Anda.

  • Jadi, jika Anda ingin mempelajari lebih lanjut, ada dokumentasi untuk R8, penganalisis konfigurasi, dan Jetpack Keep.

  • Ada juga dokumentasi tentang Android CLI yang membahas perintah analisis dan cara menggunakannya.

  • Terima kasih banyak.

Kode

Cuplikan kode

Cuplikan Skrip Kotlin pada menit 05.15 di "Pengantar pengoptimalan DEX"
android {
    buildTypes {
        release {
            optimization {
                // no need to specify default
                // rules and resource shrinking
                enabled = true
            }
        }
    }
    ...
}
Cuplikan Skrip Kotlin pada 05:15 di "Pengantar pengoptimalan 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
            )
            ...
        }
    }
    ...
}
Cuplikan shell pada menit 13:19 di "Studi kasus: Pengoptimalan WHOOP"
$ android skills list
$ android skills add r8-analyzer
Cuplikan Kotlin pada menit 14:41 di "Tantangan refleksi dalam pengoptimalan DEX"
fun createWithReflection(className: String): MyObject {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as MyObject
}
Cuplikan Kotlin pada menit 15:40 dalam "Tantangan refleksi dalam pengoptimalan 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
}
Cuplikan shell pada menit 16:52 di "Tantangan refleksi dalam pengoptimalan DEX"
# TODO: clean this up
-keep com.example.analytics.bloat.** { * };
Cuplikan Kotlin pada menit 16:59 di "Tantangan refleksi dalam pengoptimalan DEX"
fun createEventFilter(className: String): EventFilter {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as? EventFilter
}
Cuplikan shell pada 17:07 di "Tantangan refleksi dalam pengoptimalan DEX"
### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
    *
}
Cuplikan shell pada menit 17:15 di "Tantangan refleksi dalam pengoptimalan DEX"
### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
    *
}
Cuplikan shell pada 17:19 di "Tantangan refleksi dalam pengoptimalan DEX"
### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
    *
}
Cuplikan shell pada menit 17:40 di "Tantangan refleksi dalam pengoptimalan DEX"
### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
   void <init>();
}
Cuplikan shell pada menit 17:50 di "Tantangan refleksi dalam pengoptimalan DEX"
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
   void <init>();
}
Cuplikan shell pada 18:03 di "Tantangan refleksi dalam pengoptimalan 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>();
}
Cuplikan Kotlin pada 18:57 di "Tantangan refleksi dalam pengoptimalan DEX"
@androidx.annotation.Keep
class EventFilter { … }

@androidx.annotation.Keep
class SizeBasedEventFilter { … }

@androidx.annotation.Keep
class DropLogsEventFilter { … }
Cuplikan Kotlin pada menit 19:22 di "Tantangan refleksi dalam pengoptimalan DEX"
@Keep
class EventFilter { … }
Cuplikan Kotlin pada menit 19:29 di "Tantangan refleksi dalam pengoptimalan DEX"
@Keep
class EventFilter { … }

@Keep
class SizeBasedEventFilter { … }

@Keep
class DropLogsEventFilter { … }
Cuplikan Skrip Kotlin pada menit 20:38 di "Pengantar Jetpack Keep"
plugins {
   id("com.android.library")
   id("androidx.annotation.keep")
}

dependencies {
    implementation("androidx.annotation.keep:keep")
    ...
}
Cuplikan shell pada 20:54 di "Pengantar Jetpack Keep"
./gradlew analyticsbloat:lintFix
Cuplikan Kotlin pada 20:55 di "Pengantar Jetpack Keep"
@UsesReflectionToConstruct(
   classConstant = EventFilter::class,
   parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
   return Class.forName(className)
       .getDeclaredConstructor()
       .newInstance() as? EventFilter
}
Cuplikan shell pada 21:25 di "Pengantar 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>();
}
Cuplikan Kotlin pada menit 21:52 di "Pengantar 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()
}
Cuplikan shell pada 22:05 di "Pengantar 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);
}
Cuplikan Kotlin pada menit 22:16 di "Pengantar 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
}
Cuplikan shell pada 22:27 di "Pengantar 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>;
}
Cuplikan shell pada menit 24:22 di "Menganalisis aplikasi dengan Android CLI"
$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>
Cuplikan JSON pada 24:52 di "Menganalisis aplikasi dengan 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"
  }
Cuplikan shell pada menit 25:17 di "Menganalisis aplikasi dengan Android CLI"
$ android analyze <library.aar>
$ android analyze <library.jar>
Cuplikan JSON pada menit 26:46 di "Menganalisis aplikasi dengan 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.** { *; }",
              },
              ...
            ]
          },
          ...
}