目录:
1. VirtualApp 如何启动第三方 App
* 使用占位 Activity 启动
* 栈的管理
2. VirtualApp 如何加载第三方 App
1. VirtualApp 如何启动第三方 App
整体流程:
使用占位 Activity 启动
VirtualApp 采用的是通过占位的方式,因为双开应用并没有真实的安装在系统上,所以需要采用占位的方式,系统启动的是 StubActivity,在 TransactionHandlerProxy 进行替换。
<activity
android:name="com.android.dockercore.client.stub.StubActivity$C10"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
android:process=":p10"
android:taskAffinity="com.android.dockercore.vt"
android:theme="@style/VATheme" />
<activity
android:name="com.android.dockercore.client.stub.StubActivity$C11"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
android:process=":p11"
android:taskAffinity="com.android.dockercore.vt"
android:theme="@style/VATheme" />
<activity
android:name="com.android.dockercore.client.stub.StubActivity$C12"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
android:process=":p12"
android:taskAffinity="com.android.dockercore.vt"
android:theme="@style/VATheme" />
...
public abstract class StubActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// The savedInstanceState\'s classLoader is not exist.
super.onCreate(null);
finish();
// It seems that we have conflict with the other Android-Plugin-Framework.
Intent stubIntent = getIntent();
// Try to acquire the actually component information.
if (r.intent != null) {
if (TextUtils.equals(r.info.processName, VirtualRuntime.getProcessName()) && r.userId == VUserHandle.myUserId()) {
// Retry to inject the HCallback to instead of the exist one.
InvocationStubManager.getInstance().checkEnv(HCallbackStub.class);
Intent intent = r.intent;
intent.setExtrasClassLoader(VClientImpl.get().getCurrentApplication().getClassLoader());
startActivity(intent);
} else {
// Start the target Activity in other process.
VActivityManager.get().startActivity(r.intent, r.userId);
}
}
}
public static class C0 extends StubActivity {
}
public static class C1 extends StubActivity {
}
// ...
}
ActivityStack 中启动进程时存储真实 Activity 数据:
private Intent startActivityProcess(int userId, ActivityRecord sourceRecord, Intent intent, ActivityInfo info) {
intent = new Intent(intent);
ProcessRecord targetApp = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
if (targetApp == null) {
return null;
}
Intent targetIntent = new Intent();
targetIntent.setClassName(DockerCore.get().getHostPkg(), fetchStubActivity(targetApp.vpid, info));
ComponentName component = intent.getComponent();
if (component == null) {
component = ComponentUtils.toComponentName(info);
}
targetIntent.setType(component.flattenToString());
StubActivityRecord saveInstance = new StubActivityRecord(intent, info,
sourceRecord != null ? sourceRecord.component : null, userId);
/**
* stub.putExtra("_VA_|_intent_", intent);
* stub.putExtra("_VA_|_info_", info);
* stub.putExtra("_VA_|_caller_", caller);
* stub.putExtra("_VA_|_user_id_", userId);
*/
saveInstance.saveToIntent(targetIntent);
return targetIntent;
}
TransactionHandlerProxy 还原回来:
@Override
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
Intent stubIntent = mirror.android.app.ActivityThread.ActivityClientRecord.intent.get(r);
/**
* this.intent = stub.getParcelableExtra("_VA_|_intent_");
* this.info = stub.getParcelableExtra("_VA_|_info_");
* this.caller = stub.getParcelableExtra("_VA_|_caller_");
* this.userId = stub.getIntExtra("_VA_|_user_id_", 0);
*/
StubActivityRecord saveInstance = new StubActivityRecord(stubIntent);
if (saveInstance.intent == null) {
Log.i(TAG, "save instance intent is null, return");
return null;
}
Intent intent = saveInstance.intent;
ComponentName caller = saveInstance.caller;
IBinder token = mirror.android.app.ActivityThread.ActivityClientRecord.token.get(r);
ActivityInfo info = saveInstance.info;
// ...
}
栈的管理
任务栈之 taskAffinity 属性
TaskAffinity 特点如下:
- TaskAffinity 参数标识着Activity所需要的任务栈的名称,默认情况下,一个应用中所有 Activity 所需要的任务栈名称都为该应用的包名。
- TaskAffinity 属性一般跟 singleTask 模式或者跟 allowTaskReparenting 属性结合使用,在其他情况下没有实际意义。
- TaskAffinity 属性的值不能与当前应用包名相同,否则其值跟作废没两样。
在 VirtualApp 中启动 58App 后,我们使用 adb shell dumpsys activity activities 来查看当前栈信息:
* TaskRecord{4aeb6c0 #456 A=com.android.dockercore.vt U=0 StackId=456 sz=1}
userId=0 effectiveUid=u0a1836 mCallingUid=u0a1836 mUserSetupComplete=true mCallingPackage=com.docker
affinity=com.android.dockercore.vt
intent={typ=com.wuba.wbrn/com.wuba.LaunchActivity flg=0x18280000 cmp=com.docker/com.android.dockercore.client.stub.StubActivity$C0}
mActivityComponent=com.docker/com.android.dockercore.client.stub.StubActivity$C0
autoRemoveRecents=true isPersistable=true numFullscreen=1 activityType=1
rootWasReset=true mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
Activities=[ActivityRecord{162d156 u0 com.docker/com.android.dockercore.client.stub.StubActivity$C0 t456}]
askedCompatMode=false inRecents=true isAvailable=true
mRootProcess=ProcessRecord{2927ff9 3162:com.docker:p0/u0a1836}
stackId=456
首先 ActivityStack 维护了一个应用的栈的列表如下图所示:
/**
* [Key] = TaskId [Value] = TaskRecord
*/
private final SparseArray<TaskRecord> mHistory = new SparseArray<>();
对于双开应用的 Task 可以看出系统认为启动的 Activity 为 StubActivity, 进程为 com.docker:px
ActivityStack:
/**
* [Key] = TaskId [Value] = TaskRecord
*/
private final SparseArray<TaskRecord> mHistory = new SparseArray<>();
int startActivitiesLocked(int userId, Intent[] intents, ActivityInfo[] infos, String[] resolvedTypes, IBinder token, Bundle options) {
optimizeTasksLocked();
ReuseTarget reuseTarget = ReuseTarget.CURRENT;
Intent intent = intents[0];
ActivityInfo info = infos[0];
ActivityRecord resultTo = findActivityByToken(userId, token);
if (resultTo != null && resultTo.launchMode == LAUNCH_SINGLE_INSTANCE) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TOP)) {
removeFlags(intent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK) && !containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
removeFlags(intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING:
reuseTarget = ReuseTarget.DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS:
reuseTarget = ReuseTarget.MULTIPLE;
break;
}
}
if (containFlags(intent, Intent.FLAG_ACTIVITY_NEW_TASK)) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK) ? ReuseTarget.MULTIPLE : ReuseTarget.AFFINITY;
} else if (info.launchMode == LAUNCH_SINGLE_TASK) {
reuseTarget = containFlags(intent, Intent.FLAG_ACTIVITY_MULTIPLE_TASK) ? ReuseTarget.MULTIPLE : ReuseTarget.AFFINITY;
}
if (resultTo == null && reuseTarget == ReuseTarget.CURRENT) {
reuseTarget = ReuseTarget.AFFINITY;
}
String affinity = ComponentUtils.getTaskAffinity(info);
TaskRecord reuseTask = null;
if (reuseTarget == ReuseTarget.AFFINITY) {
reuseTask = findTaskByAffinityLocked(userId, affinity);
} else if (reuseTarget == ReuseTarget.CURRENT) {
reuseTask = resultTo.task;
} else if (reuseTarget == ReuseTarget.DOCUMENT) {
reuseTask = findTaskByIntentLocked(userId, intent);
}
Intent[] destIntents = startActivitiesProcess(userId, intents, infos, resultTo);
if (reuseTask == null) {
realStartActivitiesLocked(null, destIntents, resolvedTypes, options);
} else {
ActivityRecord top = topActivityInTask(reuseTask);
if (top != null) {
realStartActivitiesLocked(top.token, destIntents, resolvedTypes, options);
}
}
return 0;
}
private TaskRecord findTaskByAffinityLocked(int userId, String affinity) {
for (int i = 0; i < this.mHistory.size(); i++) {
TaskRecord r = this.mHistory.valueAt(i);
if (userId == r.userId && affinity.equals(r.affinity)) {
return r;
}
}
return null;
}
private void startActivityInNewTaskLocked(int userId, Intent intent, ActivityInfo info, Bundle options) {
Intent destIntent = startActivityProcess(userId, null, intent, info);
if (destIntent != null) {
destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
destIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
destIntent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// noinspection deprecation
destIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
} else {
destIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
DockerCore.get().getContext().startActivity(destIntent, options);
} else {
DockerCore.get().getContext().startActivity(destIntent);
}
}
}
Android 5.0 引入了 document-centric 模式,可以让 Activity 在 Recent Screen 中以多个 Task 的样式出现。实现也很简单,只需要在 startActivity 的 Intent 中加入相应的属性即可。
- FLAG_ACTIVITY_NEW_DOCUMENT:如果在 startActivity 的时候,带上这个参数,系统会寻找所有的 Task 来查找是否已经存在了需要启动的 Activity。如果没有找到,那么被启动的 Activity 将会被放入一个新的 Task 中;如果找到了要启动的 Activity,则会把该 Activity 的 Task 移到前台,并调用该 Activity 的 onNewIntent() 方法。
- FLAG_ACTIVITY_MULTIPLE_TASK:如果 intent 带有 FLAG_ACTIVITY_NEW_DOCUMENT 的同时也添加了 FLAG_ACTIVITY_MULTIPLE_TASK,那么系统会每一次都创建一个新的 Task 来存放要启动的 Activity。
2. VirtualApp 如何加载第三方 App
VClientImpl#bindApplicationNoCheck():
(和我们平常看到的使用 Classlocader 的插件化思路不一样,直接通过系统服务创建 LoadedApk 来启动装载):
AppBindData data = new AppBindData();
// 获取该 APP 的安装信息
InstalledAppInfo info = DockerCore.get().getInstalledAppInfo(packageName, 0);
// 获取 applicationInfo
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
// 获取进程名
data.processName = processName;
data.appInfo.processName = processName;
// 获取 providers
data.providers = VPackageManager.get().queryContentProviders(processName, getVUid(), PackageManager.GET_META_DATA);
mBoundApplication = data;
NativeEngine.launchEngine();
// 主线程
Object mainThread = DockerCore.mainThread();
NativeEngine.startDexOverride();
// 创建 Context 上下文
Context context = createPackageContext(data.appInfo.packageName);
// 设置 ActivityThread 数据
Object boundApp = fixBoundApp(mBoundApplication);
mBoundApplication.info = ContextImpl.mPackageInfo.get(context);
mirror.android.app.ActivityThread.AppBindData.info.set(boundApp, data.info);
// make LoadedApk
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
// 设置主线程的 application
mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
ContextFixer.fixContext(mInitialApplication);
// 调用 Application 的生命周期
DockerCore.get().getComponentDelegate().beforeApplicationCreate(mInitialApplication);
资源的加载,还是这个函数:
VClientImpl#bindApplicationNoCheck()
private void bindApplicationNoCheck(String packageName, String processName, ConditionVariable lock) {
...
// 从 VPMS 获取 apk 信息
data.appInfo = VPackageManager.get().getApplicationInfo(packageName, 0, getUserId(vuid));
...
//首次创建Context
Context context = createPackageContext(data.appInfo.packageName);
...
//开始构建子程序包的 Application 对象,并且替换原来通过 Host Stub 生成的 mInitialApplication
mInitialApplication = LoadedApk.makeApplication.call(data.info, false, null);
mirror.android.app.ActivityThread.mInitialApplication.set(mainThread, mInitialApplication);
...
try {
//调用 Application.onCreate
mInstrumentation.callApplicationOnCreate(mInitialApplication);
...
}
已知 LoadedApk 与 Resource 对象全局唯一,且其首次初始化是在初始化 Application 的时候。因此倘若我们能在初始化 Application 生成 Context 前替换掉 LoadedApk 对象中的资源路径,则后面拿到的 Resource 对象都会是插件 apk 对应的 Resource.