第三方美小二加载直连流程
美小二插件使用的是DroidPlugin框架 所以先得理解一下这个框架的原理
- 如何把插件apk中的代码和资源加载到当前虚拟机
- 如何把插件apk中的四大组件注册到进程中。
- 如何防止插件apk中的资源和宿主apk中的资源引用冲突
初始化插件环境
入口BizApplication onCreate方法中调用 PluginHelper.getInstance().applicationOnCreate(getBaseContext());
-
进入HookFactory的installHook方法。这里先走第一次是宿主进程的流程 安装IActivityManagerHook和IPackageManagerHook 在onInstall方法利用动态代理对AMS和PMS的端代理进行hook。具体处理类参考IActivityManagerHookHandle,IPackageManagerHookHandle。
-
进入PluginManager的connectToService 进入PluginServiceProvider的call方法。初始化IPluginManagerImpl该类提供对插件的管理服务。调用loadAllPlugin
-
进入MyActivityManagerService的onCreate方法该类功能
1、系统预定义N个进程。每个进程下有4中launchmod的activity,1个服务,一个ContentProvider。
2、每个插件可以在多个进程中运行,这由插件自己的processName属性决定。
3、插件系统最多可以同时运行N个进程,M个插件(M <= N or M >= N)。
4、多个插件运行在同一个进程中,如果他们的签名相同。(我们可以通过一个开关来决定。)
5、在运行第M+1个插件时,如果预定义的N个进程被占满,最低优先级的进程会被kill掉。腾出预定义的进程用来运行此个插件StaticProcessList:该类主要缓存宿主包下四大组件的信息 key=processName value=ProcessItem ProceeItem中 activityInfos key=ActivityInfo.name,value=ActivityInfo,serviceInfos key=ServiceInfo.name,value=ServiceInfo。
RunningProcessList: 正在运行的进程列表set当前context
loadHostRequestedPermission:缓存预置请求权限,mLock.notifyAll() 解除waitForReadyInner
进入PluginManager的onServiceConnected方法 。IActivityManagerHook和IPackageManagerHook开启hook
-
安装插件包
PluginManager.installPackage(String filepath, int flags) 主要功能
PackageInfo info = pm.getPackageArchiveInfo(filepath, 0); 获取插件apk包信息
PluginDirHelper.getPluginApkFile(mContext, info.packageName);安装路径data/data/{host-packagename}/Plugin/{plugin-packagename}
PluginPackageParser 利用系统的PackageParser将插件AndroidMainfest解析保存到内存中
(这里各个版本的兼容在com.morgoo.droidplugin.pm.parser包下)
启动插件包Activity
找到美小二LoadPluginActivity startPlugin 打个断点。
进入IActivityManagerHookHandle.startActivity的beforeInvoke 方法
这里面就有文章了看看开启前如何将目标Activity替换成AndroidManifest中的Activity
1. 找到startActivity方法中的intent参数
2. IPluginManagerImpl.getActivityInfo 通过intent中的className找到插件中对应的Activity(安装插件包时有将activity信息缓存在PackageParser中),通过PackageParser的generateActivityInfo生成对应的ActivityInfo
3. 选择代理的Activity(Android有一个限制:**必须在AndroidManifest.xml中显示声明使用的Activity**),所以这里要选择一个在AndroidManifest中预先埋好的Activity
4. IPluginManagerImpl.selectStubActivityInfoByIntent 先从MyActivityManagerService的mRunningProcessList 找到是否有正在运行的进程,如果有则直接使用之,否则从mStaticProcessList中选择(mStaticProcessList中的数据在初始化插件环境的时候填充里面是宿主AndroidManifest的信息)合适的ActivityInfo(比较launchMode)。
5. 构建宿主ProxyAcitivity的intent 设置回宿主的Classloader(在插件中使用的是插件自定义classLoader要区分开)。将原来的intent当做bundle传递。
上面是将Activity替换成ProxyActivty欺骗系统的过程,下面分析AMS启动Activity时如何替换成原来的Activity.
这边在新进程第一次启动Activity重新走到宿主Application的onCreate方法。开始初始化插件进程的一些环境
-
hook各种Binder服务。插件访问服务使用宿主的配置 (HookFactory的installHook)
-
PluginCallbackHook的onInstall 中讲ActivityThread中的mH的mCallback替换为PluginCallback
在Activity的期待流程中可以知道AMS startActivity后会交给ActivityThread启动,ActivityThread通过
Handler mH去分发事件
-
在PluginCallback的handleLaunchActivity方法中打个断点.获取通过intent传递过来的原始targetIntent,获取
我们实际要启动的ComponentName 通过插件的PackageParser获取到ActivityInfo
-
PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
这个方法也比较重要 做了下面几个事
- 通过插件的applicationInfo创建了一个loadedApk
- 创建了一个com.morgoo.droidplugin.core.PluginClassLoader 用于加载插件apk中的类hook到loadApk上,当前线程Thread.currentThread().setContextClassLoader(classloader);
- 如果loadedApk的mApplication为空。执行makeApplication方法 这时候就会走到PluginInstrumentation的callApplicationOnCreate 走到插件apk的AndroidManifest中定义的Application类的onCreate方法
- 将targetIntent 写入msg.obj的intent字段
-
我们知道Activity的创建在ActivityThread的performLaunchActivity方法中。
activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);
这里的cl 被我们hook了就是.PluginClassLoader compenet是从targetIntent获取的 所以这里反射获取的是我们插件中的Activity 到这一步Activity 就替换回来了。AMS也接管了该Activity的生命周
资源问题
由于加载插件新开了个进程 第一次LoadedApk.getResources。插件中loadapk被我们hook 里面的资源resDir也是插件包路径.AssetManager的addPath也是add的resDir 所以资源是分离的。如果要宿主访问插件的资源可以使用createPackageContext方式访问
插件版本管理
我们的美小二中的插件版本管理逻辑在com.mwee.android.mwpluginupgrade.load.LoadPluginPresenter中
简单的对插件化做了下了解 还有很多问题没深入
网友评论