Transformasi Kode di Android 2. Analisis AST



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 -> // .  directoryInput.file.absolutePath } transformInput.jarInputs.each { jarInput -> // .  jarInput.file.absolutePath } } 

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.

Source: https://habr.com/ru/post/id470209/


All Articles