close

這是 JsonChao 的第257期分享

前言

在本系列的上一篇文章中,我們對 APT 技術進行了講解,還沒有看過上一篇文章的朋友,建議先去閱讀

Android APT 技術探索之路
sweetying,公眾號:JsonChaoAndroid APT 技術探索之路

接下來,我們就使用 APT 技術來進行實戰應用。

Github Demo 地址:https://github.com/sweetying520/AptDemo , 大家可以看 Demo 跟隨我的思路一起分析。

回顧

在本系列的開篇,我講了在項目實踐過程中做的一個布局優化,Android 中少量的系統控件是通過 new 的方式創建出來的,而大部分控件如 androidx.appcompat.widget 下的控件,自定義控件,第三方控件等等,都是通過反射創建的。大量的反射創建多多少少會帶來一些性能問題,因此我們需要去解決反射創建的問題,我的解決思路是:

1、通過編寫 Android 插件獲取 Xml 布局中的所有控件

2、拿到控件後,通過 APT 生成用 new 的方式創建 View 的類

3、最後通過反射獲取當前類並在基類裡面完成替換

一、準備 Android 插件生成的文件

其中 1 的具體流程是:通過 Android 插件獲取所有 Xml 布局中的控件名稱,並寫入到一個.txt文件中,因 Gradle 系列還沒講,這裡我們假設所有的控件名稱已經寫入到.txt文件,如下:

image-20210629191446005

上述文件我們可以看到:

1、一些不帶 . 的系統控件,如 TextView,ImageView 。系統會默認給我們通過 new 的方式去創建,且替換為了androidx.appcompat.widget包下的控件,例如:TextView -> AppCompatTextView ,ImageView -> AppCompatImageView

2、帶 . 的控件。可能為 androidx.appcompat.widget 下的控件,自定義控件,第三方控件等等,這些控件如果我們不做處理,系統會通過反射去創建。因此我們主要是針對這些控件去做處理

注意:我這裡在根目錄下創建了一個 all_view_name.txt 的文件,然後放入了一些 View 的名稱,這裡只是方便我們演示。實際上用 Android 插件去生成的文件我們一般會指定放在 app 的 /build目錄下,這樣我們在 clean 的時候就能順帶把它給幹掉

現在 1 完成了,接下來 2 和 3 就回到了我們熟悉的 APT 流程,我們需要讀取該文件,通過 APT 生成相應的類,最後使用這個類的功能就 OK 了,還不熟悉 APT 的,先去學習一波 傳送門

還是基於上篇文章的工程進行實操,為了方便後續流程的講解,我還是貼出上篇文章的工程圖:

image-20210627182425586二、apt-annotation 註解編寫

編寫註解,如下:

@Inherited@Documented@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public@interfaceViewCreator{}三、規定生成的類模版,為後續自動生成代碼做準備

在實際工作中,我們一般會這麼做:

1、將需要生成的類文件實現某個定義好的接口,通過接口代理來使用

2、規定生成的 Java 類模版,根據模版去進行生成代碼邏輯的編寫

1、將需要生成的類文件實現某個定義好的接口,通過接口代理來使用

關於接口,我們一般會放到 apt-api 這個 Module 中

2、規定生成的 Java 類模版,根據模版去進行生成代碼邏輯的編寫

假設我們需要生成的 Java 類模版如下:

packagecom.dream.aptdemo;publicclassMyViewCreatorImplimplementsIMyViewCreator{@OverridepublicViewcreateView(Stringname,Contextcontext,AttributeSetattr){Viewview=null;switch(name){case"androidx.core.widget.NestedScrollView":view=newNestedScrollView(context,attr);break;case"androidx.constraintlayout.widget.ConstraintLayout":view=newConstraintLayout(context,attr);break;case"androidx.appcompat.widget.ButtonBarLayout":view=newButtonBarLayout(context,attr);break;//...default:break;}returnview;}

根據上面這些信息,我們就可以進行自動生成代碼邏輯的編寫了

四、apt-processor 自動生成代碼

這裡你就對着上面給出的代碼模版,通過 javapoet 框架編寫相應的代碼生成邏輯即可,對 javapoet 不熟的趕緊去學習一波 傳送門

@AutoService(Processor.class)@SupportedAnnotationTypes("com.dream.apt_annotation.ViewCreator")@SupportedSourceVersion(SourceVersion.RELEASE_8)publicclassMyViewCreatorProcessorextendsAbstractProcessor{/**文件生成器*/privateFilermFiler;@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentprocessingEnv){super.init(processingEnv);mFiler=processingEnv.getFiler();}@Overridepublicbooleanprocess(Set<?extendsTypeElement>annotations,RoundEnvironmentroundEnv){//從文件中讀取控件名稱,並轉換成對應的集合Set<String>mViewNameSet=readViewNameFromFile();//如果獲取的控件名稱集合為空,則終止流程if(mViewNameSet==null||mViewNameSet.isEmpty()){returnfalse;}//獲取使用了註解的元素Set<?extendsElement>elementsAnnotatedWith=roundEnv.getElementsAnnotatedWith(ViewCreator.class);for(Elementelement:elementsAnnotatedWith){System.out.println("Hello"+element.getSimpleName()+",歡迎使用APT");startGenerateCode(mViewNameSet);//如果有多個地方標註了註解,我們只讀取第一次的就行了break;}returntrue;}/***開始執行生成代碼的邏輯**@parammViewNameSet控件名稱集合*/privatevoidstartGenerateCode(Set<String>mViewNameSet){System.out.println("開始生成Java類...");System.out.println("afewmomentlater...");//===================================構建方法start======================================//1、構建方法:方法名,註解,修飾符,返回值,參數ClassNameviewType=ClassName.get("android.view","View");MethodSpec.BuildermethodBuilder=MethodSpec//方法名.methodBuilder("createView")//註解.addAnnotation(Override.class)//修飾符.addModifiers(Modifier.PUBLIC)//返回值.returns(viewType)//第一個參數.addParameter(String.class,"name")//第二個參數.addParameter(ClassName.get("android.content","Context"),"context")//第三個參數.addParameter(ClassName.get("android.util","AttributeSet"),"attr");//2、構建方法體methodBuilder.addStatement("$Tview=null",viewType);methodBuilder.beginControlFlow("switch(name)");//循環遍歷控件名稱集合for(StringviewName:mViewNameSet){//針對包含.的控件名稱進行處理if(viewName.contains(".")){//分離包名和控件名,如:androidx.constraintlayout.widget.ConstraintLayout//packageName:androidx.constraintlayout.widget//simpleViewName:ConstraintLayoutStringpackageName=viewName.substring(0,viewName.lastIndexOf("."));StringsimpleViewName=viewName.substring(viewName.lastIndexOf(".")+1);ClassNamereturnType=ClassName.get(packageName,simpleViewName);methodBuilder.addCode("case$S:\n",viewName);methodBuilder.addStatement("\tview=new$T(context,attr)",returnType);methodBuilder.addStatement("\tbreak");}}methodBuilder.addCode("default:\n");methodBuilder.addStatement("\tbreak");methodBuilder.endControlFlow();methodBuilder.addStatement("returnview");MethodSpeccreateView=methodBuilder.build();//===================================構建方法end======================================//===================================構建類start======================================TypeSpecmyViewCreatorImpl=TypeSpec.classBuilder("MyViewCreatorImpl")//類修飾符.addModifiers(Modifier.PUBLIC)//實現接口.addSuperinterface(ClassName.get("com.dream.apt_api","IMyViewCreator"))//添加方法.addMethod(createView).build();//===================================構建類end========================================//===================================指定包路徑,構建文件體start=========================//指定類包路徑JavaFilejavaFile=JavaFile.builder("com.dream.aptdemo",myViewCreatorImpl).build();//生成文件try{javaFile.writeTo(mFiler);System.out.println("生成成功...");}catch(IOExceptione){e.printStackTrace();System.out.println("生成失敗...");}//===================================指定包路徑,構建文件體end============================}/***從文件中讀取控件名稱,並轉換成對應的集合*/privateSet<String>readViewNameFromFile(){try{//獲取存儲控件名稱的文件Filefile=newFile("/Users/zhouying/AndroidStudioProjects/AptDemo/all_view_name.txt");Propertiesconfig=newProperties();config.load(newFileInputStream(file));//獲取控件名稱集合returnconfig.stringPropertyNames();}catch(IOExceptione){e.printStackTrace();}returnnull;}}

上述生成代碼的邏輯寫了詳細的注釋,主要就是對 javapoet 框架的一個應用

代碼生成好了,接下來就需要提供給上層使用

五、apt-api 業務封裝供上層使用1、定義一個接口, apt-api 和 apt-processor 都會使用到//定義一個接口publicinterfaceIMyViewCreator{/***通過new的方式創建View**@paramname控件名稱*@paramcontext上下文*@paramattributeSet屬性*/ViewcreateView(Stringname,Contextcontext,AttributeSetattributeSet);}2、反射獲取生成的類,提供相應的代理類供上層調用publicclassMyViewCreatorDelegateimplementsIMyViewCreator{privateIMyViewCreatormIMyViewCreator;//==================================單例start=====================================@SuppressWarnings("all")privateMyViewCreatorDelegate(){try{//通過反射拿到Apt生成的類ClassaClass=Class.forName("com.dream.aptdemo.MyViewCreatorImpl");mIMyViewCreator=(IMyViewCreator)aClass.newInstance();}catch(Throwablet){t.printStackTrace();}}publicstaticMyViewCreatorDelegategetInstance(){returnHolder.MY_VIEW_CREATOR_DELEGATE;}privatestaticfinalclassHolder{privatestaticfinalMyViewCreatorDelegateMY_VIEW_CREATOR_DELEGATE=newMyViewCreatorDelegate();}//==================================單例end=======================================/***通過生成的類創建View**@paramname控件名稱*@paramcontext上下文*@paramattributeSet屬性*@returnView*/@OverridepublicViewcreateView(Stringname,Contextcontext,AttributeSetattributeSet){if(mIMyViewCreator!=null){returnmIMyViewCreator.createView(name,context,attributeSet);}returnnull;}}

到這裡我們布局優化流程差不多就要結束了,接下來就是上層調用

六、app 上層調用1、在創建的 MyApplication 上添加註解

關於註解你可以添加在其他地方,因為我註解處理器裡面做了邏輯判斷,只會讀取第一次的註解。為了對應,我選擇把註解加到 MyApplication 中,如下圖:

image-202106291925198932、最後在 MainActviity 中加入替換 View 的邏輯

如下:

//...publicclassMainActivityextendsAppCompatActivity{//...@Nullable@OverridepublicViewonCreateView(@NonNullStringname,@NonNullContextcontext,@NonNullAttributeSetattrs){//1、優先使用我們生成的類去進行View的創建Viewview=MyViewCreatorDelegate.getInstance().createView(name,context,attrs);if(view!=null){returnview;}//2、一些系統的View,則走系統的一個創建流程returnsuper.onCreateView(name,context,attrs);}}

注意:一般我們會把替換 View 的邏輯放到基類裡面

七、效果驗證

運行項目

1、先看下我們打印的日誌,如下圖:

image-20210629195411055

2、再看一眼我們生成的 Java 類文件,如下圖:

image-20210629194711378

3、最後 debug 項目跟下流程,發現和我們預期的一致,如下圖:

image-20210629194101025

至此,需求完結~

八、總結

本篇文章講的一些重點內容:

1、通過 APT 讀取文件獲取所有的控件名稱並生成 Java 類

2、通過接口代理,合理的業務封裝提供給上層調用

3、在上層 Application 裡面進行註解,在 Activity 中進行 View 控件的替換

4、實際完成後的一個效果驗證

好了,本系列文章到這裡就結束了,希望能給你帶來幫助 🤝

END


往期推薦



聊聊職場中最重要的兩個核心能力

你真的會休息嗎?如何開始正確的休息?

國慶返工日,捲起來!

如何系統構建你的思維認知模型?

如何從零到一掌控習慣?

點擊下方卡片關注JsonChao,為你構建一套

未來技術人必備的底層能力系統


▲點擊上方卡片關注JsonChao,構建一套

未來Android開發必備的知識體系

歡迎把文章分享到朋友圈

年度成長社群

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()