前言
類似于 ButterKnife 刁岸、 Dagger 脏里、 ARouter,它們可以通過幾個注解虹曙,就可以實現(xiàn)以往需要很大功夫?qū)懙拇a迫横,還可以非常好的解除依賴鸦难,來提高解耦度,提升代碼的擴(kuò)展空間员淫。
其實它們也是通過一些注解在編譯時生成一部分代碼合蔽,來提高開發(fā)效率或者實現(xiàn)一些特別的功能。
這幾天在準(zhǔn)備春招的過程中對這一塊的內(nèi)容產(chǎn)生了較大的興趣介返,于是也自己動手寫了一個超級簡易版的 Router拴事,功能暫且就只實現(xiàn)跳轉(zhuǎn) Activity。
需要知道的是
編譯時注解
需要對 @Retention 有一個了解圣蝎。
元注解 @Retention
類型 | 作用 |
---|---|
SOURCE | 只在 java 源文件中存在刃宵,編譯器編譯后就被丟棄 |
CLASS | 在 Java 源文件和編譯后生成的字節(jié)碼中存在,在虛擬機(jī)運(yùn)行時被丟棄 |
RUNTIME | 運(yùn)行時也存在徘公,可以通過反射讀取注解 |
這里的 CLASS 就是編譯時注解牲证,我們可以通過這樣的注解在編譯時獲取到自己想要的一些信息,并依據(jù)它們做一些操作关面,來達(dá)到自己的目的坦袍。
注解其它相關(guān)這里就不提了。
APT
APT(annotation processing tool) 是一種處理編譯時注解等太,在編譯階段解析注解并且生成 java 代碼的技術(shù)捂齐,以此來減少開發(fā)時需要寫的代碼,我們這里用的就是 APT 技術(shù)缩抡。
AbstractProcessor
AbstractProcessor 是一個抽象類奠宜,是 Java 中專門用來處理注解的類。
AbstractProcessor 中有兩個需要重寫的方法
//初始化方法瞻想,提供處理時的環(huán)境
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
//處理注解過程
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
SPI
如果對虛擬機(jī)的有了解朋友可能會對它有印象压真,因為可以追溯到第二次雙親委派模型破壞。
它是一種動態(tài)替換機(jī)制蘑险,可以動態(tài)加載一些繼承某個接口的實體類滴肿。
AutoService
Android build 不會生成 META-INF,而 SPI 需要根據(jù)聲明在 META-INF 中的類名來執(zhí)行類文件漠其。
這里可以使用 Google 的 AutoService 來創(chuàng)建 META-INF嘴高,并將被注解的文件聲明到 META-INF 中竿音。
JavaPoet
大名鼎鼎的 square 公司的 JavaPoet, 可以有效的幫忙生成代碼和屎。
開始動手
自定義注解
創(chuàng)建一個寫自定義注解的library,注意春瞬,一定要選擇 Java Library
創(chuàng)建一個自定義注解
//指定被標(biāo)注類型為類或者接口
@Target(ElementType.TYPE)
//指定為編譯時注解
@Retention(RetentionPolicy.CLASS)
public @interface RouterUrl {
//用來做為跳轉(zhuǎn)路徑
String url();
}
使用注解
先在 app 的 gradle 中引入此包
implementation project(':lib_annotation')
接下來為 Activity 標(biāo)注柴信,因為需要跳轉(zhuǎn),這里我們寫兩個 Activty宽气。
//當(dāng)前類
@RouterUrl(url = "main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
//跳轉(zhuǎn)目標(biāo)
@RouterUrl(url = "second")
public class SecondActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv_text);
textView.setText("跳轉(zhuǎn)成功~");
}
}
自定義注解非常簡答随常,接下來看 APT 技術(shù)的核心潜沦,如何在編譯時解析一個注解。
解析注解
創(chuàng)建一個用來解析注解的 library绪氛,同樣也需要 Java 的 library唆鸡,不然將無法使用 AbstractProcessor。
在它的 gradle 中引用注解包和需要的第三方庫
implementation 'com.squareup:javapoet:1.8.0'
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation project(':lib_annotation')
創(chuàng)建一個 RouterCompiler 類
//通過 AutoService 將 Processor 聲明到 META-INF 中
@AutoService(Processor.class)
//指定解析的注解
@SupportedAnnotationTypes({"com.example.jzycc.lib_annotation.RouterUrl"})
public class RouterCompiler extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取 RouterUrl 的集合
Set< ? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(RouterUrl.class);
//這里是 JavaPoet 的使用枣察,作用是構(gòu)建一個類争占,類名為 WhatRouterTable
TypeSpec.Builder typeSpec = TypeSpec.classBuilder("WhatRouterTable")
.addModifiers(Modifier.PUBLIC);
for (Element element : elements){
//獲取注解中的 url 信息
String url = element.getAnnotation(RouterUrl.class).url();
//將其專成存有類信息的 TypeElement
TypeElement typeElement = (TypeElement)element;
//獲取目標(biāo)類的全名
String className = typeElement.getQualifiedName().toString();
//構(gòu)造一個字段,以 url 為名序目,類名為值臂痕,如 public final static url = "classname";
FieldSpec fieldSpec = FieldSpec.builder(String.class,url)
.addModifiers(Modifier.PUBLIC,Modifier.FINAL,Modifier.STATIC)
.initializer("$S",className)
.build();
//將字段添加到 typeSpec 中
typeSpec.addField(fieldSpec);
}
try{
//指定生成 Java 文件的 包名 (這個包現(xiàn)在還沒建立,接下來它做為提供給開發(fā)者使用的 api)
String packageFullName = "com.example.jzycc.lib_whatrouter";
//構(gòu)造 Java 文件
JavaFile javaFile = JavaFile.builder(packageFullName, typeSpec.build()).build();
//寫入到 filer猿涨,生成對應(yīng)的 class 文件
javaFile.writeTo(filer);
}catch (IOException e){
e.printStackTrace();
}
return true;
}
}
以上就是處理注解的邏輯握童,我們通過解析注解獲取它的url值以及被此注解表明的類名,并將這些值做為字段添加到我們解析時生成的 Java 文件中叛赚。
如何調(diào)試 APT
直接調(diào)試是無法調(diào)試 APT 代碼的澡绩,首先找到項目中的 gradle.properties 文件,也可以在 .gradle 文件中全局配置俺附。
加入如下代碼
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
如圖位置我們選擇 Edit Configurations
點(diǎn)擊下面圖中的 + 號英古,并選擇 Remote
隨便起個名字,點(diǎn)擊調(diào)試按鈕昙读,如果調(diào)試成功則會提示如下信息
Connected to the target VM, address: 'localhost:5005', transport: 'socket'
為 APT 代碼加入斷點(diǎn)召调,我們這時候 rebuild 程序就可以開始開始調(diào)試了。
接下來去看下如何使用解析注解得到的信息蛮浑。
Router 的 API
同樣唠叛,我們?yōu)樗⒁粋€ module ,但是這次我們建立的是 Android Library沮稚,因為我們在這里需要使用 Android 的一些類艺沼。
在這里,我們先運(yùn)行一下代碼蕴掏,看一下注解是否會生成我們想要的 Java 文件障般。
按兩下 shift,搜索我們創(chuàng)建的類名 'WhatRouterTable'
可以發(fā)現(xiàn)我們很成功的生成了我們想要的信息盛杰,最重要的一步已經(jīng)完成了挽荡,接下來我們來完成如何利用這個文件。
package com.example.jzycc.lib_whatrouter;
import java.lang.String;
public class WhatRouterTable {
public static final String main = "com.example.jzycc.whatrouter.MainActivity";
public static final String second = "com.example.jzycc.whatrouter.SecondActivity";
}
創(chuàng)建 WhatRouter即供,做為 Api 的提供類定拟。
package com.example.jzycc.lib_whatrouter;
import android.app.Activity;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.lang.reflect.Method;
public class WhatRouter {
private Class clazz;
private Context context;
private Class goalClazz;
private static WhatRouter instance;
//這里用單例模式來創(chuàng)建此類
public static WhatRouter getInstance(){
if (instance == null){
synchronized (WhatRouter.class){
if (instance == null){
instance = new WhatRouter();
}
}
}
return instance;
}
private WhatRouter(){
try{
clazz = Class.forName("com.example.jzycc.lib_whatrouter.WhatRouterTable");
}catch (Exception e){
Log.e("WhatRouterError", "WhatRouter: ",e );
}
}
//傳入 context
public WhatRouter with(Context context){
this.context = context;
return this;
}
//傳入 url
public WhatRouter url(String url){
try{
goalClazz = Class.forName(clazz.getField(url).get("").toString());
}catch (Exception e){
Log.e("WhatRouterError", "url: ", e);
}
return this;
}
//啟動 Activity
public void startActiviity(){
try{
Intent intent = new Intent(context, goalClazz);
context.startActivity(intent);
}catch (Exception e){
Log.e("WhatRouterError", "startActiviity: ",e );
}
}
}
非常的簡單,我們通過反射去獲取我們在解析注解時生成的類逗嫡,再根據(jù)類中的url字段獲取目標(biāo)類的類名青自,依據(jù)此類名獲取目標(biāo)類用來啟動 Activity株依。
使用一下看看是否成功
修改 MainActivity
@RouterUrl(url = "main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//這里我們啟動 SecondActivity
WhatRouter.getInstance().with(this).url("second").startActiviity();
}
}
運(yùn)行APP
成功的跳轉(zhuǎn)到了 SecondActivity。
總結(jié)
看完之后發(fā)現(xiàn)實現(xiàn)一個簡易 Router 是非常簡單的了延窜。
當(dāng)然這只是一個學(xué)習(xí) APT 技術(shù)的 demo, 如果要投入到實際場景當(dāng)中去那是完全不行的恋腕,使用技術(shù)并不難,難的是如何更好的滿足業(yè)務(wù)場景逆瑞,以及更加深入到其中去學(xué)習(xí)它的原理吗坚。
學(xué)習(xí) APT 相關(guān)的知識,我這僅僅是一個開始呆万。