Android AOP 總結(jié)

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)。

Paste_Image.png

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

Paste_Image.png
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);
   }
}

編譯完后雏门,再反編譯嘿歌,是這樣的。

Paste_Image.png

高亮部分是自動生成的代碼茁影。
用途:編譯是切入宙帝。日志記錄,打點統(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方式钦扭。

詳解見。Android 中使用ASM床绪,對Activity生命周期打點統(tǒng)計

以上所有代碼鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末客情,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子癞己,更是在濱河造成了極大的恐慌膀斋,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件末秃,死亡現(xiàn)場離奇詭異概页,居然都是意外死亡籽御,警方通過查閱死者的電腦和手機练慕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來技掏,“玉大人铃将,你說我怎么就攤上這事⊙剖幔” “怎么了劲阎?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸠真。 經(jīng)常有香客問我悯仙,道長,這世上最難降的妖魔是什么吠卷? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任锡垄,我火速辦了婚禮,結(jié)果婚禮上祭隔,老公的妹妹穿的比我還像新娘货岭。我一直安慰自己,他們只是感情好疾渴,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布千贯。 她就那樣靜靜地躺著,像睡著了一般搞坝。 火紅的嫁衣襯著肌膚如雪搔谴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天桩撮,我揣著相機與錄音敦第,去河邊找鬼慌核。 笑死,一個胖子當(dāng)著我的面吹牛申尼,可吹牛的內(nèi)容都是我干的垮卓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼师幕,長吁一口氣:“原來是場噩夢啊……” “哼粟按!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霹粥,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤灭将,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后后控,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庙曙,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年浩淘,在試婚紗的時候發(fā)現(xiàn)自己被綠了捌朴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡张抄,死狀恐怖砂蔽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情署惯,我是刑警寧澤左驾,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站极谊,受9級特大地震影響诡右,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轻猖,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一帆吻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜕依,春花似錦桅锄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐束,卻和暖如春辫秧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背被丧。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工盟戏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绪妹,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓柿究,卻偏偏與公主長得像邮旷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝇摸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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