Android 插件化基礎知識

[TOC]

一 .概述

插件化技術聽起來高深莫測无埃,實際上要解決的就是兩個問題:

  1. 代碼加載
  2. 資源加載

代碼加載

類的加載可以使用Java的ClassLoader機制钝诚,但是對于Android來說隘截,并不是說類加載進來就可以用了铲咨,很多組件都是有“生命”的固歪;因此對于這些有血有肉的類蒜鸡,必須給它們注入活力,也就是所謂的組件生命周期管理牢裳;

另外逢防,如何管理加載進來的類也是一個問題。假設多個插件依賴了相同的類蒲讯,是抽取公共依賴進行管理還是插件單獨依賴忘朝?這就是ClassLoader的管理問題

資源加載

資源加載方案大家使用的原理都差不多判帮,都是用AssetManager的隱藏方法addAssetPath局嘁;但是,不同插件的資源如何管理脊另?是公用一套資源還是插件獨立資源导狡?共用資源如何避免資源沖突?對于資源加載偎痛,有的方案共用一套資源并采用資源分段機制解決沖突(要么修改aapt要么添加編譯插件)旱捧;有的方案選擇獨立資源,不同插件管理自己的資源踩麦。

目前國內(nèi)開源的較成熟的插件方案有DLDroidPlugin枚赡;但是DL方案僅僅對Frameworl的表層做了處理,嚴重依賴that語法谓谦,編寫插件代碼和主程序代碼需單獨區(qū)分贫橙;而DroidPlugin通過Hook增強了Framework層的很多系統(tǒng)服務,開發(fā)插件就跟開發(fā)獨立app差不多反粥;就拿Activity生命周期的管理來說卢肃,DL的代理方式就像是牽線木偶疲迂,插件只不過是操縱傀儡而已;而DroidPlugin則是借尸還魂莫湘,插件是有血有肉的系統(tǒng)管理的真正組件尤蒿;DroidPlugin Hook了系統(tǒng)幾乎所有的Sevice,欺騙了大部分的系統(tǒng)API幅垮;掌握這個Hook過程需要掌握很多系統(tǒng)原理腰池,因此學習DroidPlugin對于整個Android FrameWork層大有裨益。

二. Android 類加載器

二.反射基礎知識

反射機制的概念就不描述了忙芒,看了只會更暈示弓,直接看反射機制能干嘛:

反射機制的作用:

1,反編譯:.class-->.java

2,通過反射機制訪問java對象的屬性,方法呵萨,構造方法等奏属;

反射機制中的類:

java.lang.reflect.Constructor; 

java.lang.reflect.Field;        

java.lang.reflect.Method;

java.lang.reflect.Modifier;

? 很多反射中的方法,屬性等操作我們可以從這四個類中查詢甘桑。

具體實現(xiàn):

? 1拍皮,反射機制獲取類有三種方法,我們來獲取Student類型

//第一種方式:  
Classc1 = Class.forName("Student");  

//第二種方式:  
//java中每個類型都有class 屬性.  
Classc2 = Student.class;  
 
//第三種方式:  
//java語言中任何一個java對象都有getClass 方法  
Employeee = new Student();  
Classc3 = e.getClass(); //c3是運行時類 (e的運行時類是Student)  

? 2跑杭,創(chuàng)建對象:獲取類以后我們來創(chuàng)建它的對象,利用newInstance:

Class c =Class.forName("Student");  
  
//創(chuàng)建此Class 對象所表示的類的一個新實例  
Objecto = c.newInstance(); //調(diào)用了Student的無參數(shù)構造方法.  

3,獲取屬性咆耿,這里我們先只看獲取屬性德谅,方法是同樣的道理。獲取屬性也分為分為所有的屬性和指定的屬性:

  • 先看獲取所有的屬性的寫法
//獲取整個類
Class c = Class.forName("java.lang.Integer");
//獲取所有的屬性?
Field[] fs = c.getDeclaredFields();

//定義可變長的字符串萨螺,用來存儲屬性
StringBuffer sb = new StringBuffer();
//通過追加的方法窄做,將每個屬性拼接到此字符串中
//最外邊的public定義
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//里邊的每一個屬性
for(Field field:fs){
    sb.append("\t");//空格
    sb.append(Modifier.toString(field.getModifiers())+" ");//獲得屬性的修飾符,例如public慰技,static等等
    sb.append(field.getType().getSimpleName() + " ");//屬性的類型的名字
    sb.append(field.getName()+";\n");//屬性的名字+回車
}

sb.append("}");
System.out.println(sb);

? 這里可以通過getDeclaredFields拿到所有的屬性數(shù)組椭盏,然后對于每一個屬性,可以通過

getModifiers 獲得屬性的修飾符吻商,例如public掏颊,static等等

getSimpleName 獲得屬性的類型名稱

getName 獲得屬性的名稱

field.get(Object object) 獲得相應Field的值

  • 指定的屬性
public static void main(String[] args) throws Exception{        
    //獲取類
    Class c = Class.forName("User");
    //獲取id屬性
    Field idF = c.getDeclaredField("id");
    //實例化這個類賦給o
    Object o = c.newInstance();
    //打破封裝
    idF.setAccessible(true); //使用反射機制可以打破封裝性,導致了java對象的屬性不安全艾帐。設置為true就可以訪問private修飾的東西乌叶,否則無法訪問
    //給o對象的id屬性賦值"110"
    idF.set(o, "110"); //set
    //get
    System.out.println(idF.get(o));
}

通過getDeclaredField 來拿到指定的屬性

4.獲取方法,和構造方法 同理

獲取類的無參構造函數(shù)柒爸,并實例化類

    Class clazz = Class.forName("com.yano.reflect.Person");
    Constructor c = clazz.getConstructor(null);
    Person p = (Person) c.newInstance(null);

獲取類的含參私有構造函數(shù)准浴,并實例化類

    Class clazz = Class.forName("com.yano.reflect.Person");
    Constructor c = clazz
            .getDeclaredConstructor(new Class[] { String.class });
    // 由于構造函數(shù)是 private 的,所以需要屏蔽Java語言的訪問檢查
    c.setAccessible(true);
    Person p = (Person) c
            .newInstance(new Object[] { "I'm a reflect name!" });

獲取并調(diào)用類的無參方法

    Class clazz = Class.forName("com.yano.reflect.Person");
    Constructor c = clazz.getConstructor(null);
    Person p = (Person) c.newInstance(null);
    Method method = clazz.getMethod("fun", null);
    method.invoke(p, null);

獲取并調(diào)用類的含參方法

    Class clazz = Class.forName("com.yano.reflect.Person");
    Constructor c = clazz.getConstructor(null);
    Person p = (Person) c.newInstance(null);
    Method method = clazz.getMethod("fun", new Class[] { String.class });
    method.invoke(p, new Object[] { "I'm a reflect method!" });

二. Android插件化原理解析——Hook機制之動態(tài)代理

代理是什么

為什么需要代理呢捎稚?其實這個代理與日常生活中的“代理”乐横,“中介”差不多求橄;比如你想海淘買東西,總不可能親自飛到國外去購物吧葡公,這時候我們使用第三方海淘服務比如惠惠購物助手等谈撒;同樣拿購物為例,有時候第三方購物會有折扣比如當初的米折網(wǎng)匾南,這時候我們可以少花點錢啃匿;當然有時候這個“代理”比較坑,坑我們的錢蛆楞,坑我們的貨溯乒。

從這個例子可以看出來,代理可以實現(xiàn)方法增強豹爹,比如常用的日志,緩存等裆悄;也可以實現(xiàn)方法攔截,通過代理方法修改原方法的參數(shù)和返回值臂聋,從而實現(xiàn)某種不可告人的目的~接下來我們用代碼解釋一下光稼。

靜態(tài)代理

靜態(tài)代理,是最原始的代理方式孩等;假設我們有一個購物的接口艾君,如下:

public interface Shopping {
    Object[] doShopping(long money);
}

它有一個原始的實現(xiàn),我們可以理解為親自肄方,直接去商店購物:

public class ShoppingImpl implements Shopping {
    @Override
    public Object[] doShopping(long money) {
        System.out.println("逛淘寶 ,逛商場,買買買!!");
        System.out.println(String.format("花了%s塊錢", money));
        return new Object[] { "鞋子", "衣服", "零食" };
    }
}

好了冰垄,現(xiàn)在我們自己沒時間但是需要買東西,于是我們就找了個代理幫我們買:

public class ProxyShopping implements Shopping {

    Shopping base;

    ProxyShopping(Shopping base) {
        this.base = base;
    }

    @Override
    public Object[] doShopping(long money) {

        // 先黑點錢(修改輸入?yún)?shù))
        long readCost = (long) (money * 0.5);

        System.out.println(String.format("花了%s塊錢", readCost));

        // 幫忙買東西
        Object[] things = base.doShopping(readCost);

        // 偷梁換柱(修改返回值)
        if (things != null && things.length > 1) {
            things[0] = "被掉包的東西!!";
        }

        return things;
    }
public class TestStatic {
    public static void main(String[] args) {

        // 原始的廠家
        Shopping women = new ShoppingImpl();

        System.out.println(Arrays.toString(women.doShopping(100)));

        // 換成代購
        women = new ProxyShopping(women);

        System.out.println(Arrays.toString(women.doShopping(100)));
    }
}

很不幸权她,我們找的這個代理有點坑虹茶,坑了我們的錢還坑了我們的貨;先忍忍隅要。

從以上代碼中我們可以了解到蝴罪,通過靜態(tài)代理實現(xiàn)我們的需求需要我們在每個方法中都添加相應的邏輯,這里只存在兩個方法所以工作量還不算大步清,假如Sell接口中包含上百個方法呢?這時候使用靜態(tài)代理就會編寫許多冗余代碼要门。通過使用動態(tài)代理,我們可以做一個“統(tǒng)一指示”尼啡,從而對所有代理類的方法進行統(tǒng)一處理暂衡,而不用逐一修改每個方法。下面我們來具體介紹下如何使用動態(tài)代理方式實現(xiàn)我們的需求崖瞭。

動態(tài)代理

傳統(tǒng)的靜態(tài)代理模式需要為每一個需要代理的類寫一個代理類狂巢,如果需要代理的類有幾百個那不是要累死?為了更優(yōu)雅地實現(xiàn)代理模式书聚,JDK提供了動態(tài)代理方式,可以簡單理解為JVM可以在運行時幫我們動態(tài)生成一系列的代理類,這樣我們就不需要手寫每一個靜態(tài)的代理類了荠藤。依然以購物為例,用動態(tài)代理實現(xiàn)如下:

public class ShoppingHandler implements InvocationHandler {

    /**
     * 被代理的原始對象
     */
    Object base;

    public ShoppingHandler(Object base) {
        this.base = base;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if ("doShopping".equals(method.getName())) {
            // 這里是代理Shopping接口的對象

            // 先黑點錢(修改輸入?yún)?shù))
            Long money = (Long) args[0];
            long readCost = (long) (money * 0.5);

            System.out.println(String.format("花了%s塊錢", readCost));

            // 幫忙買東西
            Object[] things = (Object[]) method.invoke(base, readCost);

            // 偷梁換柱(修改返回值)
            if (things != null && things.length > 1) {
                things[0] = "被掉包的東西!!";
            }

            return things;
        }

        if ("doSomething".equals(method.getName())) {
            // 可以代理別的,做些別的事情
            return null;
        }

        if ("doSomethingElse".equals(method.getName())) {
            // 做些別的事情
            return null;
        }

        return null;
    }
}
public static void main(String[] args) {
    Shopping women = new ShoppingImpl();
    // 正常購物
    System.out.println(Arrays.toString(women.doShopping(100)));
    // 招代理
    women = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(),
            women.getClass().getInterfaces(), new ShoppingHandler(women));

    System.out.println(Arrays.toString(women.doShopping(100)));
}

動態(tài)代理主要處理InvocationHandlerProxy類胯杭;完整代碼可以見github

上面只是個demo,如果沒接觸過動態(tài)代理受啥,肯定會一頭霧水做个,大神們這里也是一筆帶過,我們就來分析分析什么是動態(tài)代理

動態(tài)代理的類和接口

1滚局,Java.lang.reflect.Proxy:動態(tài)代理機制的主類居暖,提供一組靜態(tài)方法為一組接口動態(tài)的生成對象和代理類。

// 方法 1: 該方法用于獲取指定代理對象所關聯(lián)的調(diào)用處理器
public static InvocationHandler getInvocationHandler(Object proxy) 

// 方法 2:該方法用于獲取關聯(lián)于指定類裝載器和一組接口的動態(tài)代理類的類對象
public static Class<?> getProxyClass(ClassLoader loader, 
Class<?>... interfaces)

// 方法 3:該方法用于判斷指定類對象是否是一個動態(tài)代理類
public static boolean isProxyClass(Class<?> cl) 

// 方法 4:該方法用于為指定類裝載器藤肢、一組接口及調(diào)用處理器生成動態(tài)代理類實例
public static Object newProxyInstance(ClassLoader loader,
 Class<?>[] interfaces,InvocationHandler h)

2太闺,java.lang.reflect.InvocationHandler:調(diào)用處理器接口,自定義invokle方法嘁圈,用于實現(xiàn)對于真正委托類的代理訪問省骂。在使用動態(tài)代理時,我們需要定義一個位于代理類與委托類之間的中介類最住,這個中介類被要求實現(xiàn)InvocationHandler接口钞澳,這個接口的定義如下:

/**
 該方法負責集中處理動態(tài)代理類上的所有方法調(diào)用。
 第一個參數(shù)既是代理類實例温学,
 第二個參數(shù)是被調(diào)用的方法對象
 第三個方法是調(diào)用參數(shù)略贮。
 調(diào)用處理器根據(jù)這三個參數(shù)進行預處理或分派到委托類實例上發(fā)射執(zhí)行
*/
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

可以理解為我們真正實現(xiàn)坑錢偷貨的地方,這里雖然我們偷雞摸狗了仗岖,但是雇主委托我們該做的事情還是要做的,做生意要誠信览妖,這里用到了反射的方法去調(diào)用它原來的真正實現(xiàn)轧拄。即需要實現(xiàn)以下接口:

3,java.lang.ClassLoader:類裝載器類讽膏,將類的字節(jié)碼裝載到 Java 虛擬機(JVM)中并為其定義類對象檩电,然后該類才能被使用。Proxy類與普通類的唯一區(qū)別就是其字節(jié)碼是由 JVM 在運行時動態(tài)生成的而非預存在于任何一個 .class 文件中**府树。

動態(tài)代理機制

java動態(tài)代理創(chuàng)建對象的過程為如下步驟:

1.通過實現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器俐末;

// InvocationHandlerImpl 實現(xiàn)了 InvocationHandler 接口,并能實現(xiàn)方法調(diào)用從代理類到委托類的分派轉發(fā)
// 其內(nèi)部通常包含指向委托類實例的引用奄侠,用于真正執(zhí)行分派轉發(fā)過來的方法調(diào)用
InvocationHandler handler = new InvocationHandlerImpl(..); 

2卓箫,通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創(chuàng)建動態(tài)代理類;

// 通過 Proxy 為包括 Interface 接口在內(nèi)的一組接口動態(tài)創(chuàng)建代理類的類對象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

3垄潮,通過反射機制獲得動態(tài)代理類的構造函數(shù)烹卒,其唯一參數(shù)類型是調(diào)用處理器接口類型闷盔;

// 通過反射從生成的類對象獲得構造函數(shù)對象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

4,通過構造函數(shù)創(chuàng)建動態(tài)代理類實例旅急,構造時調(diào)用處理器對象作為參數(shù)被傳入逢勾。

// 通過構造函數(shù)對象創(chuàng)建動態(tài)代理類實例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

好復雜啊,都是什么鬼藐吮,簡單真誠點溺拱,為了簡化對象創(chuàng)建過程,Proxy類中的newProxyInstance方法封裝了2~4谣辞,只需兩步即可完成代理對象的創(chuàng)建迫摔。

// InvocationHandlerImpl 實現(xiàn)了 InvocationHandler 接口,并能實現(xiàn)方法調(diào)用從代理類到委托類的分派轉發(fā)潦闲,也就是真正的委托實現(xiàn)
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通過 Proxy 直接創(chuàng)建動態(tài)代理類實例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

看下這個方法的三個參數(shù)的意義:

復制代碼
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一個ClassLoader對象攒菠,定義了由哪個ClassLoader對象來對生成的代理對象進行加載

interfaces:  一個Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口歉闰,如果我提供了一組接口給它辖众,那么這個代理對象就宣稱實現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了

h:  一個InvocationHandler對象和敬,表示的是當我這個動態(tài)代理對象在調(diào)用方法的時候凹炸,會關聯(lián)到哪一個InvocationHandler對象上

終于在這里我們最后拿到的proxy就是我們想要的代理。完成了委托->中介->代理

ok昼弟,到這里我們梳理一下如何使用動態(tài)代理

1.聲明一個接口啤它,用作具體的委托事務聲明

2.聲明一個委托類,集成委托接口舱痘,實現(xiàn)委托的具體實現(xiàn)变骡,如:上面的購物,我們實現(xiàn)我們原本要購買的東西芭逝,這個時候因為委托者自己買的塌碌,當然不會坑蒙拐騙自己。

3.實現(xiàn)一個中介類旬盯,這個中介類實現(xiàn)InvocationHandler台妆,在invoke方法中中介在實現(xiàn)客戶需求基礎上,進行了坑蒙拐騙胖翰,挖了一點油水接剩。

4.調(diào)用Proxy.newProxyInstance來生成一個委托實例,這個時候我們就可以拿著這個實例去做想做的事情了萨咳。

再總結下:

? 1.在中介類中懊缺,我們持有了一個委托類,然后再invoke方法中調(diào)用了他的相應方法某弦,這跟我們的靜態(tài)代理差不多桐汤,因此可以理解為:中介類與委托類構成了靜態(tài)代理關系而克。在這個關系中,中介類是代理類怔毛,委托類就是委托類;

  1. 代理類與中介類也構成一個靜態(tài)代理關系员萍,在這個關系中,中介類是委托類拣度,代理類是代理類碎绎。也就是說,動態(tài)代理關系由兩組靜態(tài)代理關系組成抗果,這就是動態(tài)代理的原理筋帖。

代理Hook

我們知道代理有比原始對象更強大的能力,比如飛到國外買東西冤馏,比如坑錢坑貨日麸;那么很自然,如果我們自己創(chuàng)建代理對象逮光,然后把原始對象替換為我們的代理對象代箭,那么就可以在這個代理對象為所欲為了;修改參數(shù)涕刚,替換返回值嗡综,我們稱之為Hook。

下面我們Hook掉startActivity這個方法杜漠,使得每次調(diào)用這個方法之前輸出一條日志极景;(當然,這個輸入日志有點點弱驾茴,只是為了展示原理盼樟;只要你想,你想可以替換參數(shù)锈至,攔截這個startActivity過程恤批,使得調(diào)用它導致啟動某個別的Activity,指鹿為馬9啊)

這里的hook我們用到了靜態(tài)代理,松口氣诀浪,動態(tài)代理好復雜棋返。。雷猪。

首先我們得找到被Hook的對象睛竣,我稱之為Hook點;什么樣的對象比較好Hook呢求摇?自然是容易找到的對象射沟。什么樣的對象容易找到殊者?靜態(tài)變量和單例;在一個進程之內(nèi)验夯,靜態(tài)變量和單例變量是相對不容易發(fā)生變化的猖吴,因此非常容易定位,而普通的對象則要么無法標志挥转,要么容易改變海蔽。我們根據(jù)這個原則找到所謂的Hook點。

然后我們分析一下startActivity的調(diào)用鏈绑谣,找出合適的Hook點党窜。我們知道對于Context.startActivity(Activity.startActivity的調(diào)用鏈與之不同),由于Context的實現(xiàn)實際上是ContextImpl;我們看ConetxtImpl類的startActivity方法:

@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
        getOuterContext(), mMainThread.getApplicationThread(), null,
        (Activity)null, intent, -1, options);
}

這里借宵,實際上使用了ActivityThread類的mInstrumentation成員的execStartActivity方法幌衣;注意到,ActivityThread 實際上是主線程壤玫,而主線程一個進程只有一個豁护,因此這里是一個良好的Hook點。

接下來就是想要Hook掉我們的主線程對象垦细,也就是把這個主線程對象里面的mInstrumentation給替換成我們修改過的代理對象择镇;要替換主線程對象里面的字段,首先我們得拿到主線程對象的引用括改,如何獲取呢腻豌?ActivityThread類里面有一個靜態(tài)方法currentActivityThread可以幫助我們拿到這個對象類;但是ActivityThread是一個隱藏類嘱能,我們需要用反射去獲取吝梅,代碼如下:

// 先獲取到當前的ActivityThread對象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

拿到這個currentActivityThread之后,我們需要修改它的mInstrumentation這個字段為我們的代理對象惹骂,我們先實現(xiàn)這個代理對象苏携,由于JDK動態(tài)代理只支持接口,而這個Instrumentation是一個類对粪,沒辦法右冻,我們只有手動寫靜態(tài)代理類,覆蓋掉原始的方法即可著拭。(cglib可以做到基于類的動態(tài)代理纱扭,這里先不介紹)

public class EvilInstrumentation extends Instrumentation {

    private static final String TAG = "EvilInstrumentation";

    // ActivityThread中原始的對象, 保存起來
    Instrumentation mBase;

    public EvilInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        // Hook之前, XXX到此一游!
        Log.d(TAG, "\n執(zhí)行了startActivity, 參數(shù)如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");

        // 開始調(diào)用原始的方法, 調(diào)不調(diào)用隨你,但是不調(diào)用的話, 所有的startActivity都失效了.
        // 由于這個方法是隱藏的,因此需要使用反射調(diào)用;首先找到這個方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, 
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who, 
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某該死的rom修改了  需要手動適配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }
}

Ok,有了代理對象儡遮,我們要做的就是偷梁換柱乳蛾!代碼比較簡單,采用反射直接修改:

public static void attachContext() throws Exception{
    // 先獲取到當前的ActivityThread對象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);

    // 拿到原始的 mInstrumentation字段
    Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
    mInstrumentationField.setAccessible(true);
    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

    // 創(chuàng)建代理對象
    Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);

    // 偷梁換柱
    mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}

好了,我們啟動一個Activity測試一下肃叶,結果如下:

img

可見蹂随,Hook確實成功了!這就是使用代理進行Hook的原理——偷梁換柱因惭。整個Hook過程簡要總結如下:

  1. 尋找Hook點岳锁,原則是靜態(tài)變量或者單例對象,盡量Hook pulic的對象和方法筛欢,非public不保證每個版本都一樣浸锨,需要適配。
  2. 選擇合適的代理方式版姑,如果是接口可以用動態(tài)代理柱搜;如果是類可以手動寫代理也可以使用cglib。
  3. 偷梁換柱——用代理對象替換原始對象

完整代碼參照:understand-plugin-framework剥险;里面留有一個作業(yè):我們目前僅Hook了Context類的startActivity方法聪蘸,但是Activity類卻使用了自己的mInstrumentation;你可以嘗試Hook掉Activity類的startActivity方法表制。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末健爬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子么介,更是在濱河造成了極大的恐慌娜遵,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壤短,死亡現(xiàn)場離奇詭異设拟,居然都是意外死亡,警方通過查閱死者的電腦和手機久脯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門纳胧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帘撰,你說我怎么就攤上這事跑慕。” “怎么了摧找?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵核行,是天一觀的道長。 經(jīng)常有香客問我蹬耘,道長钮科,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任婆赠,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘休里。我一直安慰自己蛆挫,他們只是感情好,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布妙黍。 她就那樣靜靜地躺著悴侵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拭嫁。 梳的紋絲不亂的頭發(fā)上可免,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機與錄音做粤,去河邊找鬼浇借。 笑死,一個胖子當著我的面吹牛怕品,可吹牛的內(nèi)容都是我干的妇垢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼肉康,長吁一口氣:“原來是場噩夢啊……” “哼闯估!你這毒婦竟也來了?” 一聲冷哼從身側響起吼和,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涨薪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后炫乓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刚夺,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年厢岂,在試婚紗的時候發(fā)現(xiàn)自己被綠了光督。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡塔粒,死狀恐怖结借,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卒茬,我是刑警寧澤船老,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站圃酵,受9級特大地震影響柳畔,放射性物質發(fā)生泄漏。R本人自食惡果不足惜郭赐,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一薪韩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦俘陷、人聲如沸罗捎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桨菜。三九已至,卻和暖如春捉偏,著一層夾襖步出監(jiān)牢的瞬間倒得,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工夭禽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霞掺,地道東北人。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓驻粟,卻偏偏與公主長得像根悼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蜀撑,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

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