編譯時(shí)注解到自定義Router框架


找了很久網(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用來編譯。



創(chuàng)建

app依賴processor和processor_libl

processor依賴processor_lib

在processor引入我們的AutoService和javapoet


引入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)示


AutoService

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

api

process方法

在這里處理我們注解以及生成類對(duì)象
有幾個(gè)知識(shí)點(diǎn)

開始編寫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


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è)面,寫一下試試好用能生成么,




運(yùn)行結(jié)果

發(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();
            }
        });


    }
}

第二個(gè)頁(yè)面

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容