
Dalam artikel ini saya akan berbicara tentang bagaimana saya memecahkan masalah yang saya temui di bagian sebelumnya selama implementasi proyek .
Pertama, ketika menganalisis kelas yang dapat ditransformasikan, Anda perlu entah bagaimana memahami apakah kelas ini adalah penerus dari Activity
atau Fragment
, sehingga kami dapat dengan yakin mengatakan bahwa kelas tersebut cocok untuk transformasi kami.
Kedua, dalam file .class
ditransformasikan untuk semua bidang dengan anotasi @State
, @State
harus secara eksplisit menentukan jenis untuk memanggil metode yang sesuai pada bundel untuk menyimpan / mengembalikan keadaan, dan Anda dapat menentukan jenisnya dengan menganalisis semua orangtua kelas dan antarmuka yang mereka implementasikan.
Jadi, Anda hanya perlu mengurai pohon sintaksis abstrak dari file yang diubah.
Analisis AST
Untuk menganalisis kelas untuk pewarisan dari beberapa kelas dasar (dalam kasus kami, ini adalah Activity/Fragment
), cukup untuk memiliki path lengkap ke file .class
dipelajari. Lebih lanjut, itu semua tergantung pada implementasi transformator: memuat kelas melalui ClassLoader
, atau menganalisis melalui ASM menggunakan ClassReader
dan ClassVisitor
, mendapatkan semua informasi yang diperlukan tentang kelas.
Akses file
Ingatlah bahwa kelas yang kita butuhkan dapat ditempatkan di luar ruang lingkup proyek, tetapi di beberapa perpustakaan (misalnya, Activity
ada di Android SDK). Oleh karena itu, sebelum memulai transformasi, Anda perlu mendapatkan daftar path ke semua file .class
tersedia.
Untuk melakukan ini, buat perubahan kecil pada Transformer :
@Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) }
Metode getReferencedScopes
memungkinkan Anda untuk mengakses file dari cakupan yang ditentukan, dan ini hanya akan dibaca akses tanpa kemungkinan transformasi. Apa yang kita butuhkan. Dalam metode transform
, file-file ini dapat diperoleh dengan cara yang hampir sama dengan dari cakupan utama:
transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput ->
Dan satu hal lagi, file dari Andoid SDK perlu diterima secara terpisah:
project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString()
Terima kasih Google, sangat nyaman.
Isi ClassPool
Mengisi daftar semua file .class
tersedia untuk kami dengan tangan Anda agak suram: karena kami mendapatkan direktori atau file jar
sebagai input, Anda harus berkeliling semua dan mendapatkan file .class
dengan benar. Di sini saya menggunakan perpustakaan javassist yang disebutkan sebelumnya. Dia melakukan semuanya di bawah tenda dan ditambah memiliki api yang nyaman untuk bekerja dengan kelas yang diterima. Pada akhirnya, Anda hanya perlu mentransfer path ke file dan mengisi ClassPool
:
ClassPool.getDefault().appendClassPath(" ")
Sebelum memulai transformasi, ClassPool
diisi dari semua sumber file yang mungkin:
fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool)
Detail dalam trafo .
Analisis Kelas
Sekarang karena ClassPool
penuh, masih harus menghilangkan penjelasan @Stater
. Untuk melakukan ini, hapus centang pada metode visitAnnotation
dari pengunjung kami dan cukup periksa superclass dari setiap kelas untuk keberadaan Activity/Fragment
dalam hierarki warisan. Mendapatkan kelas apa pun dengan nama dari kelas pool javassist sangat sederhana:
CtClass currentClass = ClassPool.getDefault().get(className.replace("/", "."))
Dan sudah dengan CtClass
Anda bisa mendapatkan currentClass.superclass
atau currentClass.interfaces
. Melalui perbandingan superclass, saya melakukan pemeriksaan aktivitas / fragmen.
Dan akhirnya, untuk menghilangkan StateType
dan tidak menentukan jenis bidang yang akan disimpan secara eksplisit, saya melakukan hal yang sama. Untuk kenyamanan, seorang mapper (dengan tes ) ditulis yang mem-parsing deskriptor saat ini ke dalam tipe yang didukung oleh bundel.
Akibatnya, transformasi kode tidak berubah, hanya mekanisme untuk menentukan jenis variabel yang berubah.
Jadi, menggabungkan 2 pendekatan untuk bekerja dengan file .class
, saya dapat menerapkan ide asli menyimpan variabel dalam sebuah bundel menggunakan hanya satu penjelasan.
Performa
Kali ini, untuk menguji kinerja, saya menghubungkan plug-in ke proyek yang benar-benar bekerja, karena pengisian kelas kumpulan tergantung pada jumlah file dalam proyek dan berbagai perpustakaan.
Memeriksa semua ini melalui ./gradlew clean build --scan
. Transformasi transformClassesWithStaterTransformForDebug
membutuhkan waktu sekitar 2,5 detik. Saya mengukur dengan satu Activity
dengan bidang 50 @State
dan dengan 10 Activity
, kecepatannya tidak banyak berubah.