AOP總結(jié)

## AOP簡(jiǎn)介

###1.1 什么是AOP

AOP,Aspect Oriented Programming 面向切面編程

OOP力奋,Object-oriented programming面向?qū)ο缶幊?/p>

AOP和OOP是不同的編程思想脯燃。OOP強(qiáng)調(diào)的是高內(nèi)聚虐先,低耦合彰居,封裝浊竟。

提倡的是將功能模塊化暴氏,對(duì)象化翅敌。

可以通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)芙委。AOP實(shí)際是GoF設(shè)計(jì)模式的延續(xù)逞敷,設(shè)計(jì)模式孜孜不倦追求的是調(diào)用者和被調(diào)用者之間的解耦,提高代碼的靈活性和可擴(kuò)展性,AOP可以說也是這種目標(biāo)的一種實(shí)現(xiàn)灌侣。

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-cd2ec4aa590b5a08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

###1.2 AOP用途

將日志記錄推捐,性能統(tǒng)計(jì),安全控制侧啼,事務(wù)處理牛柒,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來,通過對(duì)這些行為的分離痊乾,我們希望可以將它們獨(dú)立到非指導(dǎo)業(yè)務(wù)邏輯的方法中皮壁,進(jìn)而改變這些行為的時(shí)候不影響業(yè)務(wù)邏輯的代碼。

###1.3 AOP方式對(duì)比選擇

| First Header | Hook時(shí)機(jī) | Android中應(yīng)用場(chǎng)景 |優(yōu)點(diǎn)|缺點(diǎn)|

| ------------ | ------------- | ------------ | ------------ |------------ |

| Dexposed | 運(yùn)行時(shí)動(dòng)態(tài)hook? | 滑動(dòng)流暢度監(jiān)控哪审,事件執(zhí)行監(jiān)控蛾魄,熱修復(fù) | 可以動(dòng)態(tài)監(jiān)控和系統(tǒng)通信的各種方法。| 不支持5.0以上手機(jī)|

| Xposed| 運(yùn)行時(shí)動(dòng)態(tài)hook | 同Dexposed? |可以動(dòng)態(tài)監(jiān)控和系統(tǒng)通信的各種方法湿滓。|不支持5.0以上手機(jī)滴须,必須root|

|Java Proxy |運(yùn)行時(shí)動(dòng)態(tài)hook |hook和系統(tǒng)通信接口例如:插件sdk |Java 原生API,沒有兼容性問題| 只能hook 有Interface的類|

| AspactJ| 編譯時(shí)修改代碼|統(tǒng)計(jì)方法執(zhí)行時(shí)長(zhǎng)叽奥,方法前后注入邏輯|Spring開源的AOP框架扔水,功能強(qiáng)大。注解很多朝氓∧校基本包括所有的編譯時(shí)注入方式|需要引入118K的jar|

|ASM|編譯時(shí)修改代碼|同AspactJ|字節(jié)碼操作庫|需要自己寫注解和編譯腳本。字節(jié)碼插入編寫比較費(fèi)勁

|Javassit|編譯時(shí)修改代碼|同AspactJ|基于java反射的字節(jié)碼操作類庫赵哲。對(duì)比ASM嘹狞,編寫簡(jiǎn)單|對(duì)比ASM,修改類時(shí)誓竿,執(zhí)行時(shí)間長(zhǎng)|

##Dexposed磅网,Xposed: 原理,應(yīng)用場(chǎng)景筷屡,demo

Dexposed是基于Xposed開發(fā)的hook自己app的庫涧偷。淘寶開源的簸喂。

原理:http://www.zhaoxiaodan.com/android/Android-Hook(1)-dexposed%E5%8E%9F%E7%90%86.html

http://blog.csdn.net/yueqian_scut/article/details/50939034

通過把原java方法的類型改為native來把對(duì)java函數(shù)的調(diào)用轉(zhuǎn)到native層,在native層用dvm的各種函數(shù)來操作Method的指針和對(duì)象來控制函數(shù)流程燎潮。

Git地址: https://github.com/alibaba/dexposed

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-1daa301f4352b120.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

| Runtime | Android Version | Support |

| ------------ | ------------- | ------------ |

| Dalvik |? 2.2 | Not Test|

| Dalvik |2.3 |Yes|

|Dalvik|3.0|No|

|Dalvik|4.0-4.4|Yes|

|ART|5.0|Testing|

|ART|5.1|No|

|ART|M|No|

最近貌似沒有維護(hù)了喻鳄。Github上代碼沒有更新過

使用方法:

compile 'com.taobao.android:dexposed:0.1.7@aar'

代碼示例:

``` java

/**

* doFrame 方法的hook

*/

public static class FrameMethodHook extends XC_MethodHook {

? ? private long startTime = 0;

? ? private int frameNo = -1;

? ? @Override

? ? protected void beforeHookedMethod(MethodHookParam param) throws Throwable {

? ? ? ? startTime = SystemClock.elapsedRealtime();

? ? ? ? frameNo = (Integer) param.args[1];

? ? }

? ? @Override

? ? protected void afterHookedMethod(MethodHookParam param) throws Throwable {

? ? ? ? long coastTime = SystemClock.elapsedRealtime() - startTime;

? ? ? ? Log.i(TAG, "frameNo:" + frameNo + ", coastTime:" + coastTime);

? ? }

}

private XC_MethodHook.Unhook frameMethodunHook = null;

public void hookDoFrame() {

// 找到doFrame方法,插入MethodHook

? ? FrameMethodHook frameMethodHook = new FrameMethodHook();

? ? frameMethodunHook = DexposedBridge.findAndHookMethod(Choreographer.class, "doFrame", long.class, int.class, frameMethodHook);

}

public void unHookDoFrame() {

? ? if (frameMethodunHook != null) {

? ? ? ? frameMethodunHook.unhook();

? ? }

}

```

運(yùn)行后log:

```

03-10 16:43:59.440 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13831, coastTime:153

03-10 16:43:59.520 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13832, coastTime:79

03-10 16:43:59.595 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13846, coastTime:60

03-10 16:44:00.540 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13870, coastTime:38

03-10 16:44:00.580 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13872, coastTime:38

03-10 16:44:00.585 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13875, coastTime:3

03-10 16:44:00.605 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13876, coastTime:4

03-10 16:44:00.620 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13877, coastTime:3

03-10 16:44:00.635 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13878, coastTime:2

03-10 16:44:00.655 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13879, coastTime:2

03-10 16:44:00.670 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13880, coastTime:3

03-10 16:44:00.685 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13881, coastTime:2

03-10 16:44:00.705 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13882, coastTime:3

03-10 16:44:00.720 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13883, coastTime:2

03-10 16:44:00.735 13821-13821/com.baidu.test.aop I/DexposedManager: frameNo:13884, coastTime:2

```

用途:性能監(jiān)控确封,監(jiān)控滑動(dòng)流暢除呵,view繪制時(shí)間等。

aspactJ爪喘,原理颜曾,應(yīng)用場(chǎng)景,demo

spring 提供的AOP框架秉剑,功能強(qiáng)大泛豪。缺點(diǎn),需要集成118KB的jar到APK中侦鹏。

也是編譯時(shí)攔截诡曙。在編譯的時(shí)候,修改class字節(jié)碼略水。重新寫class文件价卤。

用法:

引入classPath

```

classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'

```

增加jar

``` compile 'org.aspectj:aspectjrt:1.8.9' ```

增加gradle 插件

``` apply plugin: 'android-aspectjx' ```

增加完這3句話就可以使用aspectj了

新建一個(gè)AspectTest文件。在類最開始增加@Aspect注解渊涝。編譯器在編譯的時(shí)候荠雕,就會(huì)自動(dòng)去解析,并不需要主動(dòng)去調(diào)用AspectJ類里面的代碼驶赏。

```java

@Aspect

public class AspectTest {

? ? private static final String TAG = "AspectTest";

? ? @Before("execution(* android.app.Activity.on**(..))")

? ? public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {

? ? ? ? String key = joinPoint.getSignature().toString();

? ? ? ? Log.d(TAG, "onActivityMethodBefore: " + key);

? ? }

}

```

這段測(cè)試代碼的意思是,在所有繼承Activity的類中既鞠,執(zhí)行到onXXX方法前煤傍,增加攔截。插入這段代碼嘱蛋。

例如Activity是這樣的蚯姆。

```java

public class AnimationScaleActivity extends FragmentActivity {

? @Override

? protected void onCreate(Bundle savedInstanceState) {

? ? ? super.onCreate(savedInstanceState);

? ? ? setContentView(R.layout.an_scale);

? ? ? ImageView imgv=(ImageView) findViewById(R.id.img);

? ? ? Animation alphaAnimation=AnimationUtils.loadAnimation(this, R.anim.scale);

? ? ? imgv.startAnimation(alphaAnimation);

? ? ? Intent intent = new Intent();

? ? ? ? intent.putExtra("rs", "success");

? ? ? ? setResult(2, intent);

? }

}

```

編譯完后,再反編譯洒敏,是這樣的龄恋。

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/5251070-ed6ff96aeac7f649.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

高亮部分是自動(dòng)生成的代碼。

用途:編譯是切入凶伙。日志記錄郭毕,打點(diǎn)統(tǒng)計(jì)等工作。

## java的動(dòng)態(tài)代理機(jī)制

純java API函荣。主要API類是:

```

Proxy.newProxyInstance

public static Object newProxyInstance(ClassLoader?loader,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Class<?>[]?interfaces,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? InvocationHandler?h)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? throws IllegalArgumentException

返回一個(gè)指定接口的代理類實(shí)例显押,該接口可以將方法調(diào)用指派到指定的調(diào)用處理程序扳肛。此方法相當(dāng)于:

? ? Proxy.getProxyClass(loader, interfaces).

? ? ? ? getConstructor(new Class[] { InvocationHandler.class }).

? ? ? ? newInstance(new Object[] { handler });

```

```

Proxy.newProxyInstance?拋出?IllegalArgumentException,原因與?Proxy.getProxyClass?相同乘碑。

參數(shù):

loader?- 定義代理類的類加載器

interfaces?- 代理類要實(shí)現(xiàn)的接口列表

h?- 指派方法調(diào)用的調(diào)用處理程序

返回:

一個(gè)帶有代理類的指定調(diào)用處理程序的代理實(shí)例挖息,它由指定的類加載器定義,并實(shí)現(xiàn)指定的接口

拋出:

IllegalArgumentException?- 如果違反傳遞到?getProxyClass?的參數(shù)上的任何限制

NullPointerException?- 如果?interfaces?數(shù)組參數(shù)或其任何元素為?null兽肤,或如果調(diào)用處理程序?h?為?null

```

運(yùn)行時(shí)hook套腹。但是只能hook接口。在Android中的用途资铡,可以hook Android Framework層的接口电禀。

詳解見:另寫一遍博文。[JDK中的proxy動(dòng)態(tài)代理原理剖析](http://www.reibang.com/p/e2497db97b50 "proxy")

### javassit原理害驹,應(yīng)用場(chǎng)景鞭呕,demo

Javassist是一個(gè)開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫宛官。

使用方法如下:

引入依賴庫

``` compile 'javassist:javassist:3.12.0.GA' ```

示例:記錄方法的執(zhí)行時(shí)長(zhǎng)葫松。并打印出來。

聲明要注入的代碼:

```

private static String injectStr = "System.out.println(\"Test Javassist Inject \" ); ";

private static String fieldT1Str = "private int t1;";

private static String fieldT2Str = "private int t2;";

private static String injectTimeBefore = "t1 = System.currentTimeMillis();";

private static String injectTimeAfter = "t2 = System.currentTimeMillis();\n" +

? ? ? ? "? ? ? ? long t = t2-t1;\n" +

? ? ? ? "? ? ? ? System.out.println(\"ActivityTime\"+ this.toString()+\", oncreate \" + t);";

```

獲取默認(rèn)的ClassPool

```

ClassPool pool = ClassPool.getDefault()

pool.appendClassPath(path); // path是class文件地址

public static void injectClass(String topPath, File file, String packageName, String methodName) {

? ? ? ? try {

? ? ? ? ? ? String filePath = file.getAbsolutePath();

? ? ? ? ? ? System.out.println("filePath:" + filePath);

? ? ? ? ? ? //確保當(dāng)前文件是class文件底洗,并且不是系統(tǒng)自動(dòng)生成的class文件,且不是內(nèi)部類

? ? ? ? ? ? if (filePath.endsWith(".class")

? ? ? ? ? ? ? ? ? ? && !filePath.contains("R.class")

? ? ? ? ? ? ? ? ? ? && !filePath.contains("BuildConfig.class")

? ? ? ? ? ? ? ? ? ? && !filePath.contains("$")) {

? ? ? ? ? ? ? ? int index = filePath.indexOf(packageName);

? ? ? ? ? ? ? ? boolean isPackageClass = index != -1; // 是這個(gè)包里面的class

? ? ? ? ? ? ? ? if (isPackageClass) {

? ? ? ? ? ? ? ? ? ? int end = filePath.length() - 6;// .class = 6

? ? ? ? ? ? ? ? ? ? String className = filePath.substring(index, end)

? ? ? ? ? ? ? ? ? ? ? ? ? ? .replace('\\', '.').replace('/', '.');

? ? ? ? ? ? ? ? ? ? System.out.println("className:" + className);

? ? ? ? ? ? ? ? ? ? // 開始修改class文件

? ? ? ? ? ? ? ? ? ? CtClass c = pool.getCtClass(className);

? ? ? ? ? ? ? ? ? ? if (c.isFrozen()) {

? ? ? ? ? ? ? ? ? ? ? ? c.defrost();

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? CtMethod ctMethod = c.getDeclaredMethod(methodName);

? ? ? ? ? ? ? ? ? ? if (ctMethod != null) {

? ? ? ? ? ? ? ? ? ? ? ? c.addField(CtField.make(fieldT1Str, c));

? ? ? ? ? ? ? ? ? ? ? ? c.addField(CtField.make(fieldT2Str, c));

? ? ? ? ? ? ? ? ? ? ? ? System.out.println("injectActivity");

? ? ? ? ? ? ? ? ? ? ? ? ctMethod.insertBefore(injectTimeBefore);

? ? ? ? ? ? ? ? ? ? ? ? ctMethod.insertAfter(injectTimeAfter);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? c.writeFile(topPath);

? ? ? ? ? ? ? ? ? ? c.detach();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

```

寫法很簡(jiǎn)單腋么。API也比較易懂。和java 反射的API很像亥揖。

原方法:

```

protected void onCreate(Bundle savedInstanceState) {

? super.onCreate(savedInstanceState);

? setContentView(R.layout.an_scale);

? }

```

反編譯后的方法:

```

protected void onCreate(Bundle savedInstanceState) {

? ? ? ? this.t1 = (int) System.currentTimeMillis();

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? this.t2 = (int) System.currentTimeMillis();

? ? ? ? System.out.println(new StringBuffer().append("ActivityTime").append(toString()).append(", oncreate ").append((long) (this.t2 - this.t1)).toString());

}

```

優(yōu)點(diǎn):使用簡(jiǎn)單珊擂。APK中不需要引入第三方j(luò)ar。引入的jar是在編譯時(shí)费变,gradle插件使用的摧扇。

### ASM原理,應(yīng)用場(chǎng)景

ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具挚歧。無需提供源代碼即可對(duì)應(yīng)用嵌入所需debug代碼扛稽,用于應(yīng)用API性能分析。ASM可以直接產(chǎn)生二進(jìn)制class文件滑负,也可以在類被加入JVM之前動(dòng)態(tài)修改類行為在张。

26個(gè)class文件,通過Javassist, ASM進(jìn)行AOP處理的執(zhí)行時(shí)長(zhǎng)

| AOP方式 | 時(shí)長(zhǎng) |

| --------- | ------ |

| Javassist | 712ms? |

| ASM |120ms? |

所以ASM在執(zhí)行時(shí)間矮慕,性能上占有帮匾。在編譯時(shí)修改class文件,我也推薦使用ASM方式痴鳄。

詳解見:[Android 中使用ASM瘟斜,對(duì)Activity生命周期打點(diǎn)統(tǒng)計(jì)](http://www.reibang.com/p/0b47f88cfcec "ASM")

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哼转,更是在濱河造成了極大的恐慌明未,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壹蔓,死亡現(xiàn)場(chǎng)離奇詭異趟妥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)佣蓉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門披摄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勇凭,你說我怎么就攤上這事疚膊。” “怎么了虾标?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵寓盗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我璧函,道長(zhǎng)傀蚌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任蘸吓,我火速辦了婚禮善炫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘库继。我一直安慰自己箩艺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布宪萄。 她就那樣靜靜地躺著艺谆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拜英。 梳的紋絲不亂的頭發(fā)上静汤,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音聊记,去河邊找鬼。 笑死恢暖,一個(gè)胖子當(dāng)著我的面吹牛排监,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杰捂,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼舆床,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挨队,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤谷暮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盛垦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湿弦,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年腾夯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颊埃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝶俱,死狀恐怖班利,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榨呆,我是刑警寧澤罗标,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站积蜻,受9級(jí)特大地震影響闯割,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浅侨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一纽谒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧如输,春花似錦、人聲如沸不见。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稳吮。三九已至灶似,卻和暖如春列林,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砌创。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宰缤。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓撵溃,卻偏偏與公主長(zhǎng)得像缘挑,于是被迫代替她去往敵國和親语淘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355