大話AOP與Android的愛(ài)恨情仇

1. AOP與OOP的區(qū)別

平時(shí)我接觸多的就是OOP(Object Oriented Programming面向?qū)ο螅?、AOP(Aspect Oriented Programming面向切面)這兩種編程方式,我用自己的語(yǔ)言來(lái)解釋一下這兩者的區(qū)別:
OOP:

專業(yè)術(shù)語(yǔ): OOP(面向?qū)ο缶幊蹋┽槍?duì)業(yè)務(wù)處理過(guò)程的實(shí)體及其屬性和行為進(jìn)行抽象封裝,以獲得更加清晰高效的邏輯單元?jiǎng)澐帧?br> 面向?qū)ο髠?cè)重靜態(tài),名詞,狀態(tài),組織,數(shù)據(jù),載體是空間;

大白話: OOP面向?qū)ο蟮娜筇卣?: 封裝 , 繼承 , 多態(tài) 。這些特征也說(shuō)明了OOP是面向?qū)ο蟮?,我們做什么都是考慮一個(gè)對(duì)象,我們需要完成一個(gè)任務(wù)的時(shí)候一般都想著把一些操作封裝成一個(gè)類,所有的變量和操作都封裝到一個(gè)類里面,那么這個(gè)類就是我們的對(duì)象,我們要實(shí)現(xiàn)某個(gè)特定的功能,首先也想想著在這個(gè)對(duì)象里面去實(shí)現(xiàn)。
面向?qū)ο笠彩怯忻黠@缺點(diǎn)的,比如我們想實(shí)現(xiàn)某些不是常用的功能,我們需要去在需要的對(duì)象中去一一實(shí)現(xiàn)這些功能,并我們要不斷去維護(hù)這些功能,一旦多了我們就會(huì)很累的。
比如Android中一些按鍵統(tǒng)計(jì)、生命周期統(tǒng)計(jì),特定統(tǒng)計(jì)都是比較瑣碎的事情,要利用面向?qū)ο蟮乃枷肴?shí)現(xiàn)都不是很完美,這就要求去一一實(shí)現(xiàn),顯得很瑣碎。

AOP:

專業(yè)術(shù)語(yǔ): AOP則是針對(duì)業(yè)務(wù)處理過(guò)程中的切面進(jìn)行提取,它所面對(duì)的是處理過(guò)程中的某個(gè)步驟或階段,以獲得邏輯過(guò)程中各部分之間低耦合性的隔離效果。

大白話: AOP面向切面,我在使用的時(shí)候是關(guān)注具體的方法和功能切入點(diǎn),不需要知道也不用關(guān)心所在什么類或者是什么對(duì)象,我們只關(guān)注功能的實(shí)現(xiàn),具體對(duì)象是誰(shuí),愛(ài)誰(shuí)誰(shuí)!

網(wǎng)上很流行的說(shuō)明段子:

舉個(gè)簡(jiǎn)單的例子,對(duì)于“雇員”這樣一個(gè)業(yè)務(wù)實(shí)體進(jìn)行封裝,自然是OOP/OOD的任務(wù),我們可以為其建立一個(gè)“Employee”類,并將“雇員”相關(guān)的屬性和行為封裝其中。而用AOP設(shè)計(jì)思想對(duì)“雇員”進(jìn)行封裝將無(wú)從談起。同樣,對(duì)于“權(quán)限檢查”這一動(dòng)作片斷進(jìn)行劃分,則是AOP的目標(biāo)領(lǐng)域。而通過(guò)OOD/OOP對(duì)一個(gè)動(dòng)作進(jìn)行封裝,則有點(diǎn)不倫不類。換而言之,OOD/OOP面向名詞領(lǐng)域,AOP面向動(dòng)詞領(lǐng)域。
如果說(shuō)面向?qū)ο缶幊淌顷P(guān)注將需求功能劃分為不同的并且相對(duì)獨(dú)立,封裝良好的類,并讓它們有著屬于自己的行為,依靠繼承和多態(tài)等來(lái)定義彼此的關(guān)系的話;那么面向方面編程則是希望能夠?qū)⑼ㄓ眯枨蠊δ軓牟幌嚓P(guān)的類當(dāng)中分離出來(lái),能夠使得很多類共享一個(gè)行為,一旦發(fā)生變化,不必修改很多類,而只需要修改這個(gè)行為即可。
面向方面編程和面向?qū)ο缶幊滩坏皇腔ハ喔?jìng)爭(zhēng)的技術(shù)而且彼此還是很好的互補(bǔ)。面向?qū)ο缶幊讨饕糜跒橥粚?duì)象層次的公用行為建模。它的弱點(diǎn)是將公共行為應(yīng)用于多個(gè)無(wú)關(guān)對(duì)象模型之間。而這恰恰是面向方面編程適合的地方。有了 AOP,我們可以定義交叉的關(guān)系,并將這些關(guān)系應(yīng)用于跨模塊的、彼此不同的對(duì)象模型。AOP 同時(shí)還可以讓我們層次化功能性而不是嵌入功能性,從而使得代碼有更好的可讀性和易于維護(hù)。它會(huì)和面向?qū)ο缶幊毯献鞯煤芎谩?/p>

上面的段子不知道誰(shuí)說(shuō)的,很流行,但確實(shí)說(shuō)得很明白!

2. AOP的Java實(shí)現(xiàn)方式

上面說(shuō)過(guò),AOP關(guān)注的方法功能點(diǎn),事先不知道所在對(duì)象是誰(shuí),當(dāng)然程序的運(yùn)行都是需要拿到對(duì)象在運(yùn)行的,要在知道方法功能點(diǎn)的前提下拿到對(duì)象并執(zhí)行,這就需要用到Java的動(dòng)態(tài)代理。
在java的動(dòng)態(tài)代理機(jī)制中,有兩個(gè)重要的類或接口,一個(gè)是 InvocationHandler(Interface)、另一個(gè)則是 Proxy(Class),我們可以自己寫代碼去定義自己的動(dòng)態(tài)代理,去實(shí)現(xiàn)AOP,但是太麻煩了。Java有很多AOP實(shí)現(xiàn)的庫(kù),JavaWeb里面有JBoss、SpringFramework、AspectJ等等。Android我熟悉,Android中基于AOP實(shí)現(xiàn)的庫(kù)有ButterKnife、dagger2、EventBus3.0、Retrofit 2.0等等,不是說(shuō)這些庫(kù)完全基于AOP實(shí)現(xiàn),只是說(shuō)里面部分功能基于AOP。

3. Java Annotation

Java Annotation是JDK5.0引入的一種注釋機(jī)制。Java源代碼里面有自己的注解,我們還是很常見(jiàn)的:

java中的Annotation:

@Deprecated  -- @Deprecated 所標(biāo)注內(nèi)容,不再被建議使用。
@Override    -- @Override 只能標(biāo)注方法,表示該方法覆蓋父類中的方法。
@Documented  -- @Documented 所標(biāo)注內(nèi)容,可以出現(xiàn)在javadoc中。
@Inherited   -- @Inherited只能被用來(lái)標(biāo)注“Annotation類型”,它所標(biāo)注的Annotation具有繼承性。
@Retention   -- @Retention只能被用來(lái)標(biāo)注“Annotation類型”,而且它被用來(lái)指定Annotation的RetentionPolicy屬性。
@Target      -- @Target只能被用來(lái)標(biāo)注“Annotation類型”,而且它被用來(lái)指定Annotation的ElementType屬性。
@SuppressWarnings -- @SuppressWarnings 所標(biāo)注內(nèi)容產(chǎn)生的警告,編譯器會(huì)對(duì)這些警告保持靜默。

當(dāng)然我們也可以自定義注解,只是我們自定的注解需要我們自己去反射或者動(dòng)態(tài)代理來(lái)處理注解一次來(lái)實(shí)現(xiàn)AOP,自己處理還是比較麻煩,反射也是比較耗費(fèi)性能的,不建議使用,最好是Annotation+APT來(lái)做,把注解翻譯成Java代碼,避免性能損耗,APT后面會(huì)說(shuō)到。

Java Annotation的本質(zhì)是使用了java的動(dòng)態(tài)代理,Annotation并不代表AOP,Java的AOP實(shí)現(xiàn)可以基于Java動(dòng)態(tài)代理,也可以基于Annotation,但是自定義的Annotation需要結(jié)合 Java的動(dòng)態(tài)代理才可以實(shí)現(xiàn)AOP。

雖然源代碼我沒(méi)看過(guò),JavaWeb的一些庫(kù)JBoss、SpringFramework、AspectJ等等也是有使用Java Annotation,不可能所有的AOP都是完全自己去用動(dòng)態(tài)代理實(shí)現(xiàn),不然寫起來(lái)也費(fèi)勁,用起來(lái)也不方便。
Java的Class類中有一系列支持Annotation和反射的實(shí)現(xiàn):

這里寫圖片描述

[點(diǎn)擊查看大圖]

里面有一個(gè)很關(guān)鍵的接口:AnnotatedElement,里面是Class反射時(shí)對(duì)Annotation支持的一系列方法:

這里寫圖片描述

[點(diǎn)擊查看大圖]

具體的功能我就不解釋了,自己去查一下。
基于java的Annotation中的Target和Retention結(jié)合類似反射原理我們可以實(shí)現(xiàn)自己的AOP。很多Android的AOP實(shí)現(xiàn)也是這么玩的。

4. Annotation的Android實(shí)現(xiàn)方式

Android是用Java寫的,上面說(shuō)到了的上面Java Annotation 動(dòng)態(tài)代理當(dāng)然都適用于Android,但是Android也有自己的AOP實(shí)現(xiàn)方式,但是Android的AOP實(shí)現(xiàn)原理跟Java的實(shí)現(xiàn)原理是一樣的。

再次強(qiáng)調(diào):

Java Annotation的本質(zhì)是使用了java的動(dòng)態(tài)代理,Annotation并不代表AOP,Java的AOP實(shí)現(xiàn)可以基于Java動(dòng)態(tài)代理,也可以基于Annotation,但是自定義的Annotation需要結(jié)合 Java的動(dòng)態(tài)代理才可以實(shí)現(xiàn)AOP。

4.1 Android自帶基于Annotation的AOP實(shí)現(xiàn)

Android源碼中也有Google官方自定義的AndroidAnnotations,下面是代碼結(jié)構(gòu):

這里寫圖片描述

[點(diǎn)擊查看大圖]

右邊代碼展示的是Android自帶的Keep注解,就是防止代碼混淆用到的,可以看到Keep同樣是基于Java Annotation自定義的而來(lái)的。源代碼我沒(méi)去看,Android自定的注解實(shí)現(xiàn)也是基于Annotation和APT(注解處理工具)的AOP操作。

4.2 自定義Annotation加反射實(shí)現(xiàn)findViewById

自己的Annotation:

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

Annotation反射處理工具:

public class AnnotateUtils {
    public static void injectViews(Activity activity) {
        Class<? extends Activity> object = activity.getClass(); // 獲取activity的Class
        Field[] fields = object.getDeclaredFields(); // 通過(guò)Class獲取activity的所有字段
        for (Field field : fields) { // 遍歷所有字段
            // 獲取字段的注解,如果沒(méi)有ViewInject注解,則返回null
            ViewInject viewInject = field.getAnnotation(ViewInject.class); 
            if (viewInject != null) {
                int viewId = viewInject.value(); // 獲取字段注解的參數(shù),這就是我們傳進(jìn)去控件Id
                if (viewId != -1) {
                    try {
                        // 獲取類中的findViewById方法,參數(shù)為int
                        Method method = object.getMethod("findViewById", int.class);
                        // 執(zhí)行該方法,返回一個(gè)Object類型的View實(shí)例
                        Object resView = method.invoke(activity, viewId);
                        field.setAccessible(true);
                        // 把字段的值設(shè)置為該View的實(shí)例
                        field.set(activity, resView);
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

使用示例:

@ViewInject(R.id.buy)
private Button buy;
@ViewInject(R.id.money)
private TextView money;
@ViewInject(R.id.tv_power)
private TextView power;
@ViewInject(R.id.tv_life)
private TextView life;
@ViewInject(R.id.tv_dex)
private TextView dex;

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

4.3 Android開(kāi)源庫(kù)使用Java Annotation

下面是 butterknife使用的注解:

這里寫圖片描述

[點(diǎn)擊查看大圖]

左邊是butterknife的注解,右邊是最常用的View綁定注解BindView,可以看到也是使用了java的Annotation中的@Retention()和@Target()。
其他的庫(kù)其實(shí)也差不多,這里就不一一截圖驗(yàn)證了。

雖然Java Annotation并不是AOP,但是為了方便使用AOP,為了更好的使用我們往往需要使用Annotation來(lái)輔助。具體體現(xiàn)就是這些開(kāi)源庫(kù)的各種注解。

黃金天然不是貨幣,而貨幣天然是黃金,是不是可以這樣撕逼一下:Annotation天然不是AOP,而AOP天然是Annotation。

5. 大話AOP與Android的愛(ài)恨情仇之AOP三大精鋼

三大精鋼分別是:APT、AspectJ、Javassist
下面是這三個(gè)的作用時(shí)間:


這里寫圖片描述

5.1 APT

APT用來(lái)在編譯時(shí)期掃描處理源代碼中的注解信息,我們可以根據(jù)注解信息生成一些文件,比如Java文件。利用APT為我們生成的Java代碼,實(shí)現(xiàn)冗余的代碼功能,這樣就減少手動(dòng)的代碼輸入,提升了編碼效率,而且使源代碼看起來(lái)更清晰簡(jiǎn)潔。

請(qǐng)參考:利用APT實(shí)現(xiàn)Android編譯時(shí)注解

Dagger、ButterKnife、AndroidAnnotation、EventBus的注解實(shí)現(xiàn)AOP為什么非要使用APT?
如果不使用用APT基于注解動(dòng)態(tài)生成java代碼,那么就需要在運(yùn)行的時(shí)候使用反射或者動(dòng)態(tài)代理,那么耗費(fèi)了不必要的性能,本來(lái)就是為了方便的庫(kù)反而更加耗費(fèi)性能那就沒(méi)人用了,使用APT增加了代碼量,但是不耗費(fèi)性能,用起來(lái)方便高性能無(wú)察覺(jué)。

可以說(shuō)基于Java Annotation的自定義注解配合APT實(shí)現(xiàn)了很多簡(jiǎn)單易用性能優(yōu)越的AOP用法的開(kāi)源庫(kù)。

5.2 AspectJ

  1. AspectJ是一個(gè)代碼生成工具(Code Generator)。

  2. AspectJ語(yǔ)法就是用來(lái)定義代碼生成規(guī)則的語(yǔ)法。

Aspectj一個(gè)易用的、功能強(qiáng)大的aop編程語(yǔ)言,AspectJ是AOP的Java語(yǔ)言的實(shí)現(xiàn),AspectJ是一個(gè)代碼生成工具(Code Generator)。
使用AspectJ有兩種方法:

  1. 完全使用AspectJ的語(yǔ)言。這語(yǔ)言一點(diǎn)也不難,和Java幾乎一樣,也能在AspectJ中調(diào)用Java的任何類庫(kù)。AspectJ只是多了一些關(guān)鍵詞罷了。
  2. 或者使用純Java語(yǔ)言開(kāi)發(fā),然后使用AspectJ注解,簡(jiǎn)稱@AspectJ。

使用AspectJ用java開(kāi)發(fā)時(shí)使用的AspectJ注解其實(shí)也是基于Java Annotation的,下面看結(jié)構(gòu)圖:

這里寫圖片描述

[點(diǎn)擊查看大圖]

左邊是所有的AspectJ注解,右邊是AspectJ注解中最關(guān)鍵的Aspect注解,可以看到也是使用了java的Annotation中的@Retention()和@Target()。
這是我之前做的基于AspectJ做的統(tǒng)計(jì),生命周期監(jiān)聽(tīng)和click監(jiān)聽(tīng):
https://github.com/Dawish/CustomViews/tree/master/Aoplib
請(qǐng)參考牛逼的文章:http://blog.csdn.net/innost/article/details/49387395

關(guān)鍵詞 說(shuō)明
Before 切入點(diǎn)之前執(zhí)行,切入點(diǎn)執(zhí)行之前我們可以先執(zhí)行我們的方法,可以攔截切入點(diǎn)的執(zhí)行,比如攔截需要用戶支付或者登陸的操作
after 切入點(diǎn)之后執(zhí)行 ,比如記錄用戶行為的操作
around 包含切入點(diǎn)的前后,切入點(diǎn)的執(zhí)行可以控制。比如需要條件判斷再執(zhí)行,執(zhí)行完成后給用戶提示的操作

需要著重說(shuō)一下Advice,Advice就是我們插入的代碼以何種方式插入,有Before還有After、Around。

關(guān)鍵詞 說(shuō)明
Before 切入點(diǎn)之前執(zhí)行,切入點(diǎn)執(zhí)行之前我們可以先執(zhí)行我們的方法,可以攔截切入點(diǎn)的執(zhí)行,比如攔截需要用戶支付或者登陸的操作
after 切入點(diǎn)之后執(zhí)行 ,比如記錄用戶行為的操作
around 包含切入點(diǎn)的前后,切入點(diǎn)的執(zhí)行可以控制。比如需要條件判斷再執(zhí)行,執(zhí)行完成后給用戶提示的操作

示例:

    /**下面第一個(gè)*號(hào)表示被切入的方法不限定返回類型**/
    @Before("execution(* android.view.View.OnClickListener.onClick(..))")
    public void onUserAction(JoinPoint joinPoint){
        Log.e(TAG, "aop statistics click");
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }
        View clickView = (View) args[0];
        //你想做的操作
    }

上面是在執(zhí)行onClick之前切入,我們可以根據(jù)JoinPoint 來(lái)獲取onClick(View v)方法本身的參數(shù),來(lái)對(duì)點(diǎn)擊的View做操作,我們也可以根據(jù)用戶的連續(xù)點(diǎn)擊間隔時(shí)間來(lái)防止點(diǎn)擊速度過(guò)快導(dǎo)致的雙擊。
是不是很強(qiáng)大。
我們可以用JoinPoint 參數(shù)來(lái)獲取更多的內(nèi)容:

  • java.lang.Object[] getArgs():獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?/li>
  • Signature getSignature() :獲取連接點(diǎn)的方法簽名對(duì)象;
  • java.lang.Object getTarget() :獲取連接點(diǎn)所在的目標(biāo)對(duì)象;
  • java.lang.Object getThis() :獲取代理對(duì)象本身;

有了Before、After、Around和JoinPoint 的四者配合,真是6得飛起,可上天入地啊,可以很方便完成Android開(kāi)發(fā)中很惡心很繁瑣的任務(wù)。


這里寫圖片描述

來(lái),我們走一個(gè)!

5.3 Javassist

Javassist是一個(gè)開(kāi)源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba
(千葉 滋)所創(chuàng)建的。它已加入了開(kāi)放源代碼JBoss
應(yīng)用服務(wù)器項(xiàng)目,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)"AOP"框架。 -- 百度百科

代表框架:熱修復(fù)框架HotFix 、Savior(InstantRun)等

Javassist作用是在編譯器間修改class文件,與之相似的ASM(熱修復(fù)框架女媧)也有這個(gè)功能,可以讓我們直接修改編譯后的class二進(jìn)制代碼,首先我們得知道什么時(shí)候編譯完成,并且我們要趕在class文件被轉(zhuǎn)化為dex文件之前去修改。在Transfrom這個(gè)api出來(lái)之前,想要在項(xiàng)目被打包成dex之前對(duì)class進(jìn)行操作,必須自定義一個(gè)Task,然后插入到predex或者dex之前,在自定義的Task中可以使用javassist或者asm對(duì)class進(jìn)行操作。而Transform則更為方便,Transfrom會(huì)有他自己的執(zhí)行時(shí)機(jī),不需要我們插入到某個(gè)Task前面。Tranfrom一經(jīng)注冊(cè)便會(huì)自動(dòng)添加到Task執(zhí)行序列中,并且正好是項(xiàng)目被打包成dex之前。 來(lái)自--- http://www.lxweimin.com/p/dca3e2c8608a

Android熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)請(qǐng)參考:http://blog.csdn.net/u010386612/article/details/51131642

Javassist我本身不是很熟,我就不多說(shuō)了,反正都TMD超級(jí)牛逼的東西。

本人家技術(shù)有限,上面很多都是自己基于自己的知識(shí)面說(shuō)的,不足不對(duì)之處還望各位同行不吝指教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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