相關文章java
Android深刻四大組件系列
Android解析AMS系列
Android解析ClassLoader系列android
四大組件的插件化是插件化技術的核心知識點,而Activity插件化更是重中之重,Activity插件化主要有三種實現方式,分別是反射實現、接口實現和Hook技術實現。反射實現會對性能有所影響,主流的插件化框架沒有采用此方式,關於接口實現能夠閱讀dynamic-load-apk的源碼,這裏不作介紹,目前Hook技術實現是主流,所以本篇文章主要介紹Hook技術實現。 Hook技術實現主要有兩種解決方案 ,一種是經過Hook IActivityManager來實現,另外一種是Hook Instrumentation實現。在講到這兩個解決方案前,咱們須要從總體上了解Activity的啓動流程。markdown
Activity的啓動過程主要分爲兩種,一種是根Activity的啓動過程,一種是普通Activity的啓動過程。關於根Activity的啓動過程在在介紹過,這裏來簡單回顧下,以下圖所示。 app
圖 1首先Launcher進程向AMS請求建立根Activity,AMS會判斷根Activity所需的應用程序進程是否存在並啓動,若是不存在就會請求Zygote進程建立應用程序進程。應用程序進程啓動後,AMS會請求應用程序進程建立並啓動根Activity。 普通Activity和根Activity的啓動過程大同小異,可是沒有這麼複雜,由於不涉及應用程序進程的建立,跟Laucher也不要緊,以下圖所示。 框架
圖2上圖抽象的給出了普通Acticity的啓動過程。在應用程序進程中的Activity向AMS請求建立普通Activity(步驟1),AMS會對 這個Activty的生命週期管和棧進行管理,校驗Activity等等。若是Activity知足AMS的校驗,AMS就會請求應用程序進程中的ActivityThread去建立並啓動普通Activity(步驟2)。ide
AMS是在SystemServer進程中,咱們沒法直接進行修改,只能在應用程序進程中作文章。能夠採用預先佔坑的方式來解決沒有在AndroidManifest.xml中顯示聲明的問題,具體作法就是在上圖步驟1以前使用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來經過AMS的校驗。 接着在步驟2以後用插件Activity替換佔坑的Activity,接下來根據這個解決方案咱們來實踐一下。工具
爲了更好的講解啓動插件Activity的原理,這裏省略了插件Activity的加載邏輯,直接建立一個TargetActivity來表明已經加載進來的插件Activity,接着咱們再建立一個SubActivity用來佔坑。在AndroidManifest.xml中註冊SubActivity,以下所示。 AndroidManifest.xmloop
<?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用來表明已經加載進來的插件Activity,所以不須要在AndroidManifest.xml進行註冊。若是咱們直接在MainActivity中啓動TargetActivity確定會報錯(android.content.ActivityNotFoundException異常)。性能
爲了防止報錯,須要將啓動的TargetActivity替換爲SubActivity,用SubActivity來經過AMS的驗證。在第六章時講過Android 8.0與7.0的AMS家族有一些差異,主要是Android 8.0去掉了AMS的代理ActivityManagerProxy,代替它的是IActivityManager,直接採用AIDL來進行進程間通訊。 Android7.0的Activity的啓動會調用ActivityManagerNative的getDefault方法,以下所示。 frameworks/base/core/java/android/app/ActivityManagerNative.javaui
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類來實現單例,並且gDefault又是靜態的,所以IActivityManager是一個比較好的Hook點。 Android8.0的Activity的啓動會調用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類來實現單例,肯定了不管是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是一個接口,建議採用動態代理。在註釋1處攔截startActivity方法,接着獲取參數args中第一個Intent對象,它是本來要啓動插件TargetActivity的Intent。註釋二、3處新建一個subIntent用來啓動的StubActivity,註釋4處將這個TargetActivity的Intent保存到subIntent中,便於之後還原TargetActivity。註釋5處用subIntent賦值給參數args,這樣啓動的目標就變爲了StubActivity,用來經過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處對系統版本進行區分,最終獲取的是 Singleton<IActivityManager>
類型的IActivityManagerSingleton或者gDefault字段。註釋2處獲取Singleton類中的mInstance字段,從前面Singleton類的代碼能夠得知mInstance字段的類型爲T,在註釋3處獲得IActivityManagerSingleton或者gDefault字段中的T的類型,T的類型爲IActivityManager。最後動態建立代理類IActivityManagerProxy,用IActivityManagerProxy來替換IActivityManager。 自定義一個Application,在其中調用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成功",說明咱們已經成功用SubActivity經過了AMS的校驗。
前面用佔坑Activity經過了AMS的校驗,可是咱們要啓動的是插件TargetActivity,還須要用插件TargetActivity來替換佔坑的SubActivity,這一替換的時機就在圖2的步驟2以後。 ActivityThread啓動Activity的過程,如圖3所示。
圖3
ActivityThread會經過H將代碼的邏輯切換到主線程中,H類是ActivityThread的內部類並繼承自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類型的消息進行處理,最終會調用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,就會執行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實現了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類中有一個靜態變量sCurrentActivityThread,用於表示當前的ActivityThread對象,所以在註釋1處獲取ActivityThread中定義的sCurrentActivityThread對象。註釋2處獲取ActivityThread類的mH字段,接着在註釋3處獲取當前ActivityThread對象中的mH對象,最後用HCallback來替換mH中的mCallback。 在MyApplication的attachBaseContext方法中調用HookHelper的hookHandler方法,運行程序,當咱們點擊啓動插件按鈕,發現啓動的是插件TargetActivity。
插件TargetActivity確實啓動了,可是它有生命週期嗎?這裏從源碼角度來進行分析,Activity的finish方法能夠觸發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方法的調用鏈和Activity的啓動過程是相似的,註釋1處會調用的AMS的finishActivity方法,接着是AMS經過ApplicationThread調用ActivityThread,ActivityThread向H發送DESTROY_ACTIVITY類型的消息,H接收到這個消息會執行handleDestroyActivity方法,handleDestroyActivity方法又調用了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處調用Instrumentation的callActivityOnDestroy方法來調用Activity的OnDestroy方法,並傳入了r.activity。前面的例子咱們用SubActivity替換了TargetActivity經過了AMS的校驗,這樣AMS只知道SubActivity的存在,那麼AMS是如何能控制TargetActivity生命週期的回調的?咱們接着往下看,啓動Activity時會調用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處根據Activity的類名用ClassLoader加載Acitivty,接着調用Activity的attach方法,將r.token賦值給Activity的成員變量mToken。在註釋2處將ActivityClientRecord根據r.token存在mActivities中(mActivities類型爲ArrayMap<IBinder, ActivityClientRecord>
),再結合Activity的finish方法的註釋1處,能夠得出結論:AMS和ActivityThread之間的通訊採用了token來對Activity進行標識,而且此後的Activity的生命週期處理也是根據token來對Activity進行標識的。 回到咱們這個例子來,咱們在Activity啓動時用插件TargetActivity替換佔坑SubActivity,這一過程在performLaunchActivity方法調用以前,所以註釋2處的r.token指向的是TargetActivity,在performDestroyActivity的註釋1處獲取的就是表明TargetActivity的ActivityClientRecord,可見TargetActivity是具備生命週期的。
Hook Instrumentation實現要比Hook IActivityManager實現要簡潔一些,示例代碼會和Hook IActivityManager實現有重複,重複的部分這裏再也不贅述。 Hook Instrumentation實現一樣也須要用到佔坑Activity,與Hook IActivityManager實現不一樣的是,用佔坑Activity替換插件Activity以及還原插件Activity的地方不一樣。Acitivty的startActivity方法調用時序圖如圖4所示。
圖4從圖4能夠發現,在Activity經過AMS校驗前,會調用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方法中調用了Instrumentation的execStartActivity方法來激活Activity的生命週期。 如圖3所示,ActivityThread啓動Activity的過程當中會調用ActivityThread的performLaunchActivity方法,以下所示。 frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... //建立要啓動Activity的上下文環境 ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); //用類加載器來建立Activity的實例 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);//1 ... } catch (Exception e) { ... } ... return activity; } 複製代碼
註釋1處調用了mInstrumentation的newActivity方法,其內部會用類加載器來建立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是否已經在AndroidManifest.xml中註冊了,若是沒有就在註釋1處將要啓動的Activity(TargetActivity)的ClassName保存起來用於後面還原TargetActivity,接着在註釋2處替換要啓動的Activity爲StubActivity,最後經過反射調用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方法中建立了此前保存的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出獲取當前上下文環境的ActivityThread對象。 註釋3出獲取ActivityThread類中的mInstrumentation字段,最後用InstrumentationProxy來替換mInstrumentation。 在MyApplication的attachBaseContext方法中調用HookHelper的hookInstrumentation方法,運行程序,當咱們點擊啓動插件按鈕,發現啓動的是插件TargetActivity。
這一節咱們學到了啓動插件Activity的原理,主要的方案就是先用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來經過AMS的校驗,接着在合適的時機用插件Activity替換佔坑的Activity。爲了更好的講解啓動插件Activity的原理,本小節省略了插件Activity的加載邏輯,直接建立一個TargetActivity來表明已經加載進來的插件Activity。同時這一節使咱們更好的理解了Activity的啓動過程。更多的Android插件化原理請查看《Android進階解密》。