Android AOP三劍客之APT

AOP概念

AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)虐沥。AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容冀痕,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離狸演,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低金度,提高程序的可重用性,同時(shí)提高了開發(fā)的效率严沥。

Android AOP三劍客: APT, AspectJ, Javassist
這是一張用爛了的圖.jpg

傳送門:android-aop-samples

工程目錄結(jié)構(gòu)

.

  • annotation 定義注解
  • apt-library apt輔助工具類
  • apt-processor 自定義注解解析器猜极,生成輔助代碼
  • apt-plugin 自定義Gradle插件,實(shí)現(xiàn)切面和操作字節(jié)碼的插件
  • app 主目錄

Android APT

APT(Annotation Processing Tool 的簡稱)消玄,可以在代碼編譯期解析注解跟伏,并且生成新的 Java 文件,減少手動(dòng)的代碼輸入◆婀希現(xiàn)在有很多主流庫都用上了 APT受扳,比如 Dagger2, ButterKnife, EventBus3 等

定義注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
創(chuàng)建一個(gè)Module,自定義AbstractProcessor兔跌,并且用@AutoService標(biāo)記
  • AutoService會(huì)自動(dòng)在META-INF文件夾下生成Processor配置信息文件勘高,該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候坟桅,
    就能通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名华望,并裝載實(shí)例化,完成模塊的注入
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassFactory> mProxyMap = new HashMap<>();

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    mMessager = processingEnv.getMessager();
    mElementUtils = processingEnv.getElementUtils();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
    HashSet<String> supportTypes = new LinkedHashSet<>();
    supportTypes.add(BindView.class.getCanonicalName());
    return supportTypes;
}

@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
    mProxyMap.clear();
    //得到所有的注解
    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    for (Element element : elements) {
        VariableElement variableElement = (VariableElement) element;
        TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
        String fullClassName = classElement.getQualifiedName().toString();
        //elements的信息保存到mProxyMap中
        ClassFactory proxy = mProxyMap.get(fullClassName);
        if (proxy == null) {
            proxy = new ClassFactory(mElementUtils, classElement);
            mProxyMap.put(fullClassName, proxy);
        }
        BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
        int id = bindAnnotation.value();
        proxy.putElement(id, variableElement);
    }

    //通過javapoet生成
    for (String key : mProxyMap.keySet()) {
        ClassFactory proxyInfo = mProxyMap.get(key);
        JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode()).build();
        try {
            // 生成文件
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
    return true;
}

}

process方法仅乓,處理我們自定義的注解赖舟,生成代碼,這里使用的squareup公司的javapoet庫輔助生成代碼

public class ClassFactory {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

public ClassFactory(Elements elementUtils, TypeElement classElement) {
    this.mTypeElement = classElement;
    PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
    String packageName = packageElement.getQualifiedName().toString();
    String className = mTypeElement.getSimpleName().toString();
    this.mPackageName = packageName;
    this.mBindingClassName = className + "_ViewBinding";
}

public void putElement(int id, VariableElement element) {
    mVariableElementMap.put(id, element);
}

public TypeSpec generateJavaCode() {
    TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
            .addModifiers(Modifier.PUBLIC)
            .addMethod(generateMethods())
            .build();
    return bindingClass;

}

    private MethodSpec generateMethods() {
    ClassName activity = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
            .addModifiers(Modifier.PUBLIC)
            .returns(void.class)
            .addParameter(activity, "activity");

    for (int id : mVariableElementMap.keySet()) {
        VariableElement element = mVariableElementMap.get(id);
        String name = element.getSimpleName().toString();
        String type = element.asType().toString();

        methodBuilder.addCode("activity." + name + " = " + "(" + type + ")(((android.app.Activity)activity).findViewById( " + id + "));\n\n");
    }

    return methodBuilder.build();
}


public String getPackageName() {
    return mPackageName;
}
}

generateMethods方法通過for循環(huán)構(gòu)建代碼夸楣,findViewById綁定view

我們先看下原本的MainActivity有什么東東

public class MainActivity extends AppCompatActivity {

@BindView(R.id.button)
Button button;

@BindView(R.id.tv)
TextView textView;

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

    BindViewTools.bind(this);
    textView.setText("bind Button success");
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            doMarkDown();
        }
    });
}
}

這個(gè)BindViewTools.bind(this)傳送門進(jìn)去看看

public class BindViewTools {
public static void bind(Activity activity) {
    Class clazz = activity.getClass();
    try {
        Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
        Method method = bindViewClass.getMethod("bind", activity.getClass());
        method.invoke(bindViewClass.newInstance(), activity);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

}
我們可以看到他是通過反射尋找的是MainActivity_ViewBinding的類宾抓,并進(jìn)行Id綁定的

可以在build/intermediates/classes/debug/包名/MainActivity_ViewBinding.class看到生成的代碼

這下就很清晰了

1.首先定義注解
2.BindViewProcessor里的process 解析注解,生成輔助類MainActivity_ViewBinding
3.在入口BindViewTools.bind(this)豫喧,反射找到生成的MainActivity_ViewBinding類并進(jìn)行findViewById石洗。

總結(jié)

本章節(jié)主要說了APT的簡單使用,從使用角度來說紧显,APT技術(shù)并沒有難度讲衫,重點(diǎn)是怎么設(shè)計(jì),在實(shí)際項(xiàng)目中可以把很多繁瑣重復(fù)性的工作鸟妙,通過APT來生成各種代碼.

作為老司機(jī)焦人,這是彎道超車的必備秘籍挥吵,天下武功、唯快不破花椭!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忽匈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子矿辽,更是在濱河造成了極大的恐慌丹允,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袋倔,死亡現(xiàn)場離奇詭異雕蔽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宾娜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門批狐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人前塔,你說我怎么就攤上這事嚣艇。” “怎么了华弓?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵食零,是天一觀的道長。 經(jīng)常有香客問我寂屏,道長贰谣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任迁霎,我火速辦了婚禮吱抚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欧引。我一直安慰自己频伤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布芝此。 她就那樣靜靜地躺著,像睡著了一般因痛。 火紅的嫁衣襯著肌膚如雪婚苹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天鸵膏,我揣著相機(jī)與錄音膊升,去河邊找鬼。 笑死谭企,一個(gè)胖子當(dāng)著我的面吹牛廓译,可吹牛的內(nèi)容都是我干的评肆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼非区,長吁一口氣:“原來是場噩夢啊……” “哼瓜挽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起征绸,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤久橙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后管怠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淆衷,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年渤弛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祝拯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡她肯,死狀恐怖佳头,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辕宏,我是刑警寧澤畜晰,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站瑞筐,受9級(jí)特大地震影響凄鼻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聚假,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一块蚌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膘格,春花似錦峭范、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至菜秦,卻和暖如春甜害,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背球昨。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工尔店, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓嚣州,卻偏偏與公主長得像鲫售,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子该肴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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