Android中插件化簡(jiǎn)析

插件化簡(jiǎn)介

Android中熱修復(fù)主要用來(lái)修復(fù)bug,插件化則主要用來(lái)增加功能,將一些獨(dú)立的功能打包為單獨(dú)的dex作為插件。在需要的時(shí)候再動(dòng)態(tài)加載。

Android中的插件化以Hook方式為主流。

Activity插件化

Activity的插件化必然涉及Activity的啟動(dòng)過(guò)程。從Android中根Activity的啟動(dòng)過(guò)程可知,Activity的啟動(dòng)分為3個(gè)階段

Launcher或Activity請(qǐng)求AMS過(guò)程(第一次啟動(dòng)應(yīng)用程序或應(yīng)用程序打開(kāi)新的Activity)

AMS到ApplicationThread的調(diào)用過(guò)程

ActivityThread啟動(dòng)Activity過(guò)程

因?yàn)锳ctivity啟動(dòng)時(shí)必須先在Manifest中注冊(cè)。而插件中的Activity是無(wú)法提前注冊(cè)的。再根據(jù)Activity的啟動(dòng)過(guò)程,那么Activity的插件化可以分為以下3步:

占坑:使用一個(gè)占坑Activity-LocalActivity在Manifest中注冊(cè),避免插件Activity沒(méi)有注冊(cè)導(dǎo)致崩潰的情況

繞過(guò)AMS驗(yàn)證:?jiǎn)?dòng)插件Activity-PlugActivity時(shí),將PlugActivity替換為L(zhǎng)ocalActivity,進(jìn)行AMS的驗(yàn)證,生命周期及棧管理。

還原Activity:AMS調(diào)用ActivityThread啟動(dòng)Activity時(shí),將LocalActivity再替換為PlugActivity

那么再看每一步的具體實(shí)現(xiàn):

  • 創(chuàng)建占坑Activity-LocalActivity

    //一個(gè)空實(shí)現(xiàn)的Activity
    public class LocalActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_local);
        }
    }
    //在manifest中注冊(cè)
    <activity android:name=".LocalActivity"></activity>
    
  • 繞過(guò)AMS驗(yàn)證:

    要繞過(guò)AMS驗(yàn)證,必須讓在Manifest中注冊(cè)的LocalActivity代替要啟動(dòng)的PlugActivity去驗(yàn)證,那么就要在啟動(dòng)時(shí)將PlugActivity替換為L(zhǎng)ocalActivity。

    Android中Hook簡(jiǎn)析中分析過(guò),Activity啟動(dòng)時(shí)調(diào)用了Instrumentation的execStartActivity(),那么可以通過(guò)Hook的方式在這里進(jìn)行替換,先看一下之前Hook的代碼:

    public class InstrumentationProxy extends Instrumentation {
        Instrumentation instrumentation;
        private Method method;
          //持有真正啟動(dòng)Activity的Instrumentation引用
        public InstrumentationProxy(Instrumentation instrumentation) {
            this.instrumentation = instrumentation;
        }
          //重寫(xiě)啟動(dòng)Activity的execStartActivity()
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
                   //將要啟動(dòng)的Activity改為L(zhǎng)ocalActivity
                  intent.setClassName(who,"com.madnessxiong.client.LocalActivity");
              //通過(guò)反射獲取Instrumentation的class對(duì)象
            Class<? extends Instrumentation> aClass = Instrumentation.class;
            try{
              //通過(guò)class對(duì)象獲取真正啟動(dòng)Activity的execStartActivities()
               method = aClass.getMethod("execStartActivity",                Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
             //通過(guò)反射的方式執(zhí)行真正啟動(dòng)Activity的execStartActivities()
            ActivityResult invoke = (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, options);
                return invoke;
            }catch (Exception e){
            }
            return null;
        }
    
    
    }
    

    在這里修改了intent,將要啟動(dòng)的Activity改為了LocalActivity。完成了替換工作。這時(shí)啟動(dòng)Activity的就是LocalActivity。由于LocalActivity在Manifest中注冊(cè)過(guò),所以可以通過(guò)AMS的校驗(yàn)。

  • 還原Activity

    AMS最終會(huì)調(diào)用到ActivityThread.performLaunchActivity():

           private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                      //在newActivity()中用類(lèi)加載器創(chuàng)建activity的實(shí)例,
                   activity = mInstrumentation.newActivity(
                           cl, component.getClassName(), r.intent);
               return activity;
           }
    

    然后通過(guò)mInstrumentation.newActivity()創(chuàng)建一個(gè)Activity,那么這里可以作為一個(gè)hook點(diǎn)。代理Instrumentation的newActivity()。

    根據(jù)上面的分析,那么通過(guò)Hook的方式將ActivityThread的mInstrumentation替換為InstrumentationProxy:

        public void HookActivityThread(){
            try {
                  //獲取ContextImpl.class
                Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
                  //獲取mMainThread對(duì)應(yīng)的成員變量
                Field mMainThreadFiled = contextImplClass.getDeclaredField("mMainThread");
                mMainThreadFiled.setAccessible(true);
                  //成員變量activityThread
                Object activityThread = mMainThreadFiled.get(getBaseContext());
                          //獲取ActivityThread.class
                Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                  //獲取Instrumentation屬性
                Field field = activityThreadClass.getDeclaredField("mInstrumentation");
                field.setAccessible(true);
    
                Instrumentation mInstrumentation = (Instrumentation)field.get(activityThread);
                InstrumentationProxy instrumentationProxy = new InstrumentationProxy(mInstrumentation);
                field.set(activityThread,instrumentationProxy);
            }catch (Exception e){
              
            }
    
        }
    

    由于A(yíng)ctivityThread時(shí)@hide無(wú)法直接獲取它的對(duì)象,而在進(jìn)程啟動(dòng)時(shí)已經(jīng)將一個(gè)mMainThread存入了ContextImpl中。所以這里先通過(guò)反射的方式在ContextImpl中獲取了ActivityThread對(duì)象,最后用InstrumentationProxy替換mInstrumentation。那么調(diào)用newActivity()時(shí),就會(huì)來(lái)到InstrumentationProxy.newActivity():

        public Activity newActivity(ClassLoader cl, String className,
                                    Intent intent)
                throws InstantiationException, IllegalAccessException,
                ClassNotFoundException {
            return super.newActivity(cl,"com.madnessxiong.client.PlugActivity",intent);
        }
    

    在InstrumentationProxy.newActivity()中,直接調(diào)用父類(lèi),也就是Instrumentation自身的newActivity(),將類(lèi)名改為實(shí)際要啟動(dòng)的PlugActivity。就完成了Activity的還原。而整個(gè)替換的過(guò)程只是對(duì)intent中的類(lèi)名進(jìn)行了替換,而在A(yíng)MS中是通過(guò)token對(duì)Activity進(jìn)行驗(yàn)證的,所以并不影響它的生命周期。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容