相關文章
Android深入四大元件系列
Android解析AMS系列
Android解析ClassLoader系列
前言
四大元件的插件化是插件化技術的核心知識點,而Activity插件化更是重中之重,Activity插件化主要有三種實作方式,分别是反射實作、接口實作和Hook技術實作。反射實作會對性能有所影響,主流的插件化架構沒有采用此方式,關于接口實作可以閱讀dynamic-load-apk的源碼,這裡不做介紹,目前Hook技術實作是主流,是以本篇文章主要介紹Hook技術實作。 Hook技術實作主要有兩種解決方案 ,一種是通過Hook IActivityManager來實作,另一種是Hook Instrumentation實作。在講到這兩個解決方案前,我們需要從整體上了解Activity的啟動流程。
1.Activity的啟動過程回顧
Activity的啟動過程主要分為兩種,一種是根Activity的啟動過程,一種是普通Activity的啟動過程。關于根Activity的啟動過程在在介紹過,這裡來簡單回顧下,如下圖所示。
圖 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)。
2.Hook IActivityManager方案實作
AMS是在SystemServer程序中,我們無法直接進行修改,隻能在應用程式程序中做文章。可以采用預先占坑的方式來解決沒有在AndroidManifest.xml中顯示聲明的問題,具體做法就是在上圖步驟1之前使用一個在AndroidManifest.xml中注冊的Activity來進行占坑,用來通過AMS的校驗。 接着在步驟2之後用插件Activity替換占坑的Activity,接下來根據這個解決方案我們來實踐一下。
2.1 注冊Activity進行占坑
為了更好的講解啟動插件Activity的原理,這裡省略了插件Activity的加載邏輯,直接建立一個TargetActivity來代表已經加載進來的插件Activity,接着我們再建立一個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用來代表已經加載進來的插件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的啟動會調用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類來實作單例,而且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 = ;
for (int i = ; 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。注釋2、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 >= ) {//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的校驗。
2.3 還原插件Activity
前面用占坑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 = ;
public static final int PAUSE_ACTIVITY = ;
...
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 = ;
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。
2.4 插件Activity的生命周期
插件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是具有生命周期的。
3.Hook Instrumentation方案實作
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() == ) {
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。
4. 總結
這一節我們學到了啟動插件Activity的原理,主要的方案就是先用一個在AndroidManifest.xml中注冊的Activity來進行占坑,用來通過AMS的校驗,接着在合适的時機用插件Activity替換占坑的Activity。為了更好的講解啟動插件Activity的原理,本小節省略了插件Activity的加載邏輯,直接建立一個TargetActivity來代表已經加載進來的插件Activity。同時這一節使我們更好的了解了Activity的啟動過程。更多的Android插件化原理請檢視即将要出版的《Android進階之光》續作。