AOP學(xué)習(xí)總結(jié)-利用APT仿寫(xiě)B(tài)utterKnife

在應(yīng)用 AOP 之前呻拌,應(yīng)該思考幾個(gè)問(wèn)題:

  1. 明確你應(yīng)用 AOP 在什么項(xiàng)目

小范圍試用萌朱,選擇一個(gè)侵入性小的 AOP 方法

  1. 明確切入點(diǎn)的相似性

考慮切入點(diǎn)的數(shù)量和相似性,確定你是否愿意一個(gè)個(gè)在切點(diǎn)上加注红省。 解還是用相似性統(tǒng)一切入额各。

  1. 明確織入的粒度和織入的時(shí)機(jī)

怎么選擇織入的時(shí)機(jī)?編譯期間織入吧恃,還是編譯后虾啦?載入時(shí)?或是運(yùn)行時(shí)痕寓?通過(guò)比較各大 AOP 方法在織入時(shí)機(jī)方面的不同和優(yōu)缺點(diǎn)傲醉,來(lái)獲得對(duì)于如何選擇織入時(shí)機(jī)進(jìn)行判定的準(zhǔn)則。

  1. 明確對(duì)性能的要求呻率,明確對(duì)方法數(shù)的要求

除了動(dòng)態(tài)織入硬毕,其他 AOP 方法對(duì)性能的影響可以忽略不計(jì),看各自方法的優(yōu)缺點(diǎn)進(jìn)行權(quán)衡礼仗。

  1. 明確是否需要修改原有類(lèi)

  2. 明確調(diào)用的時(shí)機(jī)

仿造 ButterKnife

步驟:

  1. 定義注解
  2. 編寫(xiě)注解處理器
  3. 掃描注解
  4. 編寫(xiě)代理類(lèi)內(nèi)容
  5. 生成代理類(lèi)
  6. 調(diào)用代理類(lèi)

第一步:

新建一個(gè)名為 annoation 的 Java Library吐咳,里面存放注解。

新建一個(gè)名為 compiler 的 Java Library元践,里面實(shí)現(xiàn) APT挪丢,compiler 引用 annoation。

新建一個(gè)名為 code 的 android Library卢厂,里面封裝對(duì) APT 調(diào)用的門(mén)面乾蓬,code 引用 annoation。

第二步:

在 annoation 中編寫(xiě)注解 BindView

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}

第三步:

在 compiler 中編寫(xiě) APT 類(lèi) BindViewProcessor:

@AutoService(Processor.class) //自動(dòng)注冊(cè)
@SupportedAnnotationTypes("com.lzx.annoation.BindView") //指定解析注解
public class BindViewProcessor extends AbstractProcessor {

}

BindViewProcessor 類(lèi)要繼承 AbstractProcessor慎恒,并且添加 @AutoService 注解任内,參數(shù)填 Processor.class撵渡,這樣它就能自動(dòng)注冊(cè),為什么要自動(dòng)注冊(cè)死嗦,因?yàn)橄胍\(yùn)行注解處理器趋距,需要繁瑣的步驟:

  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 文件寫(xiě)入注解處理器的全稱(chēng),包括包路徑摘盆;

注解 @SupportedAnnotationTypes 參數(shù)是我們剛剛編寫(xiě)的 BindView 注解的路徑翼雀,這樣的意思是指定解析 BindView。

然后重寫(xiě) init 方法:

private Messager mMessager;
private Filer mFiler;
private Elements mElements;
private Map<String, List<Element>> classMap = new HashMap<>();

@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);
    mMessager = processingEnvironment.getMessager();
    mFiler = processingEnvironment.getFiler();
    mElements = processingEnvironment.getElementUtils();
    mMessager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor  init");
}

在 init 方法里面孩擂,一般是給一些變量賦值狼渊。以上的寫(xiě)法可以作為模版寫(xiě)法,就是說(shuō)基本都會(huì)這樣寫(xiě)类垦。

重點(diǎn)在于重寫(xiě) process 方法:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mMessager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor  process");
    //typeElement就相當(dāng)于com.lzx.annoation.BindView
    //通過(guò)roundEnvironment來(lái)獲取所有被BindView注解注解了的字段
    for (TypeElement typeElement : set) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, "typeElement = " + typeElement.toString());
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);
        for (Element element : elementsAnnotatedWith) {
            //如果在MainActivity中有這段代碼 @BindView(R.id.textView)TextView textView;
            //此處的element就是TextView節(jié)點(diǎn)元素
            //element.getEnclosingElement()為獲取其父節(jié)點(diǎn)元素狈邑,即MainActivty
            Element enclosingElement = element.getEnclosingElement();
            TypeMirror classTypeMirror = enclosingElement.asType();
            //className為MainActivty的全類(lèi)名
            String className = classTypeMirror.toString();
            //上面只是拿MainActivty舉個(gè)例子,但是真實(shí)的使用注解的可能還有SecondActivity等等蚤认,
            // 所有需要以類(lèi)名為鍵保存里面所有使用了BindView注解的節(jié)點(diǎn)
            List<Element> elements = classMap.get(className);
            if (elements == null) {
                elements = new ArrayList<>();
                elements.add(element);
                classMap.put(className, elements);
            } else {
                elements.add(element);
            }
        }

        Set<Map.Entry<String, List<Element>>> entries = classMap.entrySet();
        for (Map.Entry<String, List<Element>> entry : entries) {
            String key = entry.getKey();
            List<Element> value = entry.getValue();
            //生成java代碼
            generateViewBinding(key, value);
        }
    }
    return false;
}

下面來(lái)一一分析一下:

typeElement 就相當(dāng)于 com.lzx.annoation.BindView米苹,通過(guò)遍歷 set 得到,通過(guò)roundEnvironment 來(lái)獲取所有被 BindView 注解注解了的字段:

Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(typeElement);

遍歷所有被標(biāo)記字段:

for (Element element : elementsAnnotatedWith) {
    //如果在 MainActivity 中有這段代碼 @BindView(R.id.textView) TextView textView;
    //此處的 element 就是 TextView 節(jié)點(diǎn)元素
    //element.getEnclosingElement() 為獲取其父節(jié)點(diǎn)元素砰琢,即 MainActivty
    Element enclosingElement = element.getEnclosingElement();
    TypeMirror classTypeMirror = enclosingElement.asType();
    
    //className 為 MainActivty 的全類(lèi)名
    String className = classTypeMirror.toString();
    
    //上面只是拿 MainActivty 舉個(gè)例子驱入,但是真實(shí)的使用注解的可能還有 SecondActivity 等等,
    //所以需要以類(lèi)名為鍵保存里面所有使用了 BindView 注解的節(jié)點(diǎn)
    List<Element> elements = classMap.get(className);
    if (elements == null) {
        elements = new ArrayList<>();
        elements.add(element);
        classMap.put(className, elements);
    } else {
        elements.add(element);
    }
}

接下來(lái)遍歷節(jié)點(diǎn)去生成代碼氯析,生成代碼是通過(guò) JavaPoet 框架去完成:

Set<Map.Entry<String, List<Element>>> entries = classMap.entrySet();
for (Map.Entry<String, List<Element>> entry : entries) {
    String key = entry.getKey();
    List<Element> value = entry.getValue();
    //生成java代碼
    generateViewBinding(key, value);
}

generateViewBinding方法:

private void generateViewBinding(String key, List<Element> value) {
    // 生成類(lèi)元素節(jié)點(diǎn)
    TypeElement classTypeElement = mElements.getTypeElement(key);
    // 生成參數(shù) final MainActivity target
    ParameterSpec targetParameterSpec = ParameterSpec
            .builder(ClassName.get(classTypeElement), "target", Modifier.FINAL)
            .build();
    //生成參數(shù)  View source
    ParameterSpec viewParameterSpec = ParameterSpec
            .builder(ClassName.get("android.view", "View"), "source")
            .build();
    MethodSpec methodSpec = null;
    // 生成構(gòu)造函數(shù)
    MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
            .addParameter(targetParameterSpec)
            .addParameter(viewParameterSpec)
            .addAnnotation(ClassName.bestGuess("android.support.annotation.UiThread"))
            .addModifiers(Modifier.PUBLIC);
    // 構(gòu)造函數(shù)中添加代碼塊
    constructorMethodBuilder.addStatement("this.target = target");
    for (Element element : value) {
        BindView bindView = element.getAnnotation(BindView.class);
        int id = bindView.value();
        Name simpleName = element.getSimpleName();
        constructorMethodBuilder.addStatement("target.$L = source.findViewById($L)", simpleName.toString(), id);
    }
    methodSpec = constructorMethodBuilder.build();
    // 生成unbind方法
    MethodSpec.Builder unbindMethodSpec = MethodSpec.methodBuilder("unbind")
            .addModifiers(Modifier.PUBLIC);
    unbindMethodSpec.addStatement("$T target = this.target", ClassName.get(classTypeElement));
    unbindMethodSpec.addStatement("this.target = null");
    for (Element element : value) {
        Name simpleName = element.getSimpleName();
        unbindMethodSpec.addStatement("target.$L = null", simpleName.toString());
    }
    // 生成MainActivity_ViewBinding類(lèi)
    TypeSpec typeSpec = TypeSpec.classBuilder(classTypeElement.getSimpleName() + "_ViewBinding")
            .addField(ClassName.get(classTypeElement), "target", Modifier.PRIVATE)
            .addMethod(methodSpec)
            .addMethod(unbindMethodSpec.build())
            .addSuperinterface(ClassName.bestGuess("com.lzx.code.Unbinder"))
            .addModifiers(Modifier.PUBLIC)
            .build();
    // 獲取包名
    String packageName = mElements.getPackageOf(classTypeElement).getQualifiedName().toString();
    mMessager.printMessage(Diagnostic.Kind.NOTE, "packageName = " + packageName);
    JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
    //寫(xiě)入java文件
    try {
        javaFile.writeTo(mFiler);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

可以看到上面生成的類(lèi)的類(lèi)名是由 Activity 名字加上 _ViewBinding 組成的。

第四步:

在 code 中編寫(xiě)門(mén)面代碼:

ButterKnife:

public class ButterKnife {
    static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return createBinding(target, sourceView);
    }

    public static Unbinder bind(@NonNull Fragment target, View sourceView){
        return createBinding(target,sourceView);
    }

    public static Unbinder bind(@NonNull android.app.Fragment target, View sourceView){
        return createBinding(target,sourceView);
    }

    private static Unbinder createBinding(Object target, View sourceView) {
        Class<?> targetClass = target.getClass();
        Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
        if (constructor == null) {
            return Unbinder.EMPTY;
        }
        try {
            return constructor.newInstance(target, sourceView);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to invoke " + constructor, e);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            if (cause instanceof Error) {
                throw (Error) cause;
            }
            throw new RuntimeException("Unable to create binding instance.", cause);
        }
    }

    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> targetClass) {
        Constructor<? extends Unbinder> constructor = BINDINGS.get(targetClass);
        if (constructor != null) {
            return constructor;
        }

        String targetClassName = targetClass.getName();
        try {
            Class<?> viewBindingClass = Class.forName(targetClassName + "_ViewBinding");
            constructor = (Constructor<? extends Unbinder>) viewBindingClass.getConstructor(targetClass, View.class);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        BINDINGS.put(targetClass, constructor);

        return constructor;
    }
}

上面代碼的邏輯就是通過(guò) findBindingConstructorForClass 方法找到剛剛生成的類(lèi)莺褒。然后在 createBinding 方法中通過(guò)反射去實(shí)例化它掩缓,這當(dāng)中使用了緩存,提高了性能遵岩。

還有上面出現(xiàn)的 Unbinder 接口:

public interface Unbinder {
    @UiThread
    void unbind();

    Unbinder EMPTY = new Unbinder() {
        @Override public void unbind() { }
    };
}

第五步:

在 APP 工程去引用上面的代碼:

dependencies {
    implementation project(':code')
    annotationProcessor project(':compiler')
}

最后看看生成的代碼是怎么樣的:
MainActivity_ViewBinding:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;
    target.mTextView = source.findViewById(2131165209);
  }

  public void unbind() {
    MainActivity target = this.target;
    this.target = null;
    target.mTextView = null;
  }
}

對(duì)應(yīng)著生成的代碼再看剛剛的 generateViewBinding 方法你辣,是否就一目了然。

看看如何使用:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.add) TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ButterKnife.bind(this);

        mTextView.setText("Hello APT");
    }
}

回過(guò)頭來(lái)看看 ButterKnife 是怎么使用 APT 的:

15572231486342.jpg

你可能發(fā)現(xiàn)了尘执,最后一個(gè)步驟是在合適的時(shí)機(jī)去調(diào)用代理類(lèi)或門(mén)面對(duì)象舍哄。這就是 APT 的缺點(diǎn)之一,在任意包位置自動(dòng)生成代碼但是運(yùn)行時(shí)卻需要主動(dòng)調(diào)用誊锭。

參考文章:一文應(yīng)用 AOP | 最全選型考量 + 邊剖析經(jīng)典開(kāi)源庫(kù)邊實(shí)踐表悬,美滋滋

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市丧靡,隨后出現(xiàn)的幾起案子蟆沫,更是在濱河造成了極大的恐慌籽暇,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭庞,死亡現(xiàn)場(chǎng)離奇詭異戒悠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)舟山,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)绸狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人累盗,你說(shuō)我怎么就攤上這事寒矿。” “怎么了幅骄?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵劫窒,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拆座,道長(zhǎng)主巍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任挪凑,我火速辦了婚禮孕索,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躏碳。我一直安慰自己搞旭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布菇绵。 她就那樣靜靜地躺著肄渗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咬最。 梳的紋絲不亂的頭發(fā)上翎嫡,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音永乌,去河邊找鬼惑申。 笑死,一個(gè)胖子當(dāng)著我的面吹牛翅雏,可吹牛的內(nèi)容都是我干的圈驼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼望几,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绩脆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衙伶,失蹤者是張志新(化名)和其女友劉穎祈坠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體矢劲,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赦拘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芬沉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躺同。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丸逸,靈堂內(nèi)的尸體忽然破棺而出蹋艺,到底是詐尸還是另有隱情,我是刑警寧澤黄刚,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布捎谨,位于F島的核電站,受9級(jí)特大地震影響憔维,放射性物質(zhì)發(fā)生泄漏涛救。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一业扒、第九天 我趴在偏房一處隱蔽的房頂上張望检吆。 院中可真熱鬧,春花似錦程储、人聲如沸蹭沛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)摊灭。三九已至,卻和暖如春败徊,著一層夾襖步出監(jiān)牢的瞬間帚呼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工集嵌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人御毅。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓根欧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親端蛆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凤粗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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