【VirtualAPP 双开系列06】启动加载第三方 APP 过程

目录:

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.




 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
你通过以下方式下载VirtualApp: 1. 首先,你可以访问VirtualApp的GitHub地址:https://github.com/asLody/VirtualApp。在该页面上,你可以找到关于VirtualApp的更多信息,包括代码、文档和其他相关资源。 2. 另外,你也可以通过执行以下命令来下载VirtualApp: ``` git clone https://github.com/asLody/VirtualApp.git ``` 这将克隆VirtualApp的代码库到你的本地。 请注意,VirtualApp是一个开源项目,因此你可以通过GitHub页面或者使用git命令来获取它的最新版本。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【Android 插件化】VirtualApp 编译运行 ( VirtualApp 简介 | 配置 VirtualApp 编译环境 | 编译运行 ...](https://blog.csdn.net/han1202012/article/details/120754537)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【使用VirtualApp无root对手游进行修改】](https://blog.csdn.net/qq_45071266/article/details/125776070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值