今天開(kāi)始架構(gòu)師之路寄月!分為6節(jié)課焚辅,以手寫retofit ,Butterknife,Arount,Dagger2,hilit,ASM ,AOP為主
****APT :處理提取和處理 Annotation 的代碼統(tǒng)稱為(Annotation Processing Tool)段只。
作用
使用APT的優(yōu)點(diǎn)就是方便、簡(jiǎn)單,可以少些很多重復(fù)的代碼。
用過(guò)ButterKnife侦香、Dagger、EventBus等注解框架的同學(xué)就能感受到纽疟,利用這些框架可以少些很多代碼罐韩,只要寫一些注解就可以了。
編寫注解處理器
和運(yùn)行時(shí)注解的解析不一樣污朽,編譯時(shí)注解的解析需要我們自己去實(shí)現(xiàn)一個(gè)注解處理器散吵。
注解處理器(Annotation Processor)是javac的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解(Annotation)。一個(gè)注解的注解處理器矾睦,以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入晦款,生成文件(通常是.java文件)作為輸出。而且這些生成的Java文件同咱們手動(dòng)編寫的Java源代碼一樣可以調(diào)用枚冗。(注意:不能修改已經(jīng)存在的java文件代碼)缓溅。
注解處理器所做的工作,就是在代碼編譯的過(guò)程中赁温,找到我們指定的注解坛怪。然后讓我們更加自己特定的邏輯做出相應(yīng)的處理(通常是生成JAVA文件)。
注解處理器的寫法有固定套路的股囊,兩步:
注冊(cè)注解處理器(這個(gè)注解器就是我們第二步自定義的類)袜匿。
自定義注解處理器類繼承AbstractProcessor。
如何看:java里面的jdk稚疹。會(huì)在resources的里面有META-INF居灯。里面會(huì)有注解處理器的文件!javax.annotation.
所以apt用的是java的lib内狗,不是Android 怪嫌!
注解處理器會(huì)在路徑Build/classes/java/main/com/META-INF/Services/javax_anontation_process.
APT本質(zhì):生成java類
APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具其屏,確切的說(shuō)它是javac的一個(gè)工具喇勋,它用來(lái)在編譯時(shí)掃描和處理注解。注解處理器以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入偎行,生成.java文件作為輸出川背。
簡(jiǎn)單來(lái)說(shuō)就是在編譯期,通過(guò)注解生成.java文件蛤袒。
詳細(xì)解釋: 它對(duì)源代碼文件進(jìn)行檢測(cè)找出其中的Annotation熄云,使用Annotation進(jìn)行額外的處理。 Annotation處理器在處理Annotation時(shí)可以根據(jù)源文件中的Annotation生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation處理器的編寫者決定)妙真,
APT還會(huì)編譯生成的源文件和原來(lái)的源文件缴允,將它們一起生成class文件。
在這里我們將在每一個(gè)業(yè)務(wù)組件的 build.gradle 都引入ActivityRouter 的 Annotation處理器珍德,我們將會(huì)在聲明組件和Url的時(shí)候使用练般,annotationProcessor是Android官方提供的Annotation處理器插件,代碼如下:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
}
[圖片上傳失敗...(image-c4ef24-1640265031541)]
JavaPoet
更好的方案:通過(guò)javapoet
可以更加簡(jiǎn)單得生成這樣的Java代碼锈候。(后面會(huì)說(shuō)到)
JavaPoet是square推出的開(kāi)源java代碼生成框架薄料,提供Java Api生成.java源文件
需要添加JavaPoet的依賴
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">implementation'com.squareup:javapoet:1.9.0'</pre>
javapoet的詳細(xì)內(nèi)容:
1.方法名
2.返回值
3.打印
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("S)", System.class, "Hello, JavaPoet!")
.build(); TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build(); JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build(); javaFile.writeTo(System.out);</pre>
生成的代碼
上面的功能一直在完成一件事情,那就是生成Java代碼泵琳,那么生成的代碼在哪摄职?
在app/build/generated/source/apt中可以找到生成的Java文件
比如:MainActivity$$ARounter
autoService:主要作用:注冊(cè)誊役,****相當(dāng)于安卓的四大組件需要注冊(cè)一樣!
google auto 中的autoservice 可以幫助我們生成對(duì)應(yīng)的配置:使用AutoService注解谷市,可以自動(dòng)生成meta信息
spi 是一種服務(wù)發(fā)現(xiàn)的標(biāo)準(zhǔn)蛔垢,對(duì)于開(kāi)發(fā)中我們通常需要編寫 META-INF/services 文件夾中定義的類。
自定義注解處理器注冊(cè)才能被Java虛擬機(jī)調(diào)用迫悠,在上面的博客第四小節(jié)中用的方法是手動(dòng)注冊(cè)鹏漆,這比較違反程序員懶的特點(diǎn),在里面也提到了自動(dòng)注冊(cè)的方法及皂,就是AutoService
介紹下依賴庫(kù)auto-service
在使用注解處理器需要先聲明甫男,步驟:
1、需要在 processors 庫(kù)的 main 目錄下新建 resources 資源文件夾验烧;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾又跛;
3碍拆、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件;
4慨蓝、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱感混,包括包路徑;)
這樣聲明下來(lái)也太麻煩了礼烈?這就是用引入auto-service的原因弧满。
通過(guò)auto-service中的@AutoService可以自動(dòng)生成AutoService注解處理器是Google開(kāi)發(fā)的,用來(lái)生成 META-INF/services/javax.annotation.processing.Processor 文件的
怎么讓jvm在編譯期間調(diào)用我們自己寫的這個(gè)注解處理器呢此熬?
有一個(gè)快捷辦法就是使用谷歌的開(kāi)源庫(kù)auto庭呜,然后使用它提供的AutoService注解來(lái)實(shí)現(xiàn),
另外一種辦法就是自己手動(dòng)去創(chuàng)建指定的文件夾犀忱,然后配置我們的注解處理器的路徑募谎。
注解處理器的debug調(diào)試
注解處理器的debug 跟普通的代碼debug 有點(diǎn)不同:
在當(dāng)前工程路徑下輸入命令
gradlew --no-daemon
-Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac
并在Edit Configurations 中新添加一個(gè)遠(yuǎn)程配置(remote),名字隨意阴汇,端口為
5005数冬。然后點(diǎn)擊debug 按鈕,就可以連接上遠(yuǎn)程調(diào)試器進(jìn)行Annotation 的調(diào)試了搀庶。
demo地址:
https://github.com/jaminchanks/AnnotationProcess-Demo
參考博客:
http://www.reibang.com/p/7af58e8e3e18
http://www.reibang.com/p/6955a56844d7
點(diǎn)擊世界是否會(huì)用到動(dòng)態(tài)代理拐纱??哥倔?---另外一種方案秸架!
https://blog.csdn.net/u010008118/article/details/100896148
ButterKnife:黃油刀,被廢棄了∥窗撸現(xiàn)在已經(jīng)被viewBind和databing取代咕宿,依賴注入框架币绩。但是原理很重要!
問(wèn)題:他和Arounter結(jié)合的時(shí)候會(huì)又什么問(wèn)題府阀?
R2缆镣?為什么會(huì)產(chǎn)生這樣的問(wèn)題?
編****譯時(shí)注解實(shí)戰(zhàn): 手寫 ButterKnife
4個(gè)模塊
1.APP :使用
2.annoations:用于定義接口
3.annotation_complier:注解處理器试浙,用于自動(dòng)生成文件董瞻,注解處理器要放在java library里面
annotation_complier繼承:abstractProcessor
4. lib :通過(guò)反射調(diào)用
重寫幾個(gè)方法
然后我們還需要重寫AbstractProcessor
類
getSupportedAnnotationTypes()
方法
getSupportedSourceVersion()
方法
getSupportedAnnotationTypes()
方法用來(lái)指定該注解處理器是用來(lái)處理哪個(gè)注解的
getSupportedSourceVersion()
方法用來(lái)指定java版本,一般給值為SourceVersion.latestSupported()
田巴。
注意:如果必要的方法沒(méi)有寫钠糊,會(huì)導(dǎo)致不執(zhí)行主要的處理方法
里面重寫的方法可以用注解代替, evenbus源碼就是這么寫的
那我們開(kāi)始:創(chuàng)建項(xiàng)目,4步搞定
創(chuàng)建Android Module命名為app
創(chuàng)建Java library Module命名為 apt-annotation
創(chuàng)建Java library Module命名為 apt-processor 依賴 apt-annotation
創(chuàng)建Android library Module 命名為apt-library依賴 apt-annotation壹哺、auto-service
分析:
1.看下使用流程:1.有一個(gè)注解 2.有自動(dòng)生成的類可以自動(dòng)注入抄伍!
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;"> @BindView(R.id.tv_bangding)
TextView btn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this); //自動(dòng)生成的類有一個(gè)bind方法,進(jìn)行動(dòng)態(tài)注入
}
}</pre>
手寫步驟:
1.新建java module管宵。把注解方進(jìn)來(lái)截珍。
為什么要把注解單獨(dú)放一個(gè)module?
因?yàn)橹鱩odule和編譯module都需要用!
那為什么不把注解放在自動(dòng)生成注解的module里面箩朴?
field注解+編譯時(shí)+java module
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value(); }
</pre>
apt-annotation(自定義注解)
創(chuàng)建注解類BindView
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS)
:表示編譯時(shí)注解
@Target(ElementType.FIELD)
:表示注解范圍為類成員(構(gòu)造方法岗喉、方法、成員變量)
@Retention: 定義被保留的時(shí)間長(zhǎng)短
RetentionPoicy.SOURCE炸庞、RetentionPoicy.CLASS钱床、RetentionPoicy.RUNTIME
@Target: 定義所修飾的對(duì)象范圍
TYPE、FIELD埠居、METHOD查牌、PARAMETER、CONSTRUCTOR拐格、LOCAL_VARIABLE等
這里定義了運(yùn)行時(shí)注解BindView
僧免,其中value()
用于獲取對(duì)應(yīng)View
的id
。
2.新建java module,把注解處理器需要的寫進(jìn)來(lái)捏浊,依賴javapoat和autoservice
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">implementation 'com.google.auto.service:auto-service:1.0-rc2' implementation 'com.squareup:javapoet:1.10.0' implementation project(':apt-annotation')</pre>
需要先模擬好生成的類懂衩,在主程序的build/generated/source/apt
[圖片上傳失敗...(image-fd1f62-1640265031545)]
在自動(dòng)生成模塊里面會(huì)生成注冊(cè)器
[圖片上傳失敗...(image-15d481-1640265031545)]
一共5個(gè)方法:
其他3個(gè)方法可以用注解的形式下。
然后init方法和process方法注解處理器解析:
1.得到所以有標(biāo)記的注解元素或者節(jié)點(diǎn)
2.得到包名
3.得到類名
4.拼接類
5.生成java文件
主要的一些對(duì)象:
1.元素
2.文件創(chuàng)造器
3.日志信息打印輸出messager
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Messager mMessager; // 打印
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); /***
-
初始化一些類:比如日志金踪,讀寫流 * @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}/***
-
需要處理注解的類型 * @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}/***
-
java的版本 * @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的帶指定的注解BindView
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
//elements的信息保存到mProxyMap中浊洞,key:類全名
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);//通過(guò)元素得到注解類
int id = bindAnnotation.value();//得到注解類里面的值
proxy.putElement(id, variableElement);
}
//拿到開(kāi)始map存放的信息
//通過(guò)javapoet生成java類 for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true; }
}</pre>
<pre style="margin: 8px 0px;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">/**
- 加入Method * javapoet */ private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host"); for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id); //VariableElement就是相應(yīng)的元素
String name = element.getSimpleName().toString(); //控件名字
String type = element.asType().toString();//控件的類型,是btn還是text
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build(); }</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">最重要的是要得到VariableElement</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">主要就是從Elements
胡岔、TypeElement
得到想要的一些信息法希,如package name、Activity名靶瘸、變量類型苫亦、id等毛肋,</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace;">使用的時(shí)候:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton;</pre>
</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">獲取和解析控件:</pre>
<pre style="margin: 8px 0px;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198);">VariableElement element = mVariableElementMap.get(id); //VariableElement就是相應(yīng)的元素 String name = element.getSimpleName().toString(); //控件名字 String type = element.asType().toString();//控件的類型,是btn還是text</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43);">常用的幾個(gè)類(打印屋剑,元素润匙,javapoat文件讀寫操作JavaFile)</pre>
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace; font-size: 0.817rem; background-color: rgb(43, 43, 43);"> <pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">public class BindViewProcessor extends AbstractProcessor {
private Messager mMessager; // 打印
private Elements mElementUtils; // 對(duì)應(yīng)的元素
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>(); //把帶注解的元素封裝在map中</pre>
3、封裝一個(gè)調(diào)用module唉匾。 </pre>
<pre style="margin: 8px 0px;"> #### apt-library 工具類在BindViewProcessor
中創(chuàng)建了對(duì)應(yīng)的xxxActivity_ViewBinding.java
孕讳,我們改怎么調(diào)用?當(dāng)然是反射啦N”臁3Р啤!apt-library 工具類
為什么用通過(guò)反射調(diào)用峡懈?
因?yàn)檫@些類都是動(dòng)態(tài)生成的璃饱,根據(jù)不同的activity動(dòng)態(tài)生成的!
</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">public class BindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">4.使用編譯時(shí)注解</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace;">
<pre style="margin: 8px 0px; font-family: "JetBrains Mono", monospace;">public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView mTextView;
@BindView(R.id.btn)
Button mButton; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTools.bind(this);
mTextView.setText("bind TextView success");
mButton.setText("bind Button success");
}
}</pre>
</pre>
</pre>
</pre>