AOP簡介
1.1 什么是AOP
AOP,Aspect Oriented Programming 面向切面編程
OOP贯钩,Object-oriented programming面向?qū)ο缶幊?br>
AOP和OOP是不同的編程思想非迹。OOP強調(diào)的是高內(nèi)聚声畏,低耦合激率,封裝。
提倡的是將功能模塊化季蚂,對象化茫船。
可以通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術(shù)。AOP實際是GoF設(shè)計模式的延續(xù)癣蟋,設(shè)計模式孜孜不倦追求的是調(diào)用者和被調(diào)用者之間的解耦,提高代碼的靈活性和可擴展性透硝,AOP可以說也是這種目標(biāo)的一種實現(xiàn)。
1.2 AOP用途
將日志記錄疯搅,性能統(tǒng)計濒生,安全控制,事務(wù)處理幔欧,異常處理等代碼從業(yè)務(wù)邏輯代碼中劃分出來罪治,通過對這些行為的分離,我們希望可以將它們獨立到非指導(dǎo)業(yè)務(wù)邏輯的方法中礁蔗,進而改變這些行為的時候不影響業(yè)務(wù)邏輯的代碼觉义。
1.3 AOP方式對比選擇
First Header | Hook時機 | Android中應(yīng)用場景 | 優(yōu)點 | 缺點 |
---|---|---|---|---|
Dexposed | 運行時動態(tài)hook | 滑動流暢度監(jiān)控,事件執(zhí)行監(jiān)控浴井,熱修復(fù) | 可以動態(tài)監(jiān)控和系統(tǒng)通信的各種方法晒骇。 | 不支持5.0以上手機 |
Xposed | 運行時動態(tài)hook | 同Dexposed | 可以動態(tài)監(jiān)控和系統(tǒng)通信的各種方法。 | 不支持5.0以上手機磺浙,必須root |
Java Proxy | 運行時動態(tài)hook | hook和系統(tǒng)通信接口例如:插件sdk | Java 原生API洪囤,沒有兼容性問題 | 只能hook 有Interface的類 |
AspactJ | 編譯時修改代碼 | 統(tǒng)計方法執(zhí)行時長,方法前后注入邏輯 | Spring開源的AOP框架撕氧,功能強大瘤缩。注解很多÷啄啵基本包括所有的編譯時注入方式 | 需要引入118K的jar |
ASM | 編譯時修改代碼 | 同AspactJ | 字節(jié)碼操作庫 | 需要自己寫注解和編譯腳本剥啤。字節(jié)碼插入編寫比較費勁 |
Javassit | 編譯時修改代碼 | 同AspactJ | 基于java反射的字節(jié)碼操作類庫锦溪。對比ASM,編寫簡單 | 對比ASM府怯,修改類時刻诊,執(zhí)行時間長 |
Dexposed,Xposed: 原理富腊,應(yī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來把對java函數(shù)的調(diào)用轉(zhuǎn)到native層,在native層用dvm的各種函數(shù)來操作Method的指針和對象來控制函數(shù)流程肖揣。
Git地址: https://github.com/alibaba/dexposed
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 |
最近貌似沒有維護了民假。Github上代碼沒有更新過
使用方法:
compile 'com.taobao.android:dexposed:0.1.7@aar'
代碼示例:
/**
* 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();
}
}
運行后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)控滑動流暢羊异,view繪制時間等。
aspactJ彤断,原理野舶,應(yīng)用場景,demo
spring 提供的AOP框架宰衙,功能強大平道。缺點,需要集成118KB的jar到APK中供炼。
也是編譯時攔截一屋。在編譯的時候,修改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了
新建一個AspectTest文件。在類最開始增加@Aspect注解涛贯。編譯器在編譯的時候诽嘉,就會自動去解析,并不需要主動去調(diào)用AspectJ類里面的代碼弟翘。
@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);
}
}
這段測試代碼的意思是虫腋,在所有繼承Activity的類中,執(zhí)行到onXXX方法前衅胀,增加攔截岔乔。插入這段代碼。
例如Activity是這樣的滚躯。
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);
}
}
編譯完后雏门,再反編譯嘿歌,是這樣的。
高亮部分是自動生成的代碼茁影。
用途:編譯是切入宙帝。日志記錄,打點統(tǒng)計等工作募闲。
java的動態(tài)代理機制
純java API步脓。主要API類是:
Proxy.newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
返回一個指定接口的代理類實例,該接口可以將方法調(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 - 代理類要實現(xiàn)的接口列表
h - 指派方法調(diào)用的調(diào)用處理程序
返回:
一個帶有代理類的指定調(diào)用處理程序的代理實例要出,它由指定的類加載器定義鸳君,并實現(xiàn)指定的接口
拋出:
IllegalArgumentException - 如果違反傳遞到 getProxyClass 的參數(shù)上的任何限制
NullPointerException - 如果 interfaces 數(shù)組參數(shù)或其任何元素為 null,或如果調(diào)用處理程序 h 為 null
運行時hook患蹂。但是只能hook接口或颊。在Android中的用途,可以hook Android Framework層的接口传于。
詳解見:另寫一遍博文囱挑。JDK中的proxy動態(tài)代理原理剖析
javassit原理,應(yīng)用場景沼溜,demo
Javassist是一個開源的分析平挑、編輯和創(chuàng)建Java字節(jié)碼的類庫。
使用方法如下:
引入依賴庫
compile 'javassist:javassist:3.12.0.GA'
示例:記錄方法的執(zhí)行時長盛末。并打印出來弹惦。
聲明要注入的代碼:
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)自動生成的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; // 是這個包里面的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();
}
}
寫法很簡單悄但。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)點:使用簡單檐嚣。APK中不需要引入第三方j(luò)ar助泽。引入的jar是在編譯時,gradle插件使用的嚎京。
ASM原理嗡贺,應(yīng)用場景
ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具。無需提供源代碼即可對應(yīng)用嵌入所需debug代碼鞍帝,用于應(yīng)用API性能分析诫睬。ASM可以直接產(chǎn)生二進制class文件,也可以在類被加入JVM之前動態(tài)修改類行為帕涌。
26個class文件摄凡,通過Javassist, ASM進行AOP處理的執(zhí)行時長
AOP方式 | 時長 |
---|---|
Javassist | 712ms |
ASM | 120ms |
所以ASM在執(zhí)行時間续徽,性能上占有。在編譯時修改class文件亲澡,我也推薦使用ASM方式钦扭。