
找了很久網(wǎng)上也沒講編譯期注解的視頻,只能對(duì)著網(wǎng)上代碼一句句研究總結(jié),主要學(xué)完這塊,路由框架啊,奶油刀啊,基本自己就可以寫點(diǎn)low版本的了
@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {}
上面是路由框架的注解,這個(gè)注解有什么用呢,路由框架會(huì)在項(xiàng)目的編譯期通過注解處理器掃描所有添加@Route注解的Activity類,然后將Route注解中的path地址和Activity.class文件映射關(guān)系保存到它自己生成的java文件中,只要拿到了映射關(guān)系便能拿到Activity.class。相當(dāng)于有一個(gè)類專門去保存了這些類和路徑的關(guān)系。
好吧,我們先去學(xué)習(xí)下怎么搞個(gè)編譯期注解
注解篇可以看我之前的基本注解講解 :http://www.itdecent.cn/p/e59059a509f1
我這里就直接上手了,其實(shí)核心原理就是我們掃描自己自定義的注解,然后根據(jù)注解拿到被我們注解的類的相關(guān)信息,保存生成一個(gè)我們的類,類里寫上我們需要的東西,然后我們就可以根據(jù)這個(gè)類去搞一些事情。
大家也可以參考這篇博客,我是直接搞一下當(dāng)自己日記了
https://blog.csdn.net/yang_yang1994/article/details/79729621
虛處理器AbstractProcessor
我們首先看一下處理器的API。每一個(gè)處理器都是繼承于AbstractProcessor,如下所示:
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
先進(jìn)行參數(shù)講解,也會(huì)貼一些api上來,不然后面沒辦法用。
- init(ProcessingEnvironment env): 每一個(gè)注解處理器類都必須有一個(gè)空的構(gòu)造函數(shù)。然而,這里有一個(gè)特殊的init()方法,它會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類Elements, Types和Filer。
https://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/ProcessingEnvironment.html
ProcessingEnvironment
- process(Set<? extends TypeElement> annotations, RoundEnvironment env): 這相當(dāng)于每個(gè)處理器的主函數(shù)main()。你在這里寫你的掃描、評(píng)估和處理注解的代碼,以及生成Java文件。輸入?yún)?shù)RoundEnviroment,可以讓你查詢出包含特定注解的被注解元素。
*getSupportedAnnotationTypes(): 這里返回一個(gè)set集合,告訴你需要支持的哪些注解類,要把你的注解類全路徑寫成字符串返回給他。
*getSupportedSourceVersion(): 用來指定你使用的Java版本。通常這里返SourceVersion.latestSupported()。
java7以后可以使用,,但是看網(wǎng)上大家都說不建議這么使用,為了版本兼容
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
// 合法注解全名的集合
})
這里我們需要加入兩個(gè)工具框架
第一個(gè)是AutoService,因?yàn)樯勺⒔庑枰粋€(gè)特定的格式


AutoService可以幫我們自動(dòng)生成這些包和路徑,就省得自己創(chuàng)建了,尤其android是木有META-INF的
基友網(wǎng)地址
https://github.com/google/auto/tree/master/service
第二個(gè)工具是javapoet,JavaPoet是square推出的開源java代碼生成框架,提供Java Api生成.java源文件。這個(gè)框架功能非常有用,我們可以很方便的使用它根據(jù)注解、數(shù)據(jù)庫(kù)模式、協(xié)議格式等來對(duì)應(yīng)生成代碼。通過這種自動(dòng)化生成代碼的方式,可以讓我們用更加簡(jiǎn)潔優(yōu)雅的方式要替代繁瑣冗雜的重復(fù)工作。
參考鏈接 :https://blog.csdn.net/xuguobiao/article/details/72775730
基友網(wǎng)地址: https://github.com/square/javapoet
我們現(xiàn)在開始寫編譯期注解
第一步,在AnroidStudio 項(xiàng)目上新建兩個(gè)modle,分別取名為processor,processor_lib,processor依賴processor_lib,app依賴processor和processor_lib。processor_lib存放我們自定義注解,processor用來編譯。




在processor引入我們的AutoService和javapoet

最后出來效果就是下圖這樣子

下面開始編寫代碼
我們?cè)趐rocessor_lib寫下我們的注解@Leo,這塊不清楚的可以看我寫的自定義注解篇
@Target(ElementType.FIELD)//聲明在字段
@Retention(RetentionPolicy.CLASS)//聲明為編譯期注解
public @interface Leo {
String path();//倆參數(shù)
String name();
}
在我們的processor開啟我們的注解生成部分

覆蓋process,init,getSupportedSourceVersion,getSupportedAnnotationTypes 四個(gè)方法
getSupportedSourceVersion方法比較簡(jiǎn)單,直接返回支持最新的
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
getSupportedAnnotationTypes,返回支持我們的Leo注解,getCanonicalName和和getName其實(shí)一樣,getName()返回的是虛擬機(jī)里面的class的表示,而getCanonicalName()返回的是更容易理解的表示。其實(shí)對(duì)于大部分class來說這兩個(gè)方法沒有什么不同的。但是對(duì)于array或內(nèi)部類來說是有區(qū)別的。
另外,類加載(虛擬機(jī)加載)的時(shí)候需要類的名字是getName。
詳情可以看這篇博客
https://blog.csdn.net/hustzw07/article/details/71108945
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> set = new HashSet<>();
set.add(Leo.class.getCanonicalName());
return set;
}
在我們的類上加上AutoService標(biāo)示

init方法
這里有幾個(gè)知識(shí)點(diǎn)ProcessingEnvironment,Messager,Elements
private Filer filer;
private Messager messager;
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
}
ProcessingEnvironment
這玩意還得是看API,雖然英文的但是我們可以翻譯啊哈哈
大體意思就是可以提供方法進(jìn)行編寫新文件、報(bào)告錯(cuò)誤消息和查找其他實(shí)用工具。具體可以自己看看
https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/ProcessingEnvironment.html

process方法
在這里處理我們注解以及生成類對(duì)象
有幾個(gè)知識(shí)點(diǎn)
- TypeElement API
http://www.cjsdn.net/Doc/JDK60/javax/lang/model/element/TypeElement.html
通過下圖我們可以得到各種類型的TypeElement
盜個(gè)圖 - RoundEnvironment 直接看api
http://www.cjsdn.net/Doc/JDK60/javax/annotation/processing/RoundEnvironment.html

Element 我們上文說的 TypeElement,VariableElement,PackageElemtent大家都可以看看api
http://www.cjsdn.net/Doc/JDK60/javax/lang/model/element/package-summary.htmlElementKind
http://www.cjsdn.net/Doc/JDK60/javax/lang/model/element/ElementKind.html
開始編寫process,我們先整理下思路,我們?cè)谕ㄟ^編譯期可以拿到所有符合我們要求注解字段,生成我們想要的包,類和根據(jù)符合要求做的操作
那么可以分為一下幾步
- 以類做key,拿到當(dāng)前類所有的符合要求注解
- 按照類<注解>形式生成類
- 將類寫到我們特定的包里
- 如果多包我們還可以按照總包-包名-類名這種形式進(jìn)行分層
按照如下思路我們寫下代碼
//類名
String element4className;
//存放類名和元素注解的集合,HashMap保證唯一
HashMap<String, ArrayList<VariableElement>> map = new HashMap<>();
//存放元素的集合
ArrayList<VariableElement> varList;
//拿到被leo標(biāo)注的所有注解元素
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Leo.class);
//遍歷elements
for (Element element : elements) {
//返回此元素的類型。
ElementKind elementKind = element.getKind();
//如果類型是 作用在字段上
if (elementKind == ElementKind.FIELD) {
//那么element就是一個(gè)VariableElement
VariableElement var = (VariableElement) element;
//我們要得到它上層類 返回封裝此元素(非嚴(yán)格意義上)的最里層元素。
TypeElement element4class = (TypeElement) var.getEnclosingElement();
//拿到返回此類型元素的完全限定名稱
element4className = element4class.getQualifiedName().toString();
//利用elementUtils 然后 此包的完全限定名稱
String packageName = elementUtils.getPackageOf(element4class).toString();
//判斷是不是null,如果是null就生成新的存放進(jìn)去
varList = map.get(element4className);
if (varList == null) {
varList = new ArrayList<VariableElement>();
map.put(element4className, varList);
}
//隊(duì)列里木有就加進(jìn)去
if (!varList.contains(var)) {
varList.add(var);
}
}
}
到這里還沒結(jié)束,我們要根據(jù)我們發(fā)現(xiàn)的注解進(jìn)行操作,這里書寫類和包,我建議最好看下javapoet怎么使用
for (String key : map.keySet()) {
//根據(jù)key取出所有類
List<VariableElement> elementFileds = map.get(key);
//去掉類名最后的。class
String className = key.substring(key.lastIndexOf(".") + 1);
//類名后綴添加4Leo
className += "4Leo";
//創(chuàng)建一個(gè)public的4leo結(jié)尾的類
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC);
//生成一個(gè)返回值為String的方法 public static
MethodSpec.Builder methodBuild = MethodSpec.methodBuilder("getElemens")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(String.class);
//遍歷 注解上的name和path
for (VariableElement e : elementFileds) {
Leo annotation = e.getAnnotation(Leo.class);
String name = annotation.name();
String path = annotation.path();
//返回name和path
methodBuild.returns(String.class)
.addStatement("return $S", "name=" + name + "----path---" + path);
}
//創(chuàng)建方法
MethodSpec printNameMethodSpec = methodBuild.build();
//創(chuàng)建類
TypeSpec classTypeSpec = classBuilder.addMethod(printNameMethodSpec).build();
try {
//寫出包名和類
JavaFile javaFile = JavaFile.builder(packageName, classTypeSpec)
//添加注解
.addFileComment(" Leo Compile time annotations!")
.build();
javaFile.writeTo(filer);
} catch (IOException exception) {
exception.printStackTrace();
}
}
return true;
運(yùn)行一下,我們發(fā)現(xiàn)這個(gè)路徑下出現(xiàn)一個(gè)和我們包一樣的4Leo的類

在我們頁(yè)面測(cè)試一下
public class MainActivity extends AppCompatActivity {
@Leo(name = "leo", path = "MainActivity")
private String Text;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String elemens = MainActivity4Leo.getElemens();
Log.e(TAG, "onCreate: "+elemens );
}
}
看下log

可見我們的自定義注解成功了
再寫這篇文章,我學(xué)習(xí)翻閱了很多API,用了一天,終于啃下了這塊硬骨頭,學(xué)會(huì)了編譯期注解我們能做很多事,比如寫個(gè)low版本的路由框架,接下來我們?cè)囈幌隆?br>
先看一下Arouter寫的,我們雖然暫時(shí)只想實(shí)現(xiàn)一個(gè)頁(yè)面跳轉(zhuǎn)功能,但是也得把XX裝足了
基本上就是app類初始化,頁(yè)面添加@Route加path,然后
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化,跳轉(zhuǎn),我們也對(duì)著寫,先把最重要的注解先寫好
// 在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí),/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
ARouter.getInstance().build("/test/activity").navigation();
在processor_lib下寫好我們的注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface LRoute {
String path();
String name()default "";
}
processor下開啟我們的注解處理

package xzzb.com.processor;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import xzzb.com.processor_lib.LRouter;
//添加AutoService注解
@AutoService(Processor.class)
public class RouterPorcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
private Elements elementUtils;
private String packageName;
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//初始化裝填我們注解的Set
HashSet<TypeElement> map = new HashSet<>();
//獲取我們的注解
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(LRoute.class);
//遍歷
for (Element element : elements) {
//拿到注解類型
ElementKind elementKind = element.getKind();
//如果作用于類上
if (elementKind == ElementKind.CLASS) {
//轉(zhuǎn)成TypeElement
TypeElement element4Class = (TypeElement) element;
//添加進(jìn)去
map.add(element4Class);
//獲取包名
packageName = elementUtils.getPackageOf(element4Class).toString();
}
}
//創(chuàng)建類
TypeSpec.Builder classBuilder = TypeSpec.classBuilder("LRouterMap")
.addModifiers(Modifier.PUBLIC);
//javapoet Classname方法可以參考基友網(wǎng),我們要生成一個(gè)HashMap<String,Sting>
ClassName hashMap = ClassName.get("java.util", "HashMap");
ClassName key = ClassName.get("java.lang", "String");
ClassName value = ClassName.get("java.lang", "String");
//生成一個(gè)map,將類名和注解值添加進(jìn)去
MethodSpec.Builder build = MethodSpec.methodBuilder("getMaps").addModifiers(Modifier.PUBLIC);
TypeName listOfHoverboards = ParameterizedTypeName.get(hashMap, key, value);
build.addStatement("$T result = new $T<>()", listOfHoverboards, hashMap);
build.returns(listOfHoverboards);
for (TypeElement e : map) {
//遍歷添加
String classname = e.getQualifiedName().toString();
LRoute annotation = e.getAnnotation(LRoute.class);
String path = annotation.path();
build.addStatement("result.put($S,$S)", path, classname);
}
//返回map,生成類和方法
build.addStatement("return result");
MethodSpec printNameMethodSpec = build.build();
TypeSpec classTypeSpec = classBuilder.addMethod(printNameMethodSpec).build();
try {
//寫出去類
JavaFile javaFile = JavaFile.builder(packageName, classTypeSpec)
.addFileComment(" Leo Compile time annotations !")
.build();
javaFile.writeTo(filer);
} catch (IOException exception) {
exception.printStackTrace();
}
return true;
}
//獲取需要的工具
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
}
//支持版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//要支持的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> set = new HashSet<>();
set.add(LRoute.class.getName());
return set;
}
}
注解這塊寫完了,我們先在倆頁(yè)面,寫一下試試好用能生成么,



發(fā)現(xiàn)我們的類名和path都被保存了下來,那么我們可以使用包名加類名的方式進(jìn)行跳轉(zhuǎn)了
跳轉(zhuǎn)也需要一個(gè)Context,我們就一路模仿Arouter去寫
public class LRouter {
private static LRouter lRouter;
/**
* @author Administrator
* @time 2018/10/18 10:56
* @describe 獲取傳入的Context
*/
public static Context getContext() {
return context;
}
private static Context context;
/**
* @author Administrator
* @time 2018/10/18 10:56
* @describe 私有化構(gòu)造函數(shù)
*/
private LRouter() {
}
/**
* @author Administrator
* @time 2018/10/18 10:57
* @describe 初始化
*/
public static void init(Application application) {
context = application;
}
/**
* @author Administrator
* @time 2018/10/18 10:56
* @describe 單例模式
*/
public static LRouter getInstance() {
if (lRouter == null) {
lRouter = new LRouter();
}
return lRouter;
}
/**
* @author Administrator
* @time 2018/10/18 10:56
* @describe 拿到生成的路由表 返回一個(gè)Postcard對(duì)象
*/
public Postcard build(String path) {
LRouterMap lRouterMap = new LRouterMap();
HashMap<String, String> maps = lRouterMap.getMaps();
Postcard postcard = new Postcard();
String classNmae = maps.get(path);
postcard.setPath(classNmae);
return postcard;
}
}
public class Postcard {
//path
private String path;
//要跳轉(zhuǎn)的包名
private String packageName;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
/**
* @author Administrator
* @time 2018/10/18 10:58
* @describe 跳轉(zhuǎn)
*/
public void navigation() {
//判斷路徑是不是null
if (!TextUtils.isEmpty(path)) {
//截取包名
int pos = path.lastIndexOf(".");
packageName = path.substring(0, pos);
//根據(jù)包名和類名跳轉(zhuǎn)
Intent intent = new Intent();
ComponentName componentName = new ComponentName(packageName, path);
intent.setComponent(componentName);
LRouter.getContext().startActivity(intent);
} else {
//路徑為null直接返回
return;
}
}
}
我們?nèi)y(cè)試一下 跳轉(zhuǎn),成功跳轉(zhuǎn)到了第二個(gè)頁(yè)面
@LRoute(path = "Main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按鈕跳轉(zhuǎn)
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//跳轉(zhuǎn)Main2頁(yè)面
LRouter.getInstance().build("Main2").navigation();
}
});
}
}

到此為止我們完成了最low版本的頁(yè)面跳轉(zhuǎn),其實(shí)我們可以仿照寫更多功能。
這篇到此為止,再見
項(xiàng)目已經(jīng)傳送到基友網(wǎng),地址 :https://github.com/594dudulang/LRouter,歡迎搞基


