APT自動化生成代碼整理

說到java的apt技術(shù)活合,其實已經(jīng)算不是很陌生了屡穗,在以前閱讀第三方框架butterknifeDagger2等框架的時候许溅,看到過apt的影子温鸽。他是squareup公司出的javapoet技術(shù)保屯,通過在java的編譯時期生成類,提高了在運行時期通過反射調(diào)用的效率涤垫。大家試想一下姑尺,如果butterknife所有的注解在運行時期都通過反射調(diào)用相應的findViewById的話,那得多慢啊雹姊。所以可以看到butterknife都是通過apt技術(shù)來生成相應的_ViewBinding股缸,大家可以看下app-->build-->generated-->source-->apt下面找到對應的_ViewBinding衡楞。好了廢話不多說吱雏,咋們下面來直接來擼碼。

實現(xiàn)功能還是跟butterknife框架findViewById的功能一樣瘾境,經(jīng)過前幾篇的學習反射歧杏,注解所以才有今天的apt技術(shù)代碼,所以不熟悉反射跟注解的伙伴們迷守,還是先看下反射和注解如何使用犬绒。

  • android studio中創(chuàng)建一個java library的module,這里我起名字叫binder_annotation兑凿,專門用來放注解的凯力,這里生成后是這個樣子:


    image.png
  • 接著在剛創(chuàng)建的binder_annotation中創(chuàng)建注解
//在編譯期起作用的注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}
  • 創(chuàng)建java library的module茵瘾,這里我起名字叫binder_compiler。先創(chuàng)建了后面再說

  • 在app的module的build.gradle通過annotationProcessor添加依賴:

dependencies {
    ....
    annotationProcessor project(':binder_compiler')
    implementation project(':binder_annotation')
}

這里注意了咐鹤,在gradle tool>=2.2之后直接用annotationProcessor添加apt的依賴拗秘,如果是在gradle tool<2.2首先得在project添加:

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后在app的build.gradle添加:

apply plugin: 'com.neenbedankt.android-apt'

添加依賴的地方:

apt project(':binder_compiler')

此處我用的是gradle tools 3.4.1因此直接用annotationProcessor添加依賴。

說完了整體的架子祈惶,下面來到binder_compiler下面雕旨,我們創(chuàng)建BinderProcessor類,并且繼承于AbstractProcessor捧请,該類是在編譯期會進行類掃描的處理類凡涩。咋們需要實現(xiàn),在實現(xiàn)之前需要了解幾個方法:

//該方法指定類處理器是什么java版本疹蛉,一般返回SourceVersion.latestSupported()表示最新的java版本就行
@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
}
//指明有哪些注解需要被掃描到活箕,返回注解的路徑
@Override
public Set<String> getSupportedAnnotationTypes() {
    //大部分class而已getName、getCanonicalNam這兩個方法沒有什么不同的可款。
    //但是對于array或內(nèi)部類等就不一樣了讹蘑。
    //getName返回的是[[Ljava.lang.String之類的表現(xiàn)形式,
    //getCanonicalName返回的就是跟我們聲明類似的形式筑舅。
    HashSet<String> supportTypes = new LinkedHashSet<>();
    supportTypes.add(BindView.class.getCanonicalName());
    return supportTypes;
    //因為兼容的原因座慰,特別是針對Android平臺,建議使用重載getSupportedAnnotationTypes()方法替代默認使用注解實現(xiàn)
}

在初始化的時候獲取到掃描對象:

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    //processingEnvironment.getElementUtils(); 處理Element的工具類翠拣,用于獲取程序的元素版仔,例如包、類误墓、方法蛮粮。
    //processingEnvironment.getTypeUtils(); 處理TypeMirror的工具類,用于取類信息
    //processingEnvironment.getFiler(); 文件工具
    //processingEnvironment.getMessager(); 錯誤處理工具
    //初始化的時候獲取到當前掃描的對象
    //processingEnv是父類定義的ProcessingEnvironment對象谜慌,其實就是init方法回傳過來的
    mElementUtils = processingEnv.getElementUtils();
}

我們的重頭戲來了process方法:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //掃描整個工程   找出含有BindView注解的元素
    //找到所有帶有BindView注解的類然想,生成對應的****_ViewBinding類
    Set<? extends Element> elements =
            roundEnvironment.getElementsAnnotatedWith(BindView.class);
    //遍歷元素
    for (Element element : elements) {
        //BindView限定了只能屬性使用,這里強轉(zhuǎn)為VariableElement欣范,如果是在類上面的变泄,那么就是typeElement
        VariableElement variableElement = (VariableElement) element;
        //返回此元素直接封裝(非嚴格意義上)的元素。
        //類或接口被認為用于封裝它直接聲明的字段恼琼、方法妨蛹、構(gòu)造方法和成員類型
        //這里就是獲取封裝屬性元素的類元素
        TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
        //獲取簡單類名
        String fullClassName = classElement.getQualifiedName().toString();
        //里面放的是BinderClassCreator,關鍵生成***_ViewBinding類在里面生成的
        BinderClassCreator creator = mCreatorMap.get(fullClassName);
        if (creator == null) {
            creator = new BinderClassCreator(mElementUtils.getPackageOf(classElement),
                    classElement);
            //生成之后就放到map中晴竞,方便下次使用
            mCreatorMap.put(fullClassName, creator);
        }
        //獲取元素注解
        BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
        //注解值
        int id = bindAnnotation.value();
        creator.putElement(id, variableElement);
    }
    for (String key : mCreatorMap.keySet()) {
        BinderClassCreator binderClassCreator = mCreatorMap.get(key);
        //通過javapoet構(gòu)建生成Java類文件
        //第一個參數(shù)傳入包名
        //第二個參數(shù)傳入TypeSpec
        JavaFile javaFile = JavaFile.builder(binderClassCreator.getPackageName(),
                binderClassCreator.generateJavaCode()).build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return false;
}

process方法里面主要做了幾件事:

  • 掃描工程里面帶有BindView注解的類
  • 通過注解的類拿到類信息蛙卤,生成BinderClassCreator對象,然后放到map中,將id和VariableElement對象給BinderClassCreator對象颤难,后面會用到
  • 最后生成javaFile對象神年,通過writeTo生成對應的***_ViewBinding。

在上面代碼中將每一個要生成***_ViewBinding類的工作都交給了BinderClassCreator類行嗤,其實最關心的還是該類:
首先看構(gòu)造方法:

public static final String ParamName = "view";
private TypeElement mTypeElement;
private String mPackageName;
private String mBinderClassName;
//key是view在xml中的id瘤袖,value是作用在類上面的element對象
private Map<Integer, VariableElement> mVariableElements = new HashMap<>();
/**
 * @param packageElement 包元素
 * @param classElement   類元素
 */
public BinderClassCreator(PackageElement packageElement, TypeElement classElement) {
    this.mTypeElement = classElement;
    mPackageName = packageElement.getQualifiedName().toString();
    mBinderClassName = classElement.getSimpleName().toString() + "_ViewBinding";
}

構(gòu)造器基本沒做什么,主要是初始化包名和class類名昂验。

存儲了id和作用在類上面的element對象
public void putElement(int id, VariableElement variableElement) {
    mVariableElements.put(id, variableElement);
}
//生成類的代碼
public TypeSpec generateJavaCode() {
    return TypeSpec.classBuilder(mBinderClassName)
            //public 修飾類
            .addModifiers(Modifier.PUBLIC)
            //添加類的方法
            .addMethod(generateMethod())
            //構(gòu)建Java類
            .build();
}
//生成bindView方法的代碼
private MethodSpec generateMethod() {
    //獲取所有注解的類的類名
    ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
    //構(gòu)建方法--方法名
    return MethodSpec.methodBuilder("bindView")
            //public方法
            .addModifiers(Modifier.PUBLIC)
            //返回void
            .returns(void.class)
            //方法傳參(參數(shù)全類名捂敌,參數(shù)名)
            .addParameter(className, ParamName)
            //方法代碼
            .addCode(generateMethodCode())
            .build();
}
//生成bindView方法里面代碼的代碼
private String generateMethodCode() {
    StringBuilder code = new StringBuilder();
    for (int id : mVariableElements.keySet()) {
        VariableElement variableElement = mVariableElements.get(id);
        //使用注解的屬性的名稱
        String name = variableElement.getSimpleName().toString();
        //使用注解的屬性的類型
        String type = variableElement.asType().toString();
        //view.name = (type)view.findViewById(id)
        String findViewCode = ParamName + "." + name + "=(" + type + ")" + ParamName +
                ".findViewById(" + id + ");\n";
        code.append(findViewCode);
    }
    return code.toString();
}

上面定義了三個方法,一個生成類既琴,另外兩個是對bindView方法的代碼生成占婉,相信大家細心點看還是看得懂的。

所有的代碼工作做好了后甫恩,緊接著需要去注冊和啟動BinderProcessor

image.png

此處是google提供的AutoService注解逆济,用來掃描工程注解的掃描器,第二個要注意的地方是啟動掃描器:
image.png

在main下面生成javax.annotation.processing.Processor文件磺箕,里面寫上要被啟動的掃描器:

com.single.router_compiler.BinderProcessor

使用
由于我們生成的APT代碼奖慌,肯定只有在運行期才能使用的,所以在編譯之前肯定是找不到***_ViewBinding類的松靡,因此咋們得需要寫個反射調(diào)用該類的工具類:

public class BinderViewTools {
    public static void init(Activity activity) {
        Class clazz = activity.getClass();
        try {
            Class<?> bindClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method bind = bindClass.getMethod("bindView", class);
            bind.invoke(bindClass.newInstance(), activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

不熟悉反射的伙伴們可以看下反射如何使用简僧,這里就不多說了。更多反射知識雕欺,在activity中直接使用:

BinderViewTools.init(this);

接著編譯下工程岛马,在app目錄的build下面可以看到生成了activity對應的*** _ViewBinding類:


image.png

完整代碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屠列,隨后出現(xiàn)的幾起案子啦逆,更是在濱河造成了極大的恐慌,老刑警劉巖笛洛,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夏志,死亡現(xiàn)場離奇詭異,居然都是意外死亡苛让,警方通過查閱死者的電腦和手機沟蔑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝌诡,“玉大人溉贿,你說我怎么就攤上這事枫吧∑趾担” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵九杂,是天一觀的道長颁湖。 經(jīng)常有香客問我宣蠕,道長,這世上最難降的妖魔是什么甥捺? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任抢蚀,我火速辦了婚禮,結(jié)果婚禮上镰禾,老公的妹妹穿的比我還像新娘皿曲。我一直安慰自己,他們只是感情好吴侦,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布屋休。 她就那樣靜靜地躺著,像睡著了一般备韧。 火紅的嫁衣襯著肌膚如雪劫樟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天织堂,我揣著相機與錄音叠艳,去河邊找鬼。 笑死易阳,一個胖子當著我的面吹牛附较,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潦俺,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼翅睛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了黑竞?” 一聲冷哼從身側(cè)響起捕发,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎很魂,沒想到半個月后扎酷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡遏匆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年法挨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幅聘。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡凡纳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帝蒿,到底是詐尸還是另有隱情荐糜,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站暴氏,受9級特大地震影響延塑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜答渔,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一关带、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沼撕,春花似錦宋雏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冲呢,卻和暖如春舍败,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敬拓。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工邻薯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乘凸。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓厕诡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親营勤。 傳聞我的和親對象是個殘疾皇子灵嫌,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容