## 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")