
سأتحدث في هذا المقال عن كيفية حل المشكلات التي واجهتها في الجزء السابق أثناء تنفيذ المشروع .
أولاً ، عند تحليل فئة قابلة للتحويل ، يجب أن تفهم بطريقة ما ما إذا كانت هذه الفئة هي خليفة Activity
أو Fragment
، حتى يمكننا أن نقول بثقة أن الفصل مناسب لتحويلنا.
ثانياً ، في ملف .class
@State
لجميع الحقول مع التعليق التوضيحي @State
، تحتاج إلى تحديد النوع بشكل صريح من أجل استدعاء الأسلوب المقابل في الحزمة لحفظ / استعادة الحالة ، ويمكنك تحديد النوع بالتحديد عن طريق تحليل جميع آباء الفئة والواجهات التي يقومون بتنفيذها.
وبالتالي ، تحتاج فقط إلى أن تكون قادراً على تحليل شجرة بناء الجملة من الملفات المحولة.
تحليل AST
من أجل تحليل الفصل للميراث من فئة أساسية (في حالتنا هو Activity/Fragment
) ، يكفي أن يكون المسار الكامل إلى ملف .class
قيد الدراسة. علاوة على ذلك ، كل هذا يتوقف على تطبيق المحول: إما تحميل الفئة من خلال ClassLoader
، أو تحليلها من خلال ASM باستخدام ClassReader
و ClassVisitor
، والحصول على جميع المعلومات اللازمة حول الفئة.
ملف الوصول
ضع في اعتبارك أن الفصل الذي نحتاج إليه يمكن أن يكون خارج نطاق المشروع ، ولكن في بعض المكتبات (على سبيل المثال ، يكون Activity
في Android SDK). لذلك ، قبل بدء التحول ، تحتاج إلى الحصول على قائمة المسارات إلى جميع ملفات .class
المتاحة.
للقيام بذلك ، قم بإجراء تغييرات صغيرة على المحولات :
@Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) }
تسمح لك طريقة getReferencedScopes
بالوصول إلى الملفات من النطاقات المحددة ، وهذا سيكون مجرد وصول للقراءة دون إمكانية التحول. فقط ما نحتاجه. في طريقة transform
، يمكن الحصول على هذه الملفات بنفس الطريقة التي يتم الحصول عليها من النطاقات الرئيسية:
transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput ->
وهناك شيء آخر ، يجب استلام الملفات من Andoid SDK بشكل منفصل:
project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString()
شكرا جوجل ، مريحة للغاية.
تعبئة الطبقة
يعد ملء قائمة بجميع ملفات .class
المتاحة لنا بأيدي كئيبة إلى حد ما: نظرًا لأننا نحصل على أدلة أو ملفات jar
كمدخلات ، فأنت بحاجة إلى الالتفاف عليها جميعًا والحصول على ملفات .class
بشكل صحيح. هنا استعملت مكتبة javassist المذكورة سابقًا. انها تفعل كل شيء تحت غطاء محرك السيارة و plus لديه api مريحة للعمل مع الطبقات المستلمة. في النهاية ، تحتاج فقط إلى نقل المسار إلى الملفات وملء ClassPool
:
ClassPool.getDefault().appendClassPath(" ")
قبل بدء التحويل ، يتم تعبئة ClassPool
من جميع مصادر الملفات الممكنة:
fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool)
التفاصيل في المحولات .
تحليل الصف
الآن بعد ClassPool
، يبقى التخلص من التعليقات التوضيحية @Stater
. للقيام بذلك ، قم بإزالة علامة الاختيار في أسلوب visitAnnotation
الخاص visitAnnotation
، وفحص ببساطة الفئة الفائقة لكل فئة بحثًا عن وجود Activity/Fragment
في التسلسل الهرمي للميراث. الحصول على أي فئة بالاسم من فئة pool javassist بسيط جدًا:
CtClass currentClass = ClassPool.getDefault().get(className.replace("/", "."))
وبالفعل مع CtClass
يمكنك الحصول على CtClass
أو currentClass.interfaces
. من خلال مقارنة الطبقة الفائقة ، قمت بفحص النشاط / الشظية.
وأخيرًا ، للتخلص من StateType
وعدم تحديد نوع الحقل الذي يجب حفظه بشكل صريح ، فعلت الشيء نفسه تقريبًا. للراحة ، تمت كتابة معين (مع الاختبارات ) يقوم بتوزيع الواصف الحالي في النوع المدعوم من قبل الحزمة.
نتيجة لذلك ، لم يتغير تحويل الشفرة ؛ لقد تم تغيير آلية تحديد نوع المتغير فقط.
لذلك ، بدمج .class
للعمل مع ملفات .class
، تمكنت من تنفيذ الفكرة الأصلية المتمثلة في حفظ المتغيرات في الحزم باستخدام تعليق توضيحي واحد فقط.
إنتاجية
هذه المرة ، لاختبار الأداء ، قمت بتوصيل المكون الإضافي بمشروع حقيقي ، لأن ملء فئة البلياردو يعتمد على عدد الملفات في المشروع والمكتبات المختلفة.
فحص كل هذا من خلال ./gradlew clean build --scan
. تأخذ transformClassesWithStaterTransformForDebug
حوالي 2.5 ثانية. قمت بقياس Activity
واحد مع 50 حقل @State
ومع 10 مثل هذه Activity
، لا تتغير السرعة كثيرًا.