关键词不能为空

位置:白城汽车新闻网 > 汽车资讯 > RePlugin中如何打开插件中的自定义进程Activity-plug

RePlugin中如何打开插件中的自定义进程Activity-plug

作者:白城汽车新闻网
日期:2020-07-11 00:53:19
阅读:
RePlugin中如何打开插件中的自定义进程Activity

在RePlugin中,我们经常使用的一个功能,就是在宿主或插件中,打开另一个插件中的Activity(该Activity位于独立进程中)。

本文将介绍宿主如何识别这个插件中自定义进程,并让开发者无感知的,像在单品中一样运行起来。

例如,插件中存在这样一个Activity,需要运行到“:image1”进程中:

RePlugin中如何打开插件中的自定义进程Activity

1 进程映射

插件中的这个进程标签,宿主肯定是不认识的,所以,第一件要做的事情,就是得让宿主认识这个进程,只有认识后,才能进一步的启动、管理它。

识别的核心思想就是占坑,通过在宿主中预埋若干个可用的进程坑位(“:p0”,“:p1”,“:p2”三个进程),在运行时,将插件中的Activity进程(根本不认识)映射为宿主中的预留进程(这就认识了)。

映射过程是在插件加载时执行的,加载插件时,会读取插件AndroidMainfext.xml中四大组件的“android:process”属性,得到自定义进程列表,然后调整进程名字。

具体流程如下图所示:

RePlugin中如何打开插件中的自定义进程Activity

1.1 获取PackageInfo

使用PackageManager的getPackageArchiveInfo接口,可以根据传入的插件APK绝对路径,获取到一个未安装APK的PackageInfo信息:

PackageInfo packageInfo=PackageManager.getPackageArchiveInfo(path);

1.2 拿到插件的四大组件列表

取到了PackageInfo,也就意味着已经取到了APK中的四大组件了,我们定义一个数据结构存储起来。

class ComponentList {

// Activity列表

finalHashMap<String, ActivityInfo> mActivities = new HashMap<>();

// Provider列表

finalHashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();

// Service列表

finalHashMap<String, ServiceInfo> mServices = new HashMap<>();

// Receiver列表(系统中直接用ActivityInfo来承载一个Receiver了)

finalHashMap<String, ActivityInfo> mReceivers = new HashMap<>();

}

1.3 生成进程映射表

生成进程映射表,有两种方式:

静态映射:

通过在插件APK的AndroidMainfext.xml文件中手工配置进程映射文件:

<meta-data

android:name="process_map"

android:value="[{'from':'com.qihoo360.replugin.sample.demo1:bg','to':'$p0'}]"/>

宿主在加载插件时,会读取插件AndroidMainfext的meta-data配置,根据配置调整其进程标识。

动态映射:

动态的将插件APK的AndroidMainfext.xml文件中的自定义进程标签映射到宿主。

1)int processIndex = 插件中自定义进程数 % 宿主中坑位进程数。

2)String process = 宿主进程坑位数组[processIndex]

具体过程如下:

RePlugin中如何打开插件中的自定义进程Activity

1.4 调整进程名字

根据以上步骤取到的四大组件列表,以及进程映射表,调整插件中组件的进程名称,四大组件依次调整:

//Activity

adjust(HashMap<String, String> processMap, activityMap);

//Service

adjust (HashMap<String, String> processMap, serviceMap);

//provider

adjust (HashMap<String, String> processMap, providerMap);

//receiver

adjust (HashMap<String, String> processMap, receiverMap);

具体如何调整?

来看Android中描述四大组件信息的类图:

RePlugin中如何打开插件中的自定义进程Activity

从上图可以看出,只要我们修改ComponentList数据结构中存储的,四大组件信息Map中的,ComponentInfo的 processName字段即可修改该组件的进程名字。

运行到这里,当插件被加载时,在内存中,插件中四大组件的android:process=”:xxxProcess”标签(宿主并不认识),就已经被完全替换成android:process=”:p0”(宿主认识)了。

接下来会介绍,这些被修改后的坑位进程是如何被使用的。

2 启动进程

宿主编译期间,会通过Gradle脚本,在AndroidMainfest.xml中注入了3个Provider(在processXXXXManifestTask 被执行后):

<provider android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP0/1/2"

android:exported="false"

android:process=":p0"

android:authorities="{packageName}.loader.p.mainN100"/>

同时,宿主中也内置了ProcessPitProviderP0,ProcessPitProviderP1,ProcessPitProviderP2三个Provider。

我们想启动P0进程,只需要执行:

getContentResolver().insert(ProcessPitProviderP0Uri, values),即可借助Provider的同步特性,拉活ProcessPitProviderP0所在的进程。

为什么我们尝试去查P0进程中的一个Provider,就能把P0进程唤醒呢?这个问题要从系统源码中寻找答案。

我们需要从Application对象的创建过程开始看起,在ActivityThread的main()方法中,我们看这个逻辑:

RePlugin中如何打开插件中的自定义进程Activity

ActivityThread#attach()方法,一开始就做了以下事情:

RePlugin中如何打开插件中的自定义进程Activity

其中,红框中的IActivityManager mgr,就是AMS留在APP进程中的代理,使用这个代理对象,从APP进程调用到AMS所在进程,AMS中具体的实现逻辑位于ActivityManagerService中的attachApplicationLocked ()方法(不同Android版本可能略有不同):

RePlugin中如何打开插件中的自定义进程Activity

通过上图,可以看到,首先,会通过pid取到ProcessRecord对象,并为ProcessRecord对象填充必要数据。

接下来,调用PMS,获取该应用的Provider列表:

RePlugin中如何打开插件中的自定义进程Activity

然后,通过APP进程留在AMS中的代理IApplicationThread thread,调用APP进程中的bindApplication()方法:

RePlugin中如何打开插件中的自定义进程Activity

如图所示,processName,ApplicationInfo,providers,等信息,也跨进程传递给了APP进程。

APP进程收到调用后,会通过Handler(H),从当前所在的Binder线程,分发到UI线程:

RePlugin中如何打开插件中的自定义进程Activity

在UI线程中,真正处理这个消息的是handleBindApplication()方法:

RePlugin中如何打开插件中的自定义进程Activity

在handleBindApplication()方法中,会真正的创建Application,我们来看详细过程:

1)获取LoadedApk(ClassLoader和Resource都会随之被获取到)

RePlugin中如何打开插件中的自定义进程Activity

2)创建ContextImpl

RePlugin中如何打开插件中的自定义进程Activity

3)makeApplication

RePlugin中如何打开插件中的自定义进程Activity

makeApplication 中,通过以上拿到的ClassLoader,加载Application的class,并且创建Application的实例,随后调用Application的attach()方法,在 attach()方法中,将会回调Application的attachBaseContext()接口。

RePlugin中如何打开插件中的自定义进程Activity

4)installContentProviders(),即:在本地进程安装所有Providers ,并且把这些Providers的Binder,publish给AMS。

RePlugin中如何打开插件中的自定义进程Activity

仔细来看installProvider():

A)首先,会在本地进程中,依次安装每一个ContentProivder,即:加载Provider的class,创建其实例,然后通过ContentProivder的attachInfo方法,调用ContentProivder的onCreate()接口。

B)然后,为每一个Provider创建一个ContentProviderHolder封装对象,这些封装对象,将会一起发布给AMS。

5)借助Instrumentation ,回调Application 的 onCreate() 接口。

分析到这里,我们就可以看出来了:

从APP进程的角度来看,ContentProivder的install过程,是介于Application#attachBasecontext()和Application#onCreate()之间的,ContentProivder的初始化,是和Application的初始化耦合在一起的。

换句话说,当我们尝试去query一个进程中的ContentProivder时,这个进程会被同步拉活,有了这个机制,当我们想启动一个进程时,就可以先同步拉活它,然后通过Binder,在该进程中执行必要的逻辑了。

3 进程回收

每一个坑位进程启动后,都会同时启动一个定时任务,每20秒轮循一次,发现当前进程中没有组件正在运行,则自杀(可以参考AMS中进程管理逻辑)。

如何定义没有组件正在运行?须同时满足以下条件:

{

当前持有activities==0

当前持有services ==0

当前持有binders ==0

}

有一点需要我们注意,就是,我们前面提到插件进程的动态分配过程。

例如:插件中 A 进程和 B 进程,都被动态分配到 P0进程中,这时候 A 进程做完了事情,主动调用了自杀逻辑,就会连累 B 进程也被意外杀死。

这种情况,可以通过编译期字节码修改来解决,即:在插件编译期间,通过Gradle 插件+字节码修改技术(如ASM,Javassist,AspectJ等技术),把System.exit(),android.os.Process.killProcess(pid)等能杀死进程的方法调用换成我们自己的实现逻辑,如:System2.exit2(),当插件中某个自定义进程尝试自杀时,通过计数器来判断是否可以真正的自杀。

4 启动自定义进程后的Activity

4.1 Activity坑位信息

前面,已经介绍了如何把插件中的自定义进程,映射为宿主中的预埋占坑进程,并且介绍了这些自定义进程的启动过程,接下来就来看看,具体的Activity启动过程。

先来看预埋的Activity坑位,宿主中的坑位信息如下图所示(UI进程和自定义进程P0,P1,P2中都包含一组完整坑位):

RePlugin中如何打开插件中的自定义进程Activity

每个进程中的一组Activity坑位:

RePlugin中如何打开插件中的自定义进程Activity

例如,以下Activity坑位代表的就是:运行在”:p2”进程中,taskAffinity=”:t3”的,launchMode=”singleTask”的坑位Activity,AMS将直接与它交互。

RePlugin中如何打开插件中的自定义进程Activity

4.2 启动过程

自定义进程Activity,启动流程如下图所示:

RePlugin中如何打开插件中的自定义进程Activity

1) 在宿主中或插件中,通过RePlugin.startActivity()启动插件中的Activity,会先入常驻进程(或者叫插件管理进程)。

2) 在插件管理进程中,插件管理器,会为该Activity分配一个目标进程(参考上述介绍的进程分配过程)。

3) 然后,同步的启动该目标进程(参考上述介绍的进程启动过程)。

4) 在插件管理进程中,通过Binder调用,使用目标进程留在插件管理进程中的代理,在目标进程中为该Activity动态分配坑位(每个进程中独立管理自己的Activity坑位信息,此时该坑位Activity还没有被AMS打开)。

5) 把在目标进程中分配好的Activity坑位信息返回给调用方(Step1)。

6) 调用者获取到了一个坑位Activity,然后会把这个坑位Activity(带着自定义进程标签),会交给AMS,由于AMS去启动。

7) AMS启动Activity是一个并不存在的坑位Activity,由于宿主的ClassLoader已经在进程启动之初(Application#attachBaseContext()接口中),被修改为我们自定义过的ClassLoader了,当类加载器去loadClass时,如果发现当前正在被load的Class是一个坑位Activity,会自动分发给指定插件的ClassLoader去加载,这样一来,就既绕过了AMS,也绕过了ClassLoader,实现了:打开一个并未安装的Activity。

到现在为止,本文之初提到的,插件中的独立进程Activity,就被我们打开了。在后续的系列文章中,我们将继续介绍Activity打开过程中涉及到的ClassLoader、Resources、Layout相关处理、Activity坑位分配算法,以及其他技术细节。

白城汽车新闻网一直为网友的需求而努力相关推荐

  • RePlugin中如何打开插件中的自定义进程Activity-plug

    plug,例如,插件中存在这样一个Activity,需要运行到“:image1”进程中:1进程映射插件中的这个进程标签,宿主肯定是不认识的,所以,第一件要做的事情,就是得让宿主认识这个进程,只有认识后,才能进一步的启动、管理它。

    汽车资讯