Android進(jìn)階解密④—插件化原理

在學(xué)習(xí)插件化之前需要看前面幾篇文章:

動(dòng)態(tài)加載技術(shù):

在程序運(yùn)行時(shí),動(dòng)態(tài)加載一些程序中原本不存在的可執(zhí)行文件并運(yùn)行起來(lái)佛嬉,疾棵,隨著應(yīng)用技術(shù)的發(fā)展,動(dòng)態(tài)加載技術(shù)逐漸派生出兩個(gè)分支砰琢,熱修復(fù)和插件化;

  • 熱修復(fù):用于修復(fù)bug
  • 插件化:解決應(yīng)用龐大,功能模塊解耦,復(fù)用其他apk的代碼
插件化思想:

將復(fù)用的apk作為插件佩厚,插入另一個(gè)apk中,比如淘寶中會(huì)有咸魚(yú)的頁(yè)面说订,用淘寶為咸魚(yú)引流抄瓦,使用插件化技術(shù),可以直接使用咸魚(yú)apk中的dex文件陶冷,這樣省去再次開(kāi)發(fā)一套咸魚(yú)頁(yè)面的成本钙姊,并且有效的降低了淘寶apk的耦合度;

Activity插件化原理:

插件化activity的目的是直接使用另一個(gè)apk的activity埂伦,而activity的啟動(dòng)和生命周期的管理需要經(jīng)過(guò)AMS的處理煞额,另一個(gè)apk的activity沒(méi)有在本項(xiàng)目的manifest注冊(cè),肯定是無(wú)法通過(guò)的,所以我們需要hook startActivity的流程立镶,繞過(guò)ams的驗(yàn)證,可以在本項(xiàng)目使用一個(gè)占坑activity类早,在發(fā)送給ams前將插件activity換成占坑activity去通過(guò)ams的驗(yàn)證媚媒,驗(yàn)證好以后在真實(shí)的啟動(dòng)時(shí)再將插件activity換回來(lái);

步驟:
  • 事先在本項(xiàng)目準(zhǔn)備好占坑activity
  • 使用占坑activity繞過(guò)ams驗(yàn)證
  • 還原插件activity

1. 準(zhǔn)備占坑activity

直接在原項(xiàng)目準(zhǔn)備一個(gè)空白的activity即可涩僻,記得必須在manifest注冊(cè),下文叫他SubActivity

2. 使用插件activity替換占坑activity

在交給ams進(jìn)程驗(yàn)證之前缭召,在用戶進(jìn)程會(huì)經(jīng)過(guò)兩個(gè)類(lèi)的傳遞,Instrumentation逆日, iActivityManager嵌巷,者兩個(gè)類(lèi)都可以作為hook點(diǎn),這里介紹hook iActivityManager的這種方法室抽;

2.1 創(chuàng)建hook點(diǎn)的代理類(lèi)搪哪,iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())){
            //  首先找到,原本需要啟動(dòng)的插件activity的原始intent
            Intent originIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
                if (args[i] instanceof Intent){
                    originIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            //  新建欺騙ams的占坑activity的intent
            Intent fakeIntent = new Intent();
            fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
            //  將真實(shí)的intent保存在fakeIntent中用于第三步的還原操作
            fakeIntent.putExtra("real_intent",originIntent);
            //  將fakeIntent寫(xiě)回原來(lái)的arges數(shù)組中
            args[index] = fakeIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}

這里使用的動(dòng)態(tài)代理創(chuàng)建iActivityManager的代理坪圾,首先找到原本啟動(dòng)的插件Activity的Intent晓折,然后新建一個(gè)啟動(dòng)SubActivity的intent替換它;

2.2替換原本的iActivityManager:
    public void hookAMS() throws Exception {
        // 獲取ActivityManager getService 返回的單例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通過(guò)單例.get()獲取iActivityManager, 這兩步需要參考源碼的iActivityManager的獲取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成動(dòng)態(tài)代理對(duì)象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 將代理對(duì)象設(shè)置到單例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }
  • 這個(gè)方法需要在startActivity前調(diào)用

3. 還原插件Activity

繞開(kāi)ams驗(yàn)證后兽泄,我們還需要真實(shí)的啟動(dòng)TargetActivity漓概,再學(xué)習(xí)了Handler機(jī)制后,我們知道m(xù)essage的處理順序是首先會(huì)判斷當(dāng)前message.callback有沒(méi)有邏輯病梢,會(huì)首先執(zhí)行callback胃珍;我們可以將Message作為Hook點(diǎn)

3.1 創(chuàng)建自定義CallBack,在handleMessage處理前蜓陌,將fakeIntent換成真實(shí)的intent
  class MCallBack implements android.os.Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            try {
                Object activityClientRecord = msg.obj;
                // 獲取fakeIntent
                Class acrClazz = activityClientRecord.getClass();
                Field intentField = acrClazz.getDeclaredField("intent");
                Intent intent = (Intent) intentField.get(activityClientRecord);
                // 取出targetActivity的Intent
                Intent realIntent = intent.getParcelableExtra("real_intent");
                // 將realIntent的內(nèi)容設(shè)置到fakeIntent
                intent.setComponent(realIntent.getComponent());
                
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            msg.getTarget().handleMessage(msg);
            return true;
        }
    }
3.2 hook ActivityThread觅彰, 修改主線程的H(Handler)的CallBack屬性,原理參考dispatchMessage方法
    private void hookActivityThread() throws Exception {
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        Object activityThreadInstance = singleInstanceField.get(null);
        
        Field mHField = activityThreadClass.getDeclaredField("mH");
        Handler handler = (Handler) mHField.get(activityThreadInstance);
        
        // 修改handler 的callback
        Class handlerClazz = handler.getClass();
        Field callbackField = handlerClazz.getDeclaredField("mCallback");
        callbackField.set(handler,new MCallBack());
    }

在Handler機(jī)制中有兩個(gè)callback护奈,一個(gè)是Handler.mCallback缔莲,一個(gè)是Message.callback

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在loop中輪詢處理message的時(shí)候會(huì)調(diào)用dispatchMessage;如果Message.callback不會(huì)null就處理Runnable的回調(diào)然后結(jié)束霉旗,如果msg.callback為null痴奏,則先執(zhí)行Handler的mCallback,并根據(jù)Handler的mCallback.handleMessage的返回值判斷是否執(zhí)行Handler.handleMessage;
根據(jù)上面的流程厌秒,我們可以在ActivityThread的H處理startActivity這個(gè)Message的handleMessage前读拆,在H的Callback中插入修改intent的代碼,做到真實(shí)的開(kāi)啟TargetActivity

3.3 插件Activity的生命周期管理:

上面的操作只做到了開(kāi)啟activity鸵闪,插件activity的生命周期是如何管理的檐晕,AMS通過(guò)token來(lái)對(duì)activity進(jìn)行識(shí)別管理,而插件activity token的綁定是不受影響的,所以插件activity是具有生命周期的辟灰;

Service插件化原理

代理分發(fā)實(shí)現(xiàn):

當(dāng)啟動(dòng)插件Service時(shí)个榕,就會(huì)先啟動(dòng)代理Service,當(dāng)代理Service運(yùn)行后芥喇,在其onStartCommand中啟動(dòng)插件Service西采;

步驟:
  • 項(xiàng)目中準(zhǔn)備好代理Service
  • hook iActivityManager 啟動(dòng)代理Service
  • 代理分發(fā):
  1. ProxyService需要長(zhǎng)時(shí)間對(duì)插件Service進(jìn)行分發(fā),所以需要return START_STICKY ProxyService重新創(chuàng)建
  2. 創(chuàng)建插件Service继控,attach械馆,onCreate;

1. 在項(xiàng)目中創(chuàng)建一個(gè)ProxyService武通,在manifest中注冊(cè)霹崎;

2. hook iActivityManager,將要啟動(dòng)的TargetService換成ProxyService

2.1 創(chuàng)建自定義iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startService".equals(method.getName())){
            Intent targetIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
                if (args[i] instanceof Intent){
                    targetIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            Intent proxyIntent = new Intent();
            proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
            proxyIntent.putExtra("target_intent",targetIntent);
            args[index] = proxyIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}
2.2 hook AMS替換原來(lái)的IActivityManager 同上
 public void hookAMS() throws Exception {
        // 獲取ActivityManager getService 返回的單例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通過(guò)單例.get()獲取iActivityManager, 這兩步需要參考源碼的iActivityManager的獲取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成動(dòng)態(tài)代理對(duì)象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 將代理對(duì)象設(shè)置到單例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }

只要在startService前調(diào)用這段代碼冶忱,就會(huì)啟動(dòng)proxyService尾菇,下面我們?cè)趐roxyService中對(duì)targetService進(jìn)行分發(fā);

2.3 在proxyService中啟動(dòng)TargetService:
  • 調(diào)用attach綁定Context
  • 調(diào)用onCreate
public class ProxyService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        try {
            // 準(zhǔn)備attach方法的參數(shù)
            Class activityThreadClazz = null;
            activityThreadClazz = Class.forName("android.app.ActivityThread");
            Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
            Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            // activityThread
            Object activityThread = sCurrentActivityThreadField.get(null);
            // applicationThread
            Object applicationThread = getApplicationMethod.invoke(activityThread, null);
            Class iInterFaceClazz = Class.forName("android.os.IInterface");
            Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
            asBinderMethod.setAccessible(true);
            // token
            Object token = asBinderMethod.invoke(applicationThread);
            // iActivityManager
            Class ActivityManagerClazz = ActivityManager.class;
            Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
            Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
            Class singleClazz = IActivityManagerSingleton.getClass();
            Method getMethod = singleClazz.getDeclaredMethod("get");
            Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);

            // targetService
            Class serviceClazz = Class.forName("android.app.Service");
            Service targetService = (Service) serviceClazz.newInstance();

            // attach
            Method attachMethod = serviceClazz.getDeclaredMethod("attach");
            attachMethod.invoke(targetService, this,
                    activityThread, intent.getComponent().getClassName(),
                    token, getApplication(), iActivityManager);
            targetService.onCreate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return START_STICKY;
    }
}

  • 首先準(zhǔn)備attach需要的參數(shù)朗和,通過(guò)反射獲取
  • 調(diào)用targetService的attach方法
  • 調(diào)用targetService的onCreate方法

資源的插件化

參考這邊換膚文章:安卓換膚實(shí)現(xiàn)原理

某東的插件化實(shí)踐

1. 插件化做的事情:

插件化的目的就是在主工程使用插件工程的代碼/類(lèi)資源

1.1插件中四大組件的處理:

當(dāng)使用插件的四大組件類(lèi)時(shí)错沽,比如插件activity的使用必須做特殊的處理,一般的處理方式有:

  • hook AMS眶拉,繞過(guò)AMS對(duì)四大組件的驗(yàn)證千埃,手動(dòng)管理生命周期
  • 直接在主工程的manifest注冊(cè)插件的activity

方式1的特點(diǎn)是實(shí)現(xiàn)難度高,靈活性高忆植,但是隨著谷歌對(duì)于系統(tǒng)非sdk調(diào)用的限制放可,這個(gè)方式可能會(huì)在未來(lái)失效;
方式2的特點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單朝刊,但是不夠靈活耀里,必須在manifest中寫(xiě)死

某東采用的方式2,直接在manifest中寫(xiě)死插件的四大組件注冊(cè)

1.2插件中類(lèi)的加載和使用:

每一個(gè)插件都設(shè)置一個(gè)ClassLoader拾氓,目的是整個(gè)插件的類(lèi)都是由一個(gè)加載器加載冯挎,所有的插件的ClassLoader都在雙親委派中繼承了另一個(gè)ClassLoader,這個(gè)目的是便于主工程的統(tǒng)一管理咙鞍;


1.3DelegateClassLoader的替換:

替換LoadedAPK的ClassLoader為DelegateClassLoader即可房官;

1.4插件資源的引用

添加一個(gè)path給AssetManager就行,詳見(jiàn)上文

2. 插件如何打包進(jìn)主工程续滋,即主工程如何集成插件包:

  • 將插件apk放入asset目錄翰守,通過(guò)assetManager去加載
  • 將插件apk文件修改后綴為.so 放入lib/armeabi目錄,主工程apk在安裝的時(shí)候會(huì)自動(dòng)將這個(gè)目錄的文件加載到data/data/< package_name >/lib/目錄下疲酌,可以直接獲壤濉了袁;

某東使用的第二種以so的形式放入lib目錄自動(dòng)加載,因?yàn)樵谶\(yùn)行時(shí)去使用AssetManager加載asset資源會(huì)影響程序的運(yùn)行時(shí)速度

3. 插件和主工程如何通信

主要借鑒的airbnb的DeepLinkDispatch

DeepLinkDispatch類(lèi)似Android原生的scheme協(xié)議湿颅,用于跳轉(zhuǎn)到另一個(gè)APP的頁(yè)面载绿,比如在美團(tuán)中打開(kāi)高德地圖,或者在微信中打開(kāi)京東油航,

DeepLinkDispatch的實(shí)現(xiàn)思路:首先在插件工程中需要被打開(kāi)的Activity添加注解卢鹦,然后在主工程調(diào)用DeepLinkDispatch.startActivityDirect()傳入注解中設(shè)置好的參數(shù),最后會(huì)通過(guò)系統(tǒng)的apiContext.startActivity()開(kāi)啟頁(yè)面

4. 插件化的未來(lái)

隨著谷歌對(duì)于系統(tǒng)API的限制越來(lái)越嚴(yán)格劝堪,并且現(xiàn)在已經(jīng)分成黑名單,深灰名單揉稚,淺灰名單留時(shí)間開(kāi)發(fā)者調(diào)整秒啦,插件化應(yīng)該是沒(méi)有未來(lái)的,我們想想插件化到底是為了什么:

  • 獨(dú)立編譯搀玖,提高開(kāi)發(fā)效率
  • 模塊解耦余境,復(fù)用代碼
東東的解決方案:

組件化:
傳統(tǒng)的組件話是新建一個(gè)Android Library,在開(kāi)發(fā)調(diào)試和實(shí)際引用的時(shí)候在Application和Library之間切換灌诅,東東的組件化是將每一個(gè)組件單獨(dú)做成一個(gè)項(xiàng)目芳来,然后在項(xiàng)目結(jié)構(gòu)中保留Application和Android Library,library用于實(shí)現(xiàn)組件的功能猜拾,app用于開(kāi)發(fā)調(diào)試即舌,在主工程使用時(shí)直接通過(guò)依賴從云端的maven sync;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挎袜,一起剝皮案震驚了整個(gè)濱河市顽聂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盯仪,老刑警劉巖紊搪,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異全景,居然都是意外死亡耀石,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)爸黄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滞伟,“玉大人,你說(shuō)我怎么就攤上這事馆纳∈迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵鲁驶,是天一觀的道長(zhǎng)鉴裹。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么径荔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任督禽,我火速辦了婚禮,結(jié)果婚禮上总处,老公的妹妹穿的比我還像新娘狈惫。我一直安慰自己,他們只是感情好鹦马,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布胧谈。 她就那樣靜靜地躺著,像睡著了一般荸频。 火紅的嫁衣襯著肌膚如雪菱肖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天旭从,我揣著相機(jī)與錄音稳强,去河邊找鬼。 笑死和悦,一個(gè)胖子當(dāng)著我的面吹牛退疫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸽素,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼褒繁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了馍忽?” 一聲冷哼從身側(cè)響起澜汤,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舵匾,沒(méi)想到半個(gè)月后俊抵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坐梯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年徽诲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吵血。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谎替,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹋辅,到底是詐尸還是另有隱情钱贯,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布侦另,位于F島的核電站秩命,受9級(jí)特大地震影響尉共,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弃锐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一袄友、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霹菊,春花似錦剧蚣、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至饶碘,卻和暖如春待诅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熊镣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留募书,地道東北人绪囱。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像莹捡,于是被迫代替她去往敵國(guó)和親鬼吵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360