Android插件化原理(一)Activity插件化

相關文章

Android深入四大組件系列
Android解析AMS系列
Android解析ClassLoader系列

前言

四大組件的插件化是插件化技術的核心知識點待笑,而Activity插件化更是重中之重,Activity插件化主要有三種實現(xiàn)方式泼返,分別是反射實現(xiàn)、接口實現(xiàn)和Hook技術實現(xiàn)脱惰。反射實現(xiàn)會對性能有所影響止毕,主流的插件化框架沒有采用此方式伶跷,關于接口實現(xiàn)可以閱讀dynamic-load-apk的源碼揉抵,這里不做介紹,目前Hook技術實現(xiàn)是主流品洛,因此本篇文章主要介紹Hook技術實現(xiàn)树姨。
Hook技術實現(xiàn)主要有兩種解決方案 ,一種是通過Hook IActivityManager來實現(xiàn)桥状,另一種是Hook Instrumentation實現(xiàn)帽揪。在講到這兩個解決方案前,我們需要從整體上了解Activity的啟動流程辅斟。

1.Activity的啟動過程回顧

Activity的啟動過程主要分為兩種转晰,一種是根Activity的啟動過程,一種是普通Activity的啟動過程士飒。關于根Activity的啟動過程在在介紹過查邢,這里來簡單回顧下,如下圖所示酵幕。



圖 1
首先Launcher進程向AMS請求創(chuàng)建根Activity扰藕,AMS會判斷根Activity所需的應用程序進程是否存在并啟動,如果不存在就會請求Zygote進程創(chuàng)建應用程序進程芳撒。應用程序進程啟動后邓深,AMS會請求應用程序進程創(chuàng)建并啟動根Activity。
普通Activity和根Activity的啟動過程大同小異笔刹,但是沒有這么復雜庐完,因為不涉及應用程序進程的創(chuàng)建,跟Laucher也沒關系徘熔,如下圖所示门躯。



圖2
上圖抽象的給出了普通Acticity的啟動過程。在應用程序進程中的Activity向AMS請求創(chuàng)建普通Activity(步驟1)酷师,AMS會對

這個Activty的生命周期管和棧進行管理讶凉,校驗Activity等等。如果Activity滿足AMS的校驗山孔,AMS就會請求應用程序進程中的ActivityThread去創(chuàng)建并啟動普通Activity(步驟2)懂讯。

2.Hook IActivityManager方案實現(xiàn)

AMS是在SystemServer進程中,我們無法直接進行修改台颠,只能在應用程序進程中做文章褐望±兆可以采用預先占坑的方式來解決沒有在AndroidManifest.xml中顯示聲明的問題,具體做法就是在上圖步驟1之前使用一個在AndroidManifest.xml中注冊的Activity來進行占坑瘫里,用來通過AMS的校驗实蔽。
接著在步驟2之后用插件Activity替換占坑的Activity,接下來根據(jù)這個解決方案我們來實踐一下谨读。

2.1 注冊Activity進行占坑

為了更好的講解啟動插件Activity的原理局装,這里省略了插件Activity的加載邏輯,直接創(chuàng)建一個TargetActivity來代表已經(jīng)加載進來的插件Activity劳殖,接著我們再創(chuàng)建一個SubActivity用來占坑铐尚。在AndroidManifest.xml中注冊SubActivity,如下所示哆姻。
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.liuwangshu.pluginactivity">S
    <application
       ...
        <activity android:name=".StubActivity"/>
    </application>
</manifest>

TargetActivity用來代表已經(jīng)加載進來的插件Activity宣增,因此不需要在AndroidManifest.xml進行注冊。如果我們直接在MainActivity中啟動TargetActivity肯定會報錯(android.content.ActivityNotFoundException異常)矛缨。

2.2 使用占坑Activity通過AMS驗證

為了防止報錯统舀,需要將啟動的TargetActivity替換為SubActivity,用SubActivity來通過AMS的驗證劳景。在第六章時講過Android 8.0與7.0的AMS家族有一些差別誉简,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager盟广,直接采用AIDL來進行進程間通信闷串。
Android7.0的Activity的啟動會調(diào)用ActivityManagerNative的getDefault方法,如下所示筋量。
frameworks/base/core/java/android/app/ActivityManagerNative.java

 static public IActivityManager getDefault() {
        return gDefault.get();
    }
 private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

getDefault方法返回了IActivityManager類型的對象烹吵,IActivityManager借助了Singleton類來實現(xiàn)單例,而且gDefault又是靜態(tài)的桨武,因此IActivityManager是一個比較好的Hook點肋拔。
Android8.0的Activity的啟動會調(diào)用ActivityManager的getService方法,如下所示呀酸。
frameworks/base/core/java/android/app/ActivityManager.java

  public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

同樣的凉蜂,getService方法返回了了IActivityManager類型的對象,并且IActivityManager借助了Singleton類來實現(xiàn)單例性誉,確定了無論是Android7.0還是Android8.0窿吩,IActivityManager都是比較好的Hook點。Singleton類如下所示错览,后面會用到纫雁。
frameworks/base/core/java/android/util/Singleton.java

public abstract class Singleton<T> {
    private T mInstance;
    protected abstract T create();
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

由于Hook需要多次對字段進行反射操作,先寫一個字段工具類FieldUtil:
FieldUtil.java

public class FieldUtil {
    public static Object getField(Class clazz, Object target, String name) throws Exception {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return field.get(target);
    }
    public static Field getField(Class clazz, String name) throws Exception{
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return field;
    }
    public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(target, value);
    }

其中setField方法不會馬上用到倾哺,接著定義替換IActivityManager的代理類IActivityManagerProxy轧邪,如下所示刽脖。

public class IActivityManagerProxy implements InvocationHandler {
    private Object mActivityManager;
    private static final String TAG = "IActivityManagerProxy";
    public IActivityManagerProxy(Object activityManager) {
        this.mActivityManager = activityManager;
    }
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {//1
            Intent intent = null;
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            intent = (Intent) args[index];
            Intent subIntent = new Intent();//2
            String packageName = "com.example.liuwangshu.pluginactivity";
            subIntent.setClassName(packageName,packageName+".StubActivity");//3
            subIntent.putExtra(HookHelper.TARGET_INTENT, intent);//4
            args[index] = subIntent;//5
        }
        return method.invoke(mActivityManager, args);
    }
}

Hook點IActivityManager是一個接口,建議采用動態(tài)代理忌愚。在注釋1處攔截startActivity方法曲管,接著獲取參數(shù)args中第一個Intent對象,它是原本要啟動插件TargetActivity的Intent菜循。注釋2翘地、3處新建一個subIntent用來啟動的StubActivity申尤,注釋4處將這個TargetActivity的Intent保存到subIntent中癌幕,便于以后還原TargetActivity。注釋5處用subIntent賦值給參數(shù)args昧穿,這樣啟動的目標就變?yōu)榱薙tubActivity勺远,用來通過AMS的校驗。
最后用代理類IActivityManagerProxy來替換IActivityManager时鸵,如下所示胶逢。

HookHelper.java

public class HookHelper {
    public static final String TARGET_INTENT = "target_intent";
    public static void hookAMS() throws Exception {
        Object defaultSingleton=null;
        if (Build.VERSION.SDK_INT >= 26) {//1
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
            //獲取activityManager中的IActivityManagerSingleton字段
            defaultSingleton=  FieldUtil.getField(activityManageClazz ,null,"IActivityManagerSingleton");
        } else {
            Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
            //獲取ActivityManagerNative中的gDefault字段
            defaultSingleton=  FieldUtil.getField(activityManagerNativeClazz,null,"gDefault");
        }
        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField= FieldUtil.getField(singletonClazz ,"mInstance");//2
        //獲取iActivityManager
        Object iActivityManager = mInstanceField.get(defaultSingleton);//3
        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
        mInstanceField.set(defaultSingleton, proxy);
    }
}

首先在注釋1處對系統(tǒng)版本進行區(qū)分,最終獲取的是 Singleton<IActivityManager>類型的IActivityManagerSingleton或者gDefault字段饰潜。注釋2處獲取Singleton類中的mInstance字段初坠,從前面Singleton類的代碼可以得知mInstance字段的類型為T,在注釋3處得到IActivityManagerSingleton或者gDefault字段中的T的類型彭雾,T的類型為IActivityManager碟刺。最后動態(tài)創(chuàng)建代理類IActivityManagerProxy,用IActivityManagerProxy來替換IActivityManager薯酝。
自定義一個Application半沽,在其中調(diào)用HookHelper 的hookAMS方法,如下所示吴菠。
MyApplication.java

public class MyApplication extends Application {
    @Override
    public void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            HookHelper.hookAMS();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在MainActivity中啟動TargetActivity者填,如下所示。
MainActivity.java

public class MainActivity extends Activity {
    private Button bt_hook;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bt_hook = (Button) this.findViewById(R.id.bt_hook);
        bt_hook.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, TargetActivity.class);
                startActivity(intent);
            }
        });
    }
}

點擊Button時做葵,啟動的并不是TargetActivity而是SubActivity占哟,同時Log中打印了"hook成功",說明我們已經(jīng)成功用SubActivity通過了AMS的校驗酿矢。

2.3 還原插件Activity

前面用占坑Activity通過了AMS的校驗重挑,但是我們要啟動的是插件TargetActivity,還需要用插件TargetActivity來替換占坑的SubActivity棠涮,這一替換的時機就在圖2的步驟2之后谬哀。
ActivityThread啟動Activity的過程,如圖3所示严肪。


圖3

ActivityThread會通過H將代碼的邏輯切換到主線程中史煎,H類是ActivityThread的內(nèi)部類并繼承自Handler谦屑,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java

private class H extends Handler {
public static final int LAUNCH_ACTIVITY         = 100;
public static final int PAUSE_ACTIVITY          = 101;
...
   public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
              }
...
}

H中重寫的handleMessage方法會對LAUNCH_ACTIVITY類型的消息進行處理篇梭,最終會調(diào)用Activity的onCreate方法氢橙。那么在哪進行替換呢?接著來看Handler的dispatchMessage方法:
frameworks/base/core/java/android/os/Handler.java

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

Handler的dispatchMessage用于處理消息恬偷,看到如果Handler的Callback類型的mCallback不為null悍手,就會執(zhí)行mCallback的handleMessage方法。因此袍患,mCallback可以作為Hook點坦康,我們可以用自定義的Callback來替換mCallback,自定義的Callback如下所示诡延。
HCallback.java

public class HCallback implements Handler.Callback{
    public static final int LAUNCH_ACTIVITY = 100;
    Handler mHandler;
    public HCallback(Handler handler) {
        mHandler = handler;
    }
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
            Object r = msg.obj;
            try {
                //得到消息中的Intent(啟動SubActivity的Intent)
                Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, "intent");
                //得到此前保存起來的Intent(啟動TargetActivity的Intent)
                Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
                //將啟動SubActivity的Intent替換為啟動TargetActivity的Intent
                intent.setComponent(target.getComponent());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mHandler.handleMessage(msg);
        return true;
    }
}

HCallback實現(xiàn)了Handler.Callback滞欠,并重寫了handleMessage方法,當收到消息的類型為LAUNCH_ACTIVITY時肆良,將啟動SubActivity的Intent替換為啟動TargetActivity的Intent筛璧。接著我們在HookHelper中定義一個hookHandler方法如下所示。

HookHelper.java

 public static void hookHandler() throws Exception {
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Object currentActivityThread= FieldUtil.getField(activityThreadClass ,null,"sCurrentActivityThread");//1
        Field mHField = FieldUtil.getField(activityThread,"mH");//2
        Handler mH = (Handler) mHField.get(currentActivityThread);//3
        FieldUtil.setField(Handler.class,mH,"mCallback",new HCallback(mH));
    }

ActivityThread類中有一個靜態(tài)變量sCurrentActivityThread惹恃,用于表示當前的ActivityThread對象夭谤,因此在注釋1處獲取ActivityThread中定義的sCurrentActivityThread對象。注釋2處獲取ActivityThread類的mH字段巫糙,接著在注釋3處獲取當前ActivityThread對象中的mH對象朗儒,最后用HCallback來替換mH中的mCallback。
在MyApplication的attachBaseContext方法中調(diào)用HookHelper的hookHandler方法曲秉,運行程序采蚀,當我們點擊啟動插件按鈕,發(fā)現(xiàn)啟動的是插件TargetActivity承二。

2.4 插件Activity的生命周期

插件TargetActivity確實啟動了榆鼠,但是它有生命周期嗎?這里從源碼角度來進行分析亥鸠,Activity的finish方法可以觸發(fā)Activity的生命周期變化妆够,和Activity的啟動過程類似,finish方法如下所示负蚊。
frameworks/base/core/java/android/app/Activity.java

 public void finish() {
        finish(DONT_FINISH_TASK_WITH_ACTIVITY);
    }
 private void finish(int finishTask) {
        if (mParent == null) {
            int resultCode;
            Intent resultData;
            synchronized (this) {
                resultCode = mResultCode;
                resultData = mResultData;
            }
            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
            try {
                if (resultData != null) {
                    resultData.prepareToLeaveProcess(this);
                }
                if (ActivityManager.getService()
                        .finishActivity(mToken, resultCode, resultData, finishTask)) {//1
                    mFinished = true;
                }
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishFromChild(this);
        }
    }

finish方法的調(diào)用鏈和Activity的啟動過程是類似的神妹,注釋1處會調(diào)用的AMS的finishActivity方法,接著是AMS通過ApplicationThread調(diào)用ActivityThread家妆,ActivityThread向H發(fā)送DESTROY_ACTIVITY類型的消息鸵荠,H接收到這個消息會執(zhí)行handleDestroyActivity方法,handleDestroyActivity方法又調(diào)用了performDestroyActivity方法伤极,如下所示蛹找。
frameworks/base/core/java/android/app/ActivityThread.java

  private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ActivityClientRecord r = mActivities.get(token);//1
        ...
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnDestroy(r.activity);//2
               ...
            } catch (SuperNotCalledException e) {
             ...
            }
        }
        mActivities.remove(token);
        StrictMode.decrementExpectedActivityCount(activityClass);
        return r;
   

注釋1處通過IBinder類型的token來獲取ActivityClientRecord姨伤,ActivityClientRecord用于描述應用進程中的Activity。在注釋2處調(diào)用Instrumentation的callActivityOnDestroy方法來調(diào)用Activity的OnDestroy方法庸疾,并傳入了r.activity乍楚。前面的例子我們用SubActivity替換了TargetActivity通過了AMS的校驗,這樣AMS只知道SubActivity的存在届慈,那么AMS是如何能控制TargetActivity生命周期的回調(diào)的徒溪?我們接著往下看,啟動Activity時會調(diào)用ActivityThread的performLaunchActivity方法金顿,如下所示臊泌。
frameworks/base/core/java/android/app/ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          ...
      
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);//1
           ...
             activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
           ...
            mActivities.put(r.token, r);//2
           ...
        return activity;
    }

注釋1處根據(jù)Activity的類名用ClassLoader加載Acitivty,接著調(diào)用Activity的attach方法串绩,將r.token賦值給Activity的成員變量mToken缺虐。在注釋2處將ActivityClientRecord根據(jù)r.token存在mActivities中(mActivities類型為ArrayMap<IBinder, ActivityClientRecord> )芜壁,再結(jié)合Activity的finish方法的注釋1處礁凡,可以得出結(jié)論:AMS和ActivityThread之間的通信采用了token來對Activity進行標識,并且此后的Activity的生命周期處理也是根據(jù)token來對Activity進行標識的慧妄。
回到我們這個例子來顷牌,我們在Activity啟動時用插件TargetActivity替換占坑SubActivity,這一過程在performLaunchActivity方法調(diào)用之前塞淹,因此注釋2處的r.token指向的是TargetActivity窟蓝,在performDestroyActivity的注釋1處獲取的就是代表TargetActivity的ActivityClientRecord,可見TargetActivity是具有生命周期的饱普。

3.Hook Instrumentation方案實現(xiàn)

Hook Instrumentation實現(xiàn)要比Hook IActivityManager實現(xiàn)要簡潔一些运挫,示例代碼會和Hook IActivityManager實現(xiàn)有重復,重復的部分這里不再贅述套耕。
Hook Instrumentation實現(xiàn)同樣也需要用到占坑Activity谁帕,與Hook IActivityManager實現(xiàn)不同的是,用占坑Activity替換插件Activity以及還原插件Activity的地方不同冯袍。Acitivty的startActivity方法調(diào)用時序圖如圖4所示匈挖。


圖4
從圖4可以發(fā)現(xiàn),在Activity通過AMS校驗前康愤,會調(diào)用Activity的startActivityForResult方法:
frameworks/base/core/java/android/app/Activity.java

 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
          ...
        } else {
         ...
        }
    }

startActivityForResult方法中調(diào)用了Instrumentation的execStartActivity方法來激活Activity的生命周期儡循。
如圖3所示,ActivityThread啟動Activity的過程中會調(diào)用ActivityThread的performLaunchActivity方法征冷,如下所示择膝。
frameworks/base/core/java/android/app/ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {       
        ...
        //創(chuàng)建要啟動Activity的上下文環(huán)境
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //用類加載器來創(chuàng)建Activity的實例
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);//1
          ...
        } catch (Exception e) {
          ...
        }
      ...
        return activity;
    }

注釋1處調(diào)用了mInstrumentation的newActivity方法,其內(nèi)部會用類加載器來創(chuàng)建Activity的實例检激‰茸剑看到這里我們可以得到方案踊赠,就是在Instrumentation的execStartActivity方法中用占坑SubActivity來通過AMS的驗證,在Instrumentation的newActivity方法中還原TargetActivity每庆,這兩部操作都和Instrumentation有關筐带,因此我們可以用自定義的Instrumentation來替換掉mInstrumentation。首先我們自定義一個Instrumentation缤灵,在execStartActivity方法中將啟動的TargetActivity替換為SubActivity伦籍,如下所示。
InstrumentationProxy.java

public class InstrumentationProxy extends Instrumentation {
    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;
    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        mInstrumentation = instrumentation;
        mPackageManager = packageManager;
    }
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        if (infos == null || infos.size() == 0) {
            intent.putExtra(HookHelper.TARGET_INTENsT_NAME, intent.getComponent().getClassName());//1
            intent.setClassName(who, "com.example.liuwangshu.pluginactivity.StubActivity");//2
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
            return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
                    target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

首先查找要啟動的Activity是否已經(jīng)在AndroidManifest.xml中注冊了腮出,如果沒有就在注釋1處將要啟動的Activity(TargetActivity)的ClassName保存起來用于后面還原TargetActivity帖鸦,接著在注釋2處替換要啟動的Activity為StubActivity,最后通過反射調(diào)用execStartActivity方法胚嘲,這樣就可以用StubActivity通過AMS的驗證作儿。在InstrumentationProxy 的newActivity方法還原TargetActivity,如下所示馋劈。
InstrumentationProxy.java

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
        IllegalAccessException, ClassNotFoundException {
    String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME);
    if (!TextUtils.isEmpty(intentName)) {
        return super.newActivity(cl, intentName, intent);
    }
    return super.newActivity(cl, className, intent);
}

newActivity方法中創(chuàng)建了此前保存的TargetActivity攻锰,完成了還原TargetActivity。
編寫hookInstrumentation方法妓雾,用InstrumentationProxy替換mInstrumentation:
HookHelper.java

  public static void hookInstrumentation(Context context) throws Exception {
        Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
        Field mMainThreadField  =FieldUtil.getField(contextImplClass,"mMainThread");//1
        Object activityThread = mMainThreadField.get(context);//2
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field mInstrumentationField=FieldUtil.getField(activityThreadClass,"mInstrumentation");//3
        FieldUtil.setField(activityThreadClass,activityThread,"mInstrumentation",new InstrumentationProxy((Instrumentation) mInstrumentationField.get(activityThread),
                context.getPackageManager()));
    }

注釋1處獲取ContextImpl類的ActivityThread類型的mMainThread字段娶吞,注釋2出獲取當前上下文環(huán)境的ActivityThread對象。
注釋3出獲取ActivityThread類中的mInstrumentation字段械姻,最后用InstrumentationProxy來替換mInstrumentation妒蛇。
在MyApplication的attachBaseContext方法中調(diào)用HookHelper的hookInstrumentation方法,運行程序楷拳,當我們點擊啟動插件按鈕绣夺,發(fā)現(xiàn)啟動的是插件TargetActivity。

4. 總結(jié)

這一節(jié)我們學到了啟動插件Activity的原理欢揖,主要的方案就是先用一個在AndroidManifest.xml中注冊的Activity來進行占坑陶耍,用來通過AMS的校驗,接著在合適的時機用插件Activity替換占坑的Activity浸颓。為了更好的講解啟動插件Activity的原理物臂,本小節(jié)省略了插件Activity的加載邏輯,直接創(chuàng)建一個TargetActivity來代表已經(jīng)加載進來的插件Activity产上。同時這一節(jié)使我們更好的理解了Activity的啟動過程棵磷。更多的Android插件化原理請查看即將要出版的《Android進階之光》續(xù)作。


?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晋涣,一起剝皮案震驚了整個濱河市仪媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖算吩,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留凭,死亡現(xiàn)場離奇詭異,居然都是意外死亡偎巢,警方通過查閱死者的電腦和手機蔼夜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來压昼,“玉大人求冷,你說我怎么就攤上這事∏舷迹” “怎么了匠题?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長但金。 經(jīng)常有香客問我韭山,道長,這世上最難降的妖魔是什么冷溃? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任钱磅,我火速辦了婚禮,結(jié)果婚禮上秃诵,老公的妹妹穿的比我還像新娘续搀。我一直安慰自己塞琼,他們只是感情好菠净,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著彪杉,像睡著了一般毅往。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上派近,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天攀唯,我揣著相機與錄音,去河邊找鬼渴丸。 笑死侯嘀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谱轨。 我是一名探鬼主播戒幔,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼土童!你這毒婦竟也來了诗茎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤献汗,失蹤者是張志新(化名)和其女友劉穎敢订,沒想到半個月后王污,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡楚午,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年昭齐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾柜。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡司浪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出把沼,到底是詐尸還是另有隱情啊易,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布饮睬,位于F島的核電站租谈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捆愁。R本人自食惡果不足惜割去,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昼丑。 院中可真熱鬧呻逆,春花似錦、人聲如沸菩帝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呼奢。三九已至宜雀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間握础,已是汗流浹背辐董。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禀综,地道東北人简烘。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像定枷,于是被迫代替她去往敵國和親孤澎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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