avatar

目录
Android中插件化的简单实现:启动未注册的Activity

个人博客

http://www.milovetingting.cn

Android中插件化的简单实现:启动未注册的Activity

前言

本文介绍在Android中启动未在AndroidManifest中注册的Activity的一个解决方案。主要需要掌握以下知识点:

  1. 反射

  2. 类加载

  3. Activity的启动过程

  4. Resource加载过程

启动应用内未注册的Activity

Activity默认都需要在AndroidManifest中注册,未注册的应用无法启动。AMS在启动应用时,会检测是否已经注册。因此,如果想要启动未注册的Activity,那么需要在Activity前,替换启动应用的Intent为已经注册过的Activity,因此可以新建一个Activity,用于占位。在检测通过后,真正启动Activity前再替换回需要启动的未注册的Activity。

获取替换Intent的Hook点

调用startActivity方法后,最后都会在Instrumentation的execStartActivity方法中调用AMS的远程方法进行处理。Android6.0及以下和Android6.0以上,在execStartActivity中调用AMS的方法有所不同,因此需要做兼容处理。

6.0

java
1
2
3
4
5
6
7
8
9
10
11
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
//...
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//...
}

通过调用ActivityManagerNative.getDefault()来获取AMS。

8.0

java
1
2
3
4
5
6
7
8
9
10
11
12
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
//...
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//...
}

通过调用ActivityManager.getService()来获取AMS。

替换Intent

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void hookAMS() {
try {
Field singletonField;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
singletonField = getField(Class.forName("android.app.ActivityManager"), "IActivityManagerSingleton");
} else {
singletonField = getField(Class.forName("android.app.ActivityManagerNative"), "gDefault");
}
Object singleton = singletonField.get(null);

Field mInstanceField = getField(Class.forName("android.util.Singleton"), "mInstance");
final Object mInstance = mInstanceField.get(singleton);

final Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{Class.forName("android.app.IActivityManager")}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent intent = (Intent) args[index];

Intent proxyIntent = new Intent(intent);
//占位的Activity
proxyIntent.setClassName("com.wangyz.plugindemo", "com.wangyz.plugindemo.ProxyActivity");
proxyIntent.putExtra("target_intent", intent);

args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
}

获取还原Intent的Hook点

Android8.0及以下

启动Activity的消息,会回调到ActivityThread中的mH的dispatchMessage方法,可以通过给mH设置一个callBack,在callBack的handleMessage中,然后替换回真正要启动的Intent,然后返回false,让handleMessage再继续处理。

java
1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

Android8.0及以下,在ActivityThread的mH中的handleMessage方法中,会处理LAUNCH_ACTIVITY类型的消息,在这里调用了handleLaunchActivity方法来启动Activity。

java
1
2
3
4
5
6
7
8
9
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);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

Android9.0

和8.0一样,设置callBack,然后修改Intent。

在ActivityThread的mH中的handleMessage方法中,会处理EXECUTE_TRANSACTION类型的消息,在这里调用了TransactionExecutor.execute方法

java
1
2
3
4
5
6
7
8
9
10
11
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;

execute方法中会调用executeCallbacks

java
1
2
3
4
5
6
7
8
9
10
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);

executeCallbacks(transaction);

executeLifecycleState(transaction);
mPendingActions.clear();
log("End resolving transaction");
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null) {
// No callbacks to execute, return early.
return;
}
log("Resolving callbacks");

final IBinder token = transaction.getActivityToken();
ActivityClientRecord r = mTransactionHandler.getActivityClient(token);

// In case when post-execution state of the last callback matches the final state requested
// for the activity in this transaction, we won't do the last transition here and do it when
// moving to final state instead (because it may contain additional parameters from server).
final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
: UNDEFINED;
// Index of the last callback that requests some post-execution state.
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);

final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
log("Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
item.getPostExecutionState());
if (closestPreExecutionState != UNDEFINED) {
cycleToPath(r, closestPreExecutionState);
}

item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
if (r == null) {
// Launch activity request will create an activity record.
r = mTransactionHandler.getActivityClient(token);
}

if (postExecutionState != UNDEFINED && r != null) {
// Skip the very last transition and perform it by explicit state request instead.
final boolean shouldExcludeLastTransition =
i == lastCallbackRequestingState && finalState == postExecutionState;
cycleToPath(r, postExecutionState, shouldExcludeLastTransition);
}
}
}

这个方法里会调用ClientTransactionItem的execute方法。ClientTransactionItem是在ActivityStackSupervisor中的realStartActivityLocked中添加的

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
// Create activity launch transaction.
final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
r.appToken);
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, mService.isNextTransitionForward(),
profilerInfo));
}

因此,ClientTransactionItem对应的具体类为LaunchActivityItem,它对应的execute方法

java
1
2
3
4
5
6
7
8
9
10
11
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}

在它的方法里又调用了ClientTransactionHandler的handleLaunchActivity,而ClientTransactionHandler就是在ActivityThread中定义的

java
1
private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);

ActivityThread继承了ClientTransactionHandler,那么它就会实现handleLaunchActivity。最终在这个方法里启动Activity

java
1
2
3
4
5
6
7
public final class ActivityThread extends ClientTransactionHandler {
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {

}
}

还原Intent

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static void hookHandler() {
try {
Field sCurrentActivityThreadThread = getField(Class.forName("android.app.ActivityThread"), "sCurrentActivityThread");
Object activityThread = sCurrentActivityThreadThread.get(null);

Field mHField = getField(Class.forName("android.app.ActivityThread"), "mH");
Object mH = mHField.get(activityThread);

Field mCallbackField = getField(Class.forName("android.os.Handler"), "mCallback");
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100: {
try {
Field intentField = getField(msg.obj.getClass(), "intent");
Intent proxyIntent = (Intent) intentField.get(msg.obj);
Intent targetIntent = proxyIntent.getParcelableExtra("target_intent");
if (targetIntent != null) {
// proxyIntent.setComponent(targetIntent.getComponent());
intentField.set(msg.obj, targetIntent);
}
} catch (Exception e) {
e.printStackTrace();
}

}
break;
case 159: {
try {
Field mActivityCallbacksField = getField(msg.obj.getClass(), "mActivityCallbacks");
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);

Field mIntentField = getField(launchActivityItem.getClass(), "mIntent");
Intent intent = (Intent) mIntentField.get(launchActivityItem);
// 获取插件的
Intent proxyIntent = intent.getParcelableExtra("target_intent");
//替换
if (proxyIntent != null) {
mIntentField.set(launchActivityItem, proxyIntent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
default:
break;
}
return false;
}
});

} catch (Exception e) {
e.printStackTrace();
}
}

在Application创建时Hook

java
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onCreate() {
super.onCreate();
//一般是从服务器下载回来,然后复制到应用的私有目录下,这里演示从sdcard复制到data目录下,6.0及以上需要申请动态权限。复制应该放在非UI线程上做,这里简化操作,放在UI线程上操作。
String pluginPath = getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath();
pluginPath = pluginPath + "/plugin.apk";
if (!new File(pluginPath).exists()) {
FileUtil.copyFile(PLUGIN_PATH, pluginPath);
}
HookUtil.loadPlugin(this, pluginPath);
HookUtil.hookAMS();
HookUtil.hookHandler();
}

到这里,就可以启用同一应用内未注册的Activity。

启动插件应用内的Activity

启动非同一应用内的Activity,相比启动同一应用内的Activity,需要多几个步骤。由于不在一个应用内,所以需要把插件的APK先加载进来,然后同样也需要在AMS检测前替换Intent为占位的Intent,在检测后,启动Activity前替换回为需要启动Activity的Intent。另外,由于插件是动态加载进去的,也需要解决资源加载的问题。

加载插件

加载插件主要是用到类加载器

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void loadPlugin(Context context, String dexPath) {

//判断dex是否存在
File dex = new File(dexPath);
if (!dex.exists()) {
return;
}

try {
//获取自己的dexElements
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

Field pathListField = getField(pathClassLoader.getClass(), "pathList");
Object pathListObject = pathListField.get(pathClassLoader);

Field dexElementsField = getField(pathListObject.getClass(), "dexElements");
Object[] dexElementsObject = (Object[]) dexElementsField.get(pathListObject);

//获取dex中的dexElements
File odex = context.getDir("odex", Context.MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odex.getAbsolutePath(), null, pathClassLoader);

Field pluginPathListField = getField(dexClassLoader.getClass(), "pathList");
Object pluginPathListObject = pluginPathListField.get(dexClassLoader);

Field pluginDexElementsField = getField(pluginPathListObject.getClass(), "dexElements");
Object[] pluginDexElementsObject = (Object[]) pluginDexElementsField.get(pluginPathListObject);

Class<?> elementClazz = dexElementsObject.getClass().getComponentType();
Object newDexElements = Array.newInstance(elementClazz, pluginDexElementsObject.length + dexElementsObject.length);
System.arraycopy(pluginDexElementsObject, 0, newDexElements, 0, pluginDexElementsObject.length);
System.arraycopy(dexElementsObject, 0, newDexElements, pluginDexElementsObject.length, dexElementsObject.length);

//设置
dexElementsField.set(pathListObject, newDexElements);

} catch (Exception e) {
e.printStackTrace();
}

}

替换Intent

这个过程和应用内的情况是一样的,不再赘述

加载资源

加载资源主要用到AssetManager的addAssetPath方法,通过反射来加载

java
1
2
3
4
5
6
7
8
9
10
11
12
13
private static Resources loadResource(Context context) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPathField = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathField.setAccessible(true);
addAssetPathField.invoke(assetManager, PATH);
Resources resources = context.getResources();
return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

源码

https://github.com/milovetingting/Samples/tree/master/PluginDemo

文章作者: milovetingting
文章链接: http://www.milovetingting.cn/2020/03/10/Android/Android%E4%B8%AD%E6%8F%92%E4%BB%B6%E5%8C%96%E7%9A%84%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0%EF%BC%9A%E5%90%AF%E5%8A%A8%E6%9C%AA%E6%B3%A8%E5%86%8C%E7%9A%84Activity/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 milovetingting

评论