0%

RePlugin原理解析

概述

RePlugin是360在2017年推出的插件化框架,其目的是让尽可能多的模块变成插件。RePlugin与其他插件化的特色在于它只Hook住了ClassLoader,One Hook这个坚持,最大程度保证了稳定性、兼容性和可维护性。

RePlugin项目地址:RePlugin,不过RePlugin上次更新已经是2019年7月了,且其没有支持androidx,网上有一个androidx版本的RePlugin,地址:replugin-androidx。虽然RePlugin已经停更,但是还是挺值得研究一下的。

详细使用教程可以看官网WIKI以及Sample示例

注意事项

  1. 宿主开启了MultiDex后,可能会导致打包时报错:Cannot fit requested classes in the main-dex file,当我把gradle插件版本从3.4.2升级到3.5.2后,打包成功;
  2. 插件工程replugin-plugin-lib不支持高版本的gradle插件,我测试的3.4.2可以支持,如果用了高版本的gradle插件,则可以通过手动让BaseActivity继承自PluginXXXActivity;
  3. 不支持androidx;
  4. gradle在编译时会根据assets/plugins目录下的jar文件生成内置插件的json文件,当内置插件有了修改时,最好先clean project。
  5. RePlugin版本目前最新是2.3.3,WIKI上的示例使用的不是最新版本,建议在项目中使用最新版本。
  6. 对于replugin-plugin-gradle插件在高版本gradle或者AndroidX上没有在编译器替换相关类的问题,可以参考下面关于replugin-plugin-gradle插件源码的解析去临时解决这个问题(不包括在gradle更高版本上某些编译报错的问题)。

项目结构

replugin-host-gradle

对应com.qihoo360.replugin:replugin-host-gradle:xxx依赖,主要负责在主程序的编译期中生产各类文件:

  • 扫描内置的插件目录assets/plugins目录,解析插件文件生成包含文件名、包名、版本、路径的plugins-builtin.json文件,这个文件的路径在build/intermediates/merged_assets/debug/out/plugins-builtin.json目录:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [
    {
    "high": null,
    "frm": null,
    "ver": 104,
    "low": null,
    "pkg": "com.qihoo360.replugin.sample.demo1",
    "path": "plugins/demo1.jar",
    "name": "demo1"
    },
    {
    "high": null,
    "frm": null,
    "ver": 1,
    "low": null,
    "pkg": "com.hearing.plugin1",
    "path": "plugins/plugin1.jar",
    "name": "plugin1"
    }
    ]
  • 根据用户的配置文件,生成HostBuildConfig类,位于build/generated/source/buildConfig/debug/com/qihoo360/replugin/gen/RePluginHostConfig.java,方便插件框架读取并自定义其属性,如:进程数、各类型占位坑的数量、是否使用AppCompat库、Host版本、pulgins-builtin.json文件名、内置插件文件名等。

  • 自动生成带Activity,Service,ContentProvider坑位的 AndroidManifest.xml文件,文件中带有如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <activity 
    android:theme="@style/Theme.AppCompat"
    android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1STTS0"
    android:exported="false"
    android:screenOrientation="portrait"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize" />

    <provider
    android:name="com.hearing.host.loader.p.Provider1"
    android:exported="false"
    android:process=":loader1"
    android:authorities="com.hearing.host.loader.p.pr1" />
    <service
    android:name="com.hearing.host.loader.s.Service1"
    android:exported="false"
    android:process=":loader1" />

    <provider
    android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP0"
    android:exported="false"
    android:process=":p0"
    android:authorities="com.hearing.host.loader.p.mainN100" />

replugin-host-library

对应com.qihoo360.replugin:replugin-host-lib:xxx依赖,是一个Java工程,由主程序负责引入,是RePlugin的核心工程,负责初始化、加载、启动、管理插件等。

replugin-plugin-gradle

对应com.qihoo360.replugin:replugin-plugin-gradle:xxx ,是一个Gradle插件,由插件负责引入,主要负责在插件的编译期中,使用Transfrom API和Javassist实现了编译期间动态的修改字节码文件,配置插件打包相关信息;动态替换插件工程中的继承基类,如下,修改Activity的继承、动态的将插件apk中调用LocalBroadcastManager的地方修改为Replugin中的PluginLocalBroadcastManager调用,动态修改ContentResolver和ContentProviderClient的调用修改成Replugin调用,动态的修改插件工程中所有调用Resource.getIdentifier方法的地方等。

1
2
3
4
5
6
7
8
9
10
11
/* LoaderActivity 替换规则 */
def private static loaderActivityRules = [
'android.app.Activity' : 'com.qihoo360.replugin.loader.a.PluginActivity',
'android.app.TabActivity' : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
'android.app.ListActivity' : 'com.qihoo360.replugin.loader.a.PluginListActivity',
'android.app.ActivityGroup' : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
'android.preference.PreferenceActivity' : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
'android.app.ExpandableListActivity' : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
]

replugin-plugin-library

对应com.qihoo360.replugin:replugin-plugin-lib:xxx依赖,是一个Java工程,由插件端负责引入,主要提供通过“Java反射”来调用主程序中RePlugin Host Library的相关接口,并提供“双向通信”的能力,以及各种基类Activity等。

其中的RePlugin、RePluginInternal、PluginServiceClient都是反射宿主App:replugin-host-library中的RePlugin、RePluginInternal、PluginServiceClient类方法。

ClassLoader

RePluginClassLoader

RePluginClassLoader是宿主的ClassLoader,继承自PathClassLoader,其构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final ClassLoader mOrig;
private Method findResourceMethod;
private Method findResourcesMethod;
private Method findLibraryMethod;
private Method getPackageMethod;

public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {

// 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来
// 但我们最终不用它,而是拷贝所有的Fields
super("", "", parent);
mOrig = orig;

// 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList)
// 注意,这里用的是“浅拷贝”
copyFromOriginal(orig);
initMethods(orig);
}

在构造方法中,将原ClassLoader中的重要变量浅拷贝到RePluginClassLoader中,用于欺骗系统还处于原Loader,并且从原Loader中反射出常用方法,用于重载方法中使用。

RePluginClassLoader的初始化在RePlugin初始化时进行:

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
/**
* 对宿主的HostClassLoader做修改。这是RePlugin中唯一需要修改宿主私有属性的位置了
*/
public class PatchClassLoaderUtils {
public static boolean patch(Application application) {
try {
// 获取Application的BaseContext (来自ContextWrapper)
Context oBase = application.getBaseContext();
if (oBase == null) {
return false;
}

// 获取mBase.mPackageInfo
// 1. ApplicationContext - Android 2.1
// 2. ContextImpl - Android 2.2 and higher
// 3. AppContextImpl - Android 2.2 and higher
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
if (oPackageInfo == null) {
return false;
}

// mPackageInfo的类型主要有两种:
// 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
// 2. android.app.LoadedApk - Android 2.3.3 and higher
// 获取mPackageInfo.mClassLoader
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
if (oClassLoader == null) {
return false;
}

// 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);

// 将新的ClassLoader写入mPackageInfo.mClassLoader
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);

// 设置线程上下文中的ClassLoader为RePluginClassLoader
// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
Thread.currentThread().setContextClassLoader(cl);
} catch (Throwable e) {
e.printStackTrace();
return false;
}
return true;
}
}

此处patch的原理可以参考Android Application启动的原理

RePluginClassLoader中主要是重载了loadClass方法,它首先会尝试从PluginDexClassLoader中加载,加载失败才会使用原ClassLoader加载,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> c = null;
c = PMF.loadClass(className, resolve);
if (c != null) {
return c;
}
try {
c = mOrig.loadClass(className);
return c;
} catch (Throwable e) {
}
return super.loadClass(className, resolve);
}

PluginDexClassLoader

PluginDexClassLoader是插件的ClassLoader,继承自DexClassLoader,构造时持有了宿主的ClassLoader,其构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
private final ClassLoader mHostClassLoader;
private static Method sLoadClassMethod;
private String mPluginName;

public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
mPluginName = pi.getName();
installMultiDexesBeforeLollipop(pi, dexPath, parent);
mHostClassLoader = RePluginInternal.getAppClassLoader();
initMethods(mHostClassLoader); // 通过从宿主ClassLoader中获取loadClass方法
}

其初始化是在调用插件的相关组件时,通过Loader类的loadDex方法实例化的,每一个插件都会有一个PluginDexClassLoader,代码如下:

1
2
3
4
5
6
7
8
9
10
11
final boolean loadDex(ClassLoader parent, int load) {
// ...
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
// ...
// 缓存表:ClassLoader
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
}
// ...
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
}

PluginDexClassLoader从宿主ClassLoader中反射获取loadClass方法,当自己的loadClass方法找不到类时,从宿主Loader中加载,代码如下:

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
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
// 插件自己的Class。从自己开始一直到BootClassLoader,采用正常的双亲委派模型流程,读到了就直接返回
Class<?> pc = null;
ClassNotFoundException cnfException = null;
try {
pc = super.loadClass(className, resolve);
if (pc != null) {
return pc;
}
} catch (ClassNotFoundException e) {
// Do not throw "e" now
cnfException = e;

if (PluginDexClassLoaderPatch.need2LoadFromHost(className)) {
try {
return loadClassFromHost(className, resolve);
} catch (ClassNotFoundException e1) {
// Do not throw "e1" now
cnfException = e1;
}
} else {
}
}

// 若插件里没有此类,则会从宿主ClassLoader中找,找到了则直接返回
// 注意:需要读取isUseHostClassIfNotFound开关。默认为关闭的。可参见该开关的说明
if (RePlugin.getConfig().isUseHostClassIfNotFound()) {
try {
return loadClassFromHost(className, resolve);
} catch (ClassNotFoundException e) {
// Do not throw "e" now
cnfException = e;
}
}

// At this point we can throw the previous exception
if (cnfException != null) {
throw cnfException;
}
return null;
}

private Class<?> loadClassFromHost(String className, boolean resolve) throws ClassNotFoundException {
Class<?> c;
try {
c = (Class<?>) sLoadClassMethod.invoke(mHostClassLoader, className, resolve);
} catch (IllegalAccessException e) {
// Just rethrow
throw new ClassNotFoundException("Calling the loadClass method failed (IllegalAccessException)", e);
} catch (InvocationTargetException e) {
// Just rethrow
throw new ClassNotFoundException("Calling the loadClass method failed (InvocationTargetException)", e);
}
return c;
}

思考

关于这种 Hook 系统 ClassLoader 的方式,是否可以修改成给宿主 PathClassLoader 加一个 parent ClassLoader 呢?这样根据 “双亲委派” 的逻辑,加上去的这个 parent ClassLoader 理论上也是能完成 RePlugin 想要做的事。

相关类

概述

  • RePlugin:RePlugin的对外入口类,提供install、uninstall、preload、startActivity、fetchPackageInfo、fetchComponentList,fetchClassLoader等等统一的方法入口,用户操作的主要是它。
  • RePlugin.App:RePlugin中的内部类,针对Application的入口类,所有针对插件Application的调用应从此类开始和初始化。
  • PMF:框架和主程序接口代码。
  • PmBase:在RePlugin中常用mPluginMgr变量表示,可以看作插件管理者。初始化插件、加载插件等一般都是从它开始。
  • PmHostSvc:class PmHostSvc extends IPluginHost.Stub,运行在Persistent进程,其它进程通过Binder与Persistent进程通信。
  • PluginManagerServer:插件管理器。用来控制插件的安装、卸载、获取等。运行在常驻进程中,其内有一个IPluginManagerServer mStub属性。
  • PluginContainers:插件容器管理中心。
  • PluginCommImpl:负责宿主与插件、插件间的互通,可通过插件的Factory直接调用,也可通过RePlugin来跳转,包含各种本地接口实现,如startActivity,getActivityInfo,loadPluginActivity等。
  • PluginLibraryInternalProxy:内部实现了真正startActivity的逻辑、还有插件Activity生命周期的接口。plugin-library中,通过“反射”调用的内部逻辑,以及host-library中直接调用startActivity等的逻辑实现。

PMF.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PmBase sPluginMgr
Context sContext
setApplicationContext()
getApplicationContext()
init()
callAppCreate()
callAttach()
addBuiltinModule()
getLocal()
getInternal()
loadClass()
forward()
dump()
stopService()

PmBase.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class PmBase {
private final Context mContext;
private final HashSet<String> mContainerActivities = new HashSet<String>();
private final HashSet<String> mContainerProviders = new HashSet<String>();
private final HashSet<String> mContainerServices = new HashSet<String>();
private ClassLoader mClassLoader;
/**
* 所有插件
*/
private final Map<String, Plugin> mPlugins = new ConcurrentHashMap<>();

/**
* 动态的类查找表
*/
private final HashMap<String, DynamicClass> mDynamicClasses = new HashMap<String, DynamicClass>();
private String mDefaultPluginName;
private Plugin mDefaultPlugin;
private PmHostSvc mHostSvc;
PluginProcessPer mClient;
PluginCommImpl mLocal;
PluginLibraryInternalProxy mInternal;
}

PmHostSvc.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PmHostSvc extends IPluginHost.Stub {
Context mContext;
PmBase mPluginMgr;
PluginServiceServer mServiceMgr;
PluginManagerServer mManager;
private boolean mNeedRestart;
PluginReceiverProxy mReceiverProxy;

@Override
public IPluginManagerServer fetchManagerServer() throws RemoteException {
return mManager.getService();
}

// ...
}

IPluginHost.aidl

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
64
65
66
67
68
69
70
interface IPluginHost {
void installBinder(String name, in IBinder binder);
IBinder fetchBinder(String name);
long fetchPersistentCookie();
IPluginClient startPluginProcess(String plugin, int process, inout PluginBinderInfo info);
String attachPluginProcess(String process, int index, in IBinder binder, String def);
List<PluginInfo> listPlugins();
void regActivity(int index, String plugin, String container, String activity);
void unregActivity(int index, String plugin, String container, String activity);
void regService(int index, String plugin, String service);
void unregService(int index, String plugin, String service);
void regPluginBinder(in PluginBinderInfo info, IBinder binder);
void unregPluginBinder(in PluginBinderInfo info, IBinder binder);
/**
* 注册某插件下所有静态声明的的 receiver 到常驻进程
*/
void regReceiver(String plugin, in Map receiverFilterMap);
void unregReceiver();
/**
* 插件收到广播
*
* @param plugin 插件名称
* @param receiver Receiver 名称
* @param Intent 广播的 Intent 数据
*/
void onReceive(String plugin, String receiver, in Intent intent);
int sumBinders(int index);
void updatePluginInfo(in PluginInfo info);
PluginInfo pluginDownloaded(String path);
boolean pluginUninstalled(in PluginInfo info);
boolean pluginExtracted(String path);
oneway void sendIntent2Process(String target, in Intent intent);
oneway void sendIntent2Plugin(String target, in Intent intent);
void sendIntent2ProcessSync(String target, in Intent intent);
void sendIntent2PluginSync(String target, in Intent intent);
boolean isProcessAlive(String name);
IBinder queryPluginBinder(String plugin, String binder);
/**
* 根据 Inent 查询所有插件中的与之匹配的 Receivers
*/
List queryPluginsReceiverList(in Intent intent);
/**
* 获取“全新Service管理方案”在Server端的服务
* Added by Jiongxuan Zhang
*/
IPluginServiceServer fetchServiceServer();
/**
* 获取 IPluginManagerServer(纯APK方案使用)的插件服务
* Added by Jiongxuan Zhang
*/
IPluginManagerServer fetchManagerServer();
/**
* 根据 taskAffinity,判断应该取第几组 TaskAffinity
* 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中
* Added by hujunjie
*/
int getTaskAffinityGroupIndex(String taskAffinity);
/**
* 通过进程名来获取PID
*/
int getPidByProcessName(String processName);
/**
* 通过PID来获取进程名
*/
String getProcessNameByPid(int pid);
/**
* dump详细的运行时信息
*/
String dump();
}

IPluginManagerServer.aidl

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
interface IPluginManagerServer {

/**
* 安装一个插件
* <p>
* 注意:若为旧插件(p-n开头),则应使用IPluginHost的pluginDownloaded方法
*
* @return 安装的插件的PluginInfo对象
*/
PluginInfo install(String path);

/**
* 卸载一个插件
* <p>
* 注意:只针对“纯APK”插件方案
*
* @param info 插件信息
* @return 是否成功卸载插件?
*/
boolean uninstall(in PluginInfo info);

/**
* 加载插件列表,方便之后使用
* <p>
* TODO 这里只返回"新版插件",供PmBase使用。将来会合并
*
* @return PluginInfo的列表
*/
List<PluginInfo> load();

/**
* 更新所有插件列表
*
* @return PluginInfo的列表
*/
List<PluginInfo> updateAll();

/**
* 设置isUsed状态,并通知所有进程更新
*
* @param pluginName 插件名
* @param used 是否已经使用
*/
void updateUsed(String pluginName, boolean used);

/**
* 获取正在运行的插件列表
*
* @return 正在运行的插件名列表
*/
PluginRunningList getRunningPlugins();

/**
* 插件是否正在运行?
*
* @param pluginName 插件名
* @param process 指定进程名,如为Null则表示查所有
* @return 是否在运行?
*/
boolean isPluginRunning(String pluginName, String process);

/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param list 正在运行的插件名列表
*/
void syncRunningPlugins(in PluginRunningList list);

/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param processName 进程名
* @param pluginName 正在运行的插件名
*/
void addToRunningPlugins(String processName, int pid, String pluginName);

/**
* 获取正在运行此插件的进程名列表
*
* @param pluginName 要查询的插件名
* @return 正在运行此插件的进程名列表。一定不会为Null
*/
String[] getRunningProcessesByPlugin(String pluginName);
}

PluginManagerServer.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
/**
* 插件管理器。用来控制插件的安装、卸载、获取等。运行在常驻进程中 <p>
* 补充:涉及到插件交互、运行机制有关的管理器,在IPluginHost中 <p>
*/
public class PluginManagerServer {
private IPluginManagerServer mStub;

public PluginManagerServer(Context context) {
mContext = context;
mStub = new Stub();
}

public IPluginManagerServer getService() {
return mStub;
}

private PluginInfo installLocked(String path) {
// ...
}

private List<PluginInfo> loadLocked() {
// ...
}

// ...

private class Stub extends IPluginManagerServer.Stub {

@Override
public PluginInfo install(String path) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.installLocked(path);
}
}

@Override
public List<PluginInfo> load() throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.loadLocked();
}
}

// ...
}
}

PluginManagerProxy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 用于各进程(包括常驻自己)缓存 PluginManagerServer 的Binder实现
*/
public class PluginManagerProxy {
private static IPluginManagerServer sRemote;

/**
* 连接到常驻进程,并缓存IPluginManagerServer对象
*/
public static void connectToServer(IPluginHost host) throws RemoteException {
if (sRemote != null) {
return;
}
sRemote = host.fetchManagerServer();
}

// ...
}

相关概念

唯一Hook

在应用启动的时候,Replugin使用RepluginClassLoader将系统的PathClassLoader替换掉,并且只篡改了loadClass方法的行为,用于加载插件的类。每一个插件都会有一个PluginDexClassLoader,RepluginClassLoader会调用插件的PluginDexClassLoader来加载插件中的类与资源。

UI/Persistent进程

Replugin启动时会默认启动两个进程,一个是UI进程,一个是Persistent进程(常驻进程),在IPluginManager接口中定义了两个常量PROCESS_UI和PROCESS_PERSIST来表示这两个进程。

Replugin默认会使用常驻进程作为Server端,其他插件进程和宿主进程全部属于Client端。当然如果修改不使用常驻进程,那么宿主的主进程将作为插件管理进程,而不管是使用宿主进程还是使用默认的常驻进程,Server端其实就是创建了一个运行在该进程中的Provider,通过Provider的query方法返回了Binder对象来实现多进程直接的的沟通和数据共享,或者说是插件之间和宿主之间沟通和数据共享,插件的安装,卸载,更新,状态判断等全部都在这个Server端完成。

  • UI进程就是程序的主进程。
  • Persistent进程是一个服务器进程,默认用:GuardService来标识。所有其他的进程在启动组件的时候都会通过PmHostSvc与这个进程通信,以下是Persistent进程中运行的两个重要服务:
    • PluginManagerServer 用于插件的管理,比如加载插件,更新插件信息,签名验证,版本检查,插件卸载等
    • PluginServiceServer 用于Service的启动调度等工作

坑位

坑位的作用需要与RepluginClassLoader配合实现,所谓坑位就是预先在Host的Manifest中注册的一些组件(Activity, Service, Content Provider,没有Broadcast Receiver),这些坑位组件的代码都是由gradle插件在编译时生成的。

在启动插件的组件时,会用这些坑位去替代要启动的组件,并且会建立一个坑位与真实组件之间的对应关系(用ActivityState表示),然后在加载类的时候RepluginClassLoader会根据前文提到的被篡改过的行为偷偷使用插件的PluginDexClassLoader加载要启动的真实组件类,骗过了系统,这就是唯一hook点的作用。​

RePlugin初始化

Host在启动的时候会先进行UI进程的初始化工作,但在进行到中途的时候会巧妙的将Persistent进程启动起来,以提供服务,不然UI进程将无法正常启动起来,因为有很多东西是运行在Persistent进程的。

初始化

入口位置在RePluginApplication#attachBaseContext(),接着调用了RePlugin.App.attachBaseContext()方法:

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
// RePlugin.App.class
public static void attachBaseContext(Application app, RePluginConfig config) {
if (sAttached) {
return;
}

// 缓存Application
RePluginInternal.init(app);
sConfig = config;
// 1.获取pn插件安装目录,即context.getFilesDir()
// 2.判断如果RePluginCallbacks为空创建一个
// 3.判断RePluginEventCallbacks为空创建一个
sConfig.initDefaults(app);

// 初始化进程间通信辅助类,因为不同进程的创建会使attachBaseContext方法走多次,
// IPC.init中会标记当前进程类型,将会影响下面的代码在不同进程中的逻辑
IPC.init(app);

// 初始化HostConfigHelper(通过反射HostConfig来实现),一定要在IPC类初始化之后才使用
HostConfigHelper.init();

AppVar.sAppContext = app;

// PluginStatusController用来管理插件的运行状态:正常运行、被禁用等情况
PluginStatusController.setAppContext(app);

PMF.init(app); // 初始化当前进程,初始化PmBase以及Hook系统的PathClassLoader
PMF.callAttach(); // 将插件与当前进程关联,如果是在单独的进程中运行插件,则会加载并运行插件

sAttached = true;
}

IPC.init方法主要就是通过proc文件获取当前进程名、进程id和宿主包名,然后设置常驻进程的名称,最后标记当前进程是否是ui进程和是不是常驻进程,源码在进程管理部分会给出。

PMF.init(app)方法如下:

1
2
3
4
5
6
7
8
9
10
11
public static final void init(Application application) {
setApplicationContext(application); // 保持对Application的引用

PluginManager.init(application);

sPluginMgr = new PmBase(application);
sPluginMgr.init();

// 在上面已经解析过了,此处是Hook系统的PathClassLoader
PatchClassLoaderUtils.patch(application);
}

PluginManager.init方法会获取当前进程的index,这个index是RePlugin自定义的,其取值在下面会讲到:

1
2
3
4
5
6
static final void init(Context context) {
// 初始化操作,方便后面执行任务,不必担心Handler为空的情况
Tasks.init();
sUid = android.os.Process.myUid();
sPluginProcessIndex = evalPluginProcess(IPC.getCurrentProcessName());
}

PmBase和它内部引用的其他对象掌握了Replugin中很多重要的功能,例如:分配坑位、初始化插件信息、Clent端连接Server端、加载插件、更新插件、删除插件、等等。其构造方法如下:

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
PmBase(Context context) {
mContext = context;
// isPluginProcess方法参考进程管理部分
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
String suffix;
// 如果是ui进程,设置suffix = N1
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
suffix = "N1";
} else {
// 后缀值0或1
suffix = "" + PluginManager.sPluginProcessIndex;
}
// HashSet<String> mContainerProviders;
// CONTAINER_PROVIDER_PART = .loader.p.Provider
// 结果 = 包名.loader.p.ProviderN1 或者 包名.loader.p.Provider0 或者 包名.loader.p.Provider1
mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);
// 结果 = 包名.loader.s.ServiceN1 或者 包名.loader.s.Service0 或者包名.loader.s.Service1
mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
}

// 创建了PluginProcessPer类,代表了当前Clent进程
mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);
// 创建了PluginCommImpl类,负责宿主与插件、插件间的互通,可通过插件的Factory直接调用,也可通过RePlugin来跳转
mLocal = new PluginCommImpl(context, this);
// Replugin框架中内部逻辑使用的很多方法都在这里,包括插件中通过“反射”调用的内部逻辑
mInternal = new PluginLibraryInternalProxy(this);
}

PmBase的构造主要做了4件事件:

  • 根据当前进程类型,拼接坑位provider和Service所对应名称并存入不同的HashSet中,PmBase类中处理保存了Provider、Service、Activitiy的坑位信息,这些名字全部都是Replugin在编译的时候在AndroidManifest.xml中声明的坑位名字:mContainerActivities,mContainerProviders,mContainerServices。
  • 创建了一个叫PluginProcessPer的类,它是一个Binder对象,它代表了“当前Clent端”,使用它来和Server端进行通信,这个类的构造中有两个类,一个是PluginContainers,用来管理Activity坑位信息的容器,初始化了多种不同启动模式和样式Activity的坑位信息。另一个PluginServiceServer类,这个类是Replugin中的一个核心类,主要负责了对Service的提供和调度工作,例如startService、stopService、bindService、unbindService全部都由这个类管理。
  • 创建了一个叫PluginCommImpl的类,负责宿主与插件、插件间的互通,很多对提供方法都经过这里中转或者最终调到这里。
  • 创建了一个PluginLibraryInternalProxy类,Replugin框架中内部逻辑使用的很多方法都在这里,包括插件中通过“反射”调用的内部逻辑如PluginActivity类的调用、Factory2等。

PmBase.init()方法会根据不同进程进行不同的操作,其逻辑如下:

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
void init() {
if (HostConfigHelper.PERSISTENT_ENABLE) {
// (默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client
if (IPC.isPersistentProcess()) {
// 初始化“Server”所做工作
initForServer();
} else {
// 连接到Server
initForClient();
}
} else {
// “UI进程”作为插件管理进程(唯一进程),则UI进程既可以作为Server也可以作为Client
if (IPC.isUIProcess()) {
// 1. 尝试初始化Server所做工作,
initForServer();

// 2. 注册该进程信息到“插件管理进程”中
// 注意:这里无需再做 initForClient,因为不需要再走一次Binder
PMF.sPluginMgr.attach();
} else {
// 其它进程?直接连接到Server即可
initForClient();
}
}

// 从mPlugins中将所有插件信息取出,保存到PLUGINS中,PLUGINS是一个HashMap,保存的key是包名或者别名,value是PluginInfo
PluginTable.initPlugins(mPlugins);
}

Persistent进程启动

initForServer

在UI进程启动流程中,可以看到如果是Persistent进程,则走如下逻辑:

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
private final void initForServer() {
// 继承于IPluginHost.Stub,是一个Binder对象
// 这个类可以理解成是我们的Server端,它直接或间接参与了Server端要做的所有事情
mHostSvc = new PmHostSvc(mContext, this);
PluginProcessMain.installHost(mHostSvc);
StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);

// 兼容即将废弃的p-n方案
mAll = new Builder.PxAll();
// 搜索所有本地插件和V5插件信息,并添加进Builder集合中就是mAll字段,然后删除一些不符合规则的插件信息
// 这里搜索了所以本地插件,也就是放在assest中的插件,是通过插件自动生成的json文件来扫描的
// v5是通过context.getDir路径来扫描的
Builder.builder(mContext, mAll);
// 将刚扫描的本地插件封装成Plugin添加进mPlugins中,mPlugins代表所有插件的集合
refreshPluginMap(mAll.getPlugins());

try {
List<PluginInfo> l = PluginManagerProxy.load();
if (l != null) {
// 将"纯APK"插件信息并入总的插件信息表中,方便查询
refreshPluginMap(l);
}
} catch (RemoteException e) {
}
}

PmHostSvc的构造方法:

1
2
3
4
5
6
7
8
class PmHostSvc extends IPluginHost.Stub {
PmHostSvc(Context context, PmBase packm) {
mContext = context;
mPluginMgr = packm;
mServiceMgr = new PluginServiceServer(context);
mManager = new PluginManagerServer(context);
}
}

PluginProcessMain.installHost

在Persistent进程中会通过PluginProcessMain.installHost(mHostSvc)连接到IPluginManagerServer,但因为IPluginManagerServer就运行在当前进程,因此这里不会进行Binder通信,而是直接调用PmHostSvc端fetchManagerServer方法。

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
private static IPluginHost sPluginHostLocal;

static final void installHost(IPluginHost host) {
sPluginHostLocal = host;
// 连接到插件化管理器的服务端
try {
PluginManagerProxy.connectToServer(sPluginHostLocal);
} catch (RemoteException e) {
}
}

public static void connectToServer(IPluginHost host) throws RemoteException {
if (sRemote != null) {
return;
}
// IPluginManagerServer sRemote
sRemote = host.fetchManagerServer();
}

// 上面的host是PmHostSvc对象,继承自IPluginHost.Stub,IPluginHost是一个AIDL文件
// 可以看到PmHostSvc中的fetchManagerServer方法
@Override
public IPluginManagerServer fetchManagerServer() throws RemoteException {
// mManager = new PluginManagerServer(context);
return mManager.getService();
}

接下来看看PluginManagerServer#getService方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//PluginManagerServer类
public class PluginManagerServer {
private IPluginManagerServer mStub;

public PluginManagerServer(Context context) {
mContext = context;
mStub = new Stub();
}

public IPluginManagerServer getService() {
return mStub;
}

//内部类Stub继承自IPluginManagerServer.Stub
private class Stub extends IPluginManagerServer.Stub {
}
}

可以看出IPluginManagerServer也是一个AIDL文件,内部有许多操作插件的方法,例如安装,卸载插件等,也就是说关于插件的所有操作都在Server端吧。

PluginManagerProxy.load

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public class PluginManagerProxy {
private static IPluginManagerServer sRemote;

public static List<PluginInfo> load() throws RemoteException {
// 不判断sRemote在不在,因为本应该在sRemote获取后就马上调用
return sRemote.load();
}
}

public class PluginManagerServer {
private PluginInfoList mList = new PluginInfoList();

private List<PluginInfo> loadLocked() {
if (!mList.load(mContext)) {
return null;
}

// 执行“更新或删除Pending”插件,并返回结果
return updateAllLocked();
}

private List<PluginInfo> updateAllLocked() {
// 判断是否需要更新插件(只有未运行的才可以)
updateAllIfNeeded();
return mList.cloneList();
}

private void updateAllIfNeeded() {
int updateNum = 0;
for (PluginInfo pi : mList) {
if (updateIfNeeded(pi)) {
updateNum++;
}
}

if (updateNum > 0) {
mList.save(mContext);
}
}

private boolean updateIfNeeded(PluginInfo curInfo) {
if (isPluginRunningLocked(curInfo.getName(), null)) {
// 插件正在被使用,不能贸然升级或者卸载
return false;
}

// 更新插件前,首先判断该插件是否需要卸载,若是则直接删除,不再做更新操作
if (curInfo.isNeedUninstall()) {
// 移除插件及其已释放的Dex、Native库等文件并向各进程发送广播,同步更新
return uninstallNow(curInfo.getPendingDelete());
} else if (curInfo.isNeedUpdate()) {
// 需要更新插件?那就直接来
updateNow(curInfo, curInfo.getPendingUpdate());
return true;
} else if (curInfo.isNeedCover()) {
updateNow(curInfo, curInfo.getPendingCover());
return true;
} else {
// 既不需要删除也不需要更新
return false;
}
}

private void updateNow(PluginInfo curInfo, PluginInfo newInfo) {
final boolean covered = newInfo.getIsPendingCover();
if (covered) {
move(curInfo, newInfo);
} else {
// 删除旧版本插件,不管是不是p-n的,且清掉Dex和Native目录
delete(curInfo);
}

newInfo.setType(PluginInfo.TYPE_EXTRACTED);

if (covered) {
curInfo.setPendingCover(null);
newInfo.setIsPendingCover(false);
//修改isPendingCover属性后必须同时修改json中的path路径
newInfo.setPath(newInfo.getApkFile().getPath());
} else {
curInfo.update(newInfo);
curInfo.setPendingUpdate(null);
}
}

private class Stub extends IPluginManagerServer.Stub {
@Override
public List<PluginInfo> load() throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.loadLocked();
}
}
}
}

顺着PluginManagerProxy.load()跟踪下去,最后真正做加载工作的是PluginInfoList.load()函数(只是加载插件信息)。

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
public class PluginInfoList implements Iterable<PluginInfo> {
private File getFile(Context context) {
// public static final String LOCAL_PLUGIN_APK_SUB_DIR = "p_a";
final File d = context.getDir(Constant.LOCAL_PLUGIN_APK_SUB_DIR, 0);
return new File(d, "p.l");
}

public boolean load(Context context) {
try {
// 1. 读出字符串
final File f = getFile(context);
// 从配置文件中读取插件信息,插件信息以JSON格式保存在这个文件中
final String result = FileUtils.readFileToString(f, Charsets.UTF_8);
if (TextUtils.isEmpty(result)) {
return false;
}

// 2. 解析出JSON
final JSONArray jArr = new JSONArray(result);
for (int i = 0; i < jArr.length(); i++) {
final JSONObject jo = jArr.optJSONObject(i);
final PluginInfo pi = PluginInfo.createByJO(jo);
if (pi == null) {
continue;
}
addToMap(pi); // 保存插件信息
}
return true;
} catch (IOException e) {
} catch (JSONException e) {
}
return false;
}
}

小结

initForServer()方法的作用:

  1. 首先创建了一个PmHostSvc对象,这个类继承自IPluginHost.Stub,是一个IPluginHost类型的Binder对象,可以说所有的插件的管理工作都是直接或者间接由它处理的,PmHostSvc代表了Server端要处理的事情,也就是插件管理进程处理的事情。
  2. 在PmHostSvc的构造方法中又创建了两个对象,一个是PluginServiceServer,这个类是用来管理插件Service的远程Server端,还有一个是PluginManagerServer,这个类在创建的时候在构造中又创建了一个继承自IPluginServiceServer.Stub的Stub对象,Stub也是一个Binder对象,这个类掌管了所有对插件的的操作,例如插件的安装、加载、卸载、更新等等。
  3. 调用PluginProcessMain.installHost(mHostSvc)方法将PmHostSvc对象也就是IPluginHost类型赋值给PluginProcessMain中的字段sPluginHostLocal。接着调用了IPluginHost.fetchManagerServer()方法将PluginManagerServer中的Stub对象,也就是IPluginServiceServer类型的Binder对象赋值给PluginManagerProxy类中的字段sRemote,这个IPluginServiceServer类型的Binder对象掌握了对插件的安装、卸载、更新等等的操作。
  4. PluginManagerProxy.load()则用来更新插件信息。

UI进程启动

initForClient

Client(UI进程)的初始化如下:

1
2
3
4
5
6
7
private final void initForClient() {
// 1. 先尝试连接
PluginProcessMain.connectToHostSvc();
// 2. 从常驻进程获取插件列表,判断列表中是否有需要更新的插件,
// 如果有则调用Binder对象在Server端更新插件信息
refreshPluginsFromHostSvc();
}

PluginProcessMain.connectToHostSvc

非常驻进程调用connectToHostSvc方法,获取常驻进程的IPluginHost:

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
64
private static IPluginHost sPluginHostRemote;

static final void connectToHostSvc() {
Context context = PMF.getApplicationContext();
// 通过判断是哪个进程然后进行远程调用返回Binder,其实返回的就是PmHostSvc对象
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
if (binder == null) {
// 无法连接到常驻进程,当前进程自杀
System.exit(1);
}
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 检测到常驻进程退出,插件进程自杀
if (PluginManager.isPluginProcess()) {
System.exit(0);
}
sPluginHostRemote = null;

// 断开和插件化管理器服务端的连接,因为已经失效
PluginManagerProxy.disconnect();
}
}, 0);
} catch (RemoteException e) {
// 无法连接到常驻进程,当前进程自杀
System.exit(1);
}

sPluginHostRemote = IPluginHost.Stub.asInterface(binder);

// 连接到插件化管理器的服务端
try {
PluginManagerProxy.connectToServer(sPluginHostRemote);

// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
System.exit(1);
}

// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}

private static final IBinder proxyFetchHostBinder(Context context, String selection) {
Cursor cursor = null;
try {
Uri uri = ProcessPitProviderPersist.URI;
cursor = context.getContentResolver().query(uri, PROJECTION_MAIN, selection, null, null);
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) {
//
}
IBinder binder = BinderCursor.getBinder(cursor);
return binder;
} finally {
CloseableUtils.closeQuietly(cursor);
}
}

当前进程尝试通过ContentResolver去访问ProcessPitProviderPersist以获取一个与Persistent进程通信的IBinder对象,但是ProcessPitProviderPersist在第一次被访问时并没有运行起来,于是Android系统会自动启动它。ProcessPitProviderPersist在Manifest中的注册代码:

1
2
3
4
5
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderPersist"
android:authorities="${applicationId}.loader.p.main"
android:exported="false"
android:process=":GuardService" />

于是Android系统立即通过ActivityManagerService向Zygote进程请求folk一个新的进程,ProcessPitProviderPersist就运行在这个进程中,这个进程就是Persistent进程了。

  1. 默认情况下,GuardService会被当作Persistent进程的名字,在IPC.init()函数中会用这个名字来判断当前进程是不是Persistent进程。
  2. 有很多坑位组件使用android:process=”:GuardService”属性,因此如果Persistent进程不小心被杀掉了,在任何需要启动这些坑位组件的地方都会将Persistent进程重新启动起来。
  3. 系统在启动新进程的时候,会在新进程中执行RepluginApplication的初始化,所以以上提到的流程都会在这个进程中执行一遍,但是因为在PmBase.init()函数中有一个条件判断IPC.isPersistentProcess(),Persistent进程会执行和UI进程不同的代码路径。

refreshPluginsFromHostSvc

refreshPluginsFromHostSvc方法从HostSvc(插件管理所在进程)获取所有的插件信息,这些信息是在Persistent进程的启动流程中被加载的,接着会判断是否有更新,如果有插件已经更新了,会通过远程调用让PluginManagerServer重新加载插件:

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
private void refreshPluginsFromHostSvc() {
List<PluginInfo> plugins = null;
try {
plugins = PluginProcessMain.getPluginHost().listPlugins();
} catch (Throwable e) {
}

// 判断是否有需要更新的插件
List<PluginInfo> updatedPlugins = null;
if (isNeedToUpdate(plugins)) {
try {
updatedPlugins = PluginManagerProxy.updateAllPlugins();
} catch (RemoteException e) {
e.printStackTrace();
}
}

if (updatedPlugins != null) {
refreshPluginMap(updatedPlugins);
} else {
refreshPluginMap(plugins);
}
}

public static final IPluginHost getPluginHost() {
if (sPluginHostLocal != null) {
return sPluginHostLocal;
}
// 可能是第一次,或者常驻进程退出了
if (sPluginHostRemote == null) {
if (IPC.isPersistentProcess()) {
throw new RuntimeException("插件框架未正常初始化");
}
// 再次唤起常驻进程
connectToHostSvc();
}
return sPluginHostRemote;
}

小结

initForClient()方法的作用:

  • 通过Provider的方式请求插件管理进程返回PmHostSvc这个Binder对象,接着通过PmHostSvc再得到PluginManagerServer这个Binder对象并把当前进程信息注册到Server端,最后通过得到的Binder对象来同步进程信息和更新插件信息。

callAttach

PMF.callAttach()其实就是调用PmBase.callAttach(),首先将插件与当前进程关联起来,主要是将RepluginClassLoader和PluginCommImpl赋值给插件,它们会在插件真正加载运行时被用到。如果插件启动了自己的进程来运行,那么在插件的进程中会真正的去运行插件:

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
final void callAttach() {
mClassLoader = PmBase.class.getClassLoader();

// 挂载,将插件与当前进程关联
for (Plugin p : mPlugins.values()) {
p.attach(mContext, mClassLoader, mLocal);
}

// 如果插件启动了自己单独的进程,就会load插件
if (PluginManager.isPluginProcess()) {
if (!TextUtils.isEmpty(mDefaultPluginName)) {
Plugin p = mPlugins.get(mDefaultPluginName);
if (p != null) {
boolean rc = p.load(Plugin.LOAD_APP, true);
if (rc) {
mDefaultPlugin = p;
mClient.init(p);
}
}
}
}
}

// Plugin.java
final void attach(Context context, ClassLoader parent, PluginCommImpl manager) {
mContext = context;
mParent = parent;
mPluginManager = manager;
}

总结

Replugin框架将插件的管理工作统一放在一个进程中,而其他进程需要通过插件管理进程返回的Binder对象来进行操作,这样既保证了信息的安全性,又可以分担其他进程的工作压力。框架的初始化主要创建了一些来管理和操作插件的Binder对象,然后通过区分进程来分别初始化插件管理进程和Clent进程各自要做的事情,插件管理进程主要是对插件信息的更新和维护,而Clent进程主要是需要获取到插件管理的进程的Binder对象来进行后续的操作等,在各进程出来完后会将所有插件的进行进行存储,然后hook系统ClassLoader,最后加载了默认插件。

整个流程跟系统AMS和ServiceManager有些类似。

插件安装原理

概述

简述一下系统PMS安装apk的过程:

  • 系统扫描安装:在PMS被创建的时候,在构造方法中会扫描固定几个文件夹,例如:/system/framework、/system/app、/data/app等目录,然后为其中的apk文件创建一个PackageParser对象并执行该对象的parsePackage方法去解析这个apk文件,主要是解析AndroidManifest.xml,并封装PackageParser.Package对象返回,接着释放apk文件的lib库,优化dex文件,最后将解析得到的数据缓存起来,例如四大组件,Rermission等,系统扫描安装的大概步骤就是这样。
  • 调用PMS接口安装:入口是PMS的installPackage,它将apk复制到/data/app目录下,并调用解析和释放apk的方法,最后会发送一个广播通知安装完成。

Replugin中的插件分为内置插件和外置插件:

  • 内置的插件的安装时在初始化的时候就自动安装和加载了,在插件管理进程初始化的时候会扫描assest目录下的一个叫plugins-builtin.json的文件,并将插件信息封装成Plugin对象存入PmBase中
    的一个叫mPlugins的ConcurrentHashMap中统一管理。
  • 外置插件的安装需要调用Replugin.install()方法来安装插件,这个过程和内置插件类似,区别就是内置插件是通过assest目录下的json文件来生成插件对象,外置插件则是通过获取插件apk的PackageInfo来生成插件对象,但是并不会处理apk中的dex、so库、资源等,只有当真正使用这个插件中的类时才会去真正的解析加载这个插件。

安装入口

入口是Replugin.install()方法:

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
// Replugin.java
public static PluginInfo install(String path) {
// 判断插件是否合法
// ...
return MP.pluginDownloaded(path);
}

// MP.java
public static final PluginInfo pluginDownloaded(String path) {
// ...
// getPluginHost返回的是PmHostSvc对象
PluginInfo info = PluginProcessMain.getPluginHost().pluginDownloaded(path);
if (info != null) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginSucceed(info);
}
return info;
}

// PmHostSvc.java
public PluginInfo pluginDownloaded(String path) throws RemoteException {
// 通过路径来判断是采用新方案,还是旧的P-N(即将废弃,有多种)方案
PluginInfo pi;
String fn = new File(path).getName();
if (fn.startsWith("p-n-") || fn.startsWith("v-plugin-") || fn.startsWith("plugin-s-") || fn.startsWith("p-m-")) {
pi = pluginDownloadedForPn(path);
} else {
pi = mManager.getService().install(path);
}

if (pi != null) {
// 通常到这里,表示“安装已成功”,这时不管处于什么状态,都应该通知外界更新插件内存表
syncInstalledPluginInfo2All(pi);
}
return pi;
}

安装插件

执行安装的是mManager.getService().install(path),这个mManager是PluginManagerServer类型,它在PmHostSvc的构造方法中被创建,是用来管理插件的安装、卸载、更新、获取等功能。

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
64
65
66
67
68
69
70
71
public IPluginManagerServer getService() {
return mStub;
}

private class Stub extends IPluginManagerServer.Stub {
@Override
public PluginInfo install(String path) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.installLocked(path);
}
}

// ...
}

private PluginInfo installLocked(String path) {
final boolean verifySignEnable = RePlugin.getConfig().getVerifySign();
final int flags = verifySignEnable ? PackageManager.GET_META_DATA | PackageManager.GET_SIGNATURES : PackageManager.GET_META_DATA;

// 1. 读取APK内容
PackageInfo pi = mContext.getPackageManager().getPackageArchiveInfo(path, flags);
if (pi == null) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.READ_PKG_INFO_FAIL);
return null;
}

// 2. 校验插件签名
if (verifySignEnable) {
if (!verifySignature(pi, path)) {
return null;
}
}

// 3. 通过PackageInfo解析插件apk中的包名、AndroidManifest.xml中的metaData标签、根据这些信息new一个PluginInfo
PluginInfo instPli = PluginInfo.parseFromPackageInfo(pi, path);
instPli.setType(PluginInfo.TYPE_NOT_INSTALL);

// 获取之前是否已经安装过这个插件,false代表是返回原对象,true返回clone后的对象
PluginInfo curPli = MP.getPlugin(instPli.getName(), false);
if (curPli != null) {
// 版本较老?直接返回
final int checkResult = checkVersion(instPli, curPli);
if (checkResult < 0) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.VERIFY_VER_FAIL);
return null;
} else if (checkResult == 0){
instPli.setIsPendingCover(true);
}
}

// 4. 将合法的APK改名后,移动(或复制,见RePluginConfig.isMoveFileWhenInstalling)到新位置
if (!copyOrMoveApk(path, instPli)) {
RePlugin.getConfig().getEventCallbacks().onInstallPluginFailed(path, RePluginEventCallbacks.InstallResult.COPY_APK_FAIL);
return null;
}

// 5. 从插件中释放 So 文件
PluginNativeLibsHelper.install(instPli.getPath(), instPli.getNativeLibsDir());

// 6. 若已经安装旧版本插件,则尝试更新插件信息,否则直接加入到列表中
if (curPli != null) {
updateOrLater(curPli, instPli);
} else {
mList.add(instPli);
}

// 7. 保存插件信息到文件中,下次可直接使用
mList.save(mContext);

return instPli;
}

更新插件

更新的方法updateOrLater如下:

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
64
65
66
67
68
private void updateOrLater(PluginInfo curPli, PluginInfo instPli) {
// 已有“待更新版本”?
PluginInfo curUpdatePli = curPli.getPendingUpdate();
if (curUpdatePli != null) {
updatePendingUpdate(curPli, instPli, curUpdatePli);
return;
}

// 正在运行?Later到下次使用时再释放。否则直接开始更新
if (RePlugin.isPluginRunning(curPli.getName())) {
if (instPli.getVersion() > curPli.getVersion()) {
// 高版本升级
curPli.setPendingUpdate(instPli);
curPli.setPendingDelete(null);
curPli.setPendingCover(null);
} else if (instPli.getVersion() == curPli.getVersion()){
// 同版本覆盖
curPli.setPendingCover(instPli);
curPli.setPendingDelete(null);
// 注意:并不需要对PendingUpdate信息做处理,因为此前的updatePendingUpdate方法时就已经返回了
}

// 设置其Parent为curPli,在PmBase.newPluginFound时会用到
instPli.setParentInfo(curPli);
} else {
updateNow(curPli, instPli);
}
}

private void updatePendingUpdate(PluginInfo curPli, PluginInfo instPli, PluginInfo curUpdatePli) {
if (curUpdatePli.getVersion() < instPli.getVersion()) {
// 现在的版本比之前"打算要更新"的版本还要新(形象的称之为“夹心层”),则删除掉该“夹心层”的版本,然后换成这个更新的

// 设置待更新版本至最大版本
curPli.setPendingUpdate(instPli);
instPli.setParentInfo(curPli);

// 删除“夹心层”插件文件
try {
FileUtils.forceDelete(new File(curUpdatePli.getPath()));
} catch (IOException e) {
if (LogRelease.LOGR) {
e.printStackTrace();
}
}
}
}

private void updateNow(PluginInfo curInfo, PluginInfo newInfo) {
final boolean covered = newInfo.getIsPendingCover();
if (covered) {
move(curInfo, newInfo);
} else {
// 删除旧版本插件,不管是不是p-n的,且清掉Dex和Native目录
delete(curInfo);
}

newInfo.setType(PluginInfo.TYPE_EXTRACTED);
if (covered) {
curInfo.setPendingCover(null);
newInfo.setIsPendingCover(false);
//修改isPendingCover属性后必须同时修改json中的path路径
newInfo.setPath(newInfo.getApkFile().getPath());
} else {
curInfo.update(newInfo);
curInfo.setPendingUpdate(null);
}
}

通知更新内存

install结束后,接着看syncPluginInfo2All方法:

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
private void syncInstalledPluginInfo2All(PluginInfo pi) {
// PS:若更新了“正在运行”的插件(属于“下次重启进程后更新”),则由于install返回的是“新的PluginInfo”,为防止出现“错误更新”,需要使用原来的。
// 举例,有一个正在运行的插件A(其Info为PluginInfoOld)升级到新版(其Info为PluginInfoNew),则:
// 1. mManager.getService().install(path) 的返回值为:PluginInfoNew
// 2. PluginInfoOld在常驻进程中的内容修改为:PluginInfoOld.mPendingUpdate = PendingInfoNew
// 3. 同步到各进程,这里存在两种可能:
// a) (有问题)同步的是PluginInfoNew,则所有进程的内存表都强制更新到新的Info上,因此【正在运行的】插件信息将丢失,会出现严重问题
// b) (没问题)同步的是PluginInfoOld,只不过这个Old里面有个mPendingUpdate指向PendingInfoNew,则不会有问题,旧的仍被使用,符合预期
// 4. 最终install方法的返回值是PluginInfoNew,这样最外面拿到的就是安装成功的新插件信息,符合开发者的预期
// 简单的说就是如果这个插件正在运行则会记录这个要更新插件的信息到原插件信息对象上,
// 直到所有“正在使用插件”的进程结束并重启后才会生效
PluginInfo needToSyncPi;
PluginInfo parent = pi.getParentInfo();
if (parent != null) {
needToSyncPi = parent;
} else {
needToSyncPi = pi;
}

// 在常驻进程内更新插件内存表
mPluginMgr.newPluginFound(needToSyncPi, false);

// 通知其它进程去更新
Intent intent = new Intent(PmBase.ACTION_NEW_PLUGIN);
intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, mNeedRestart);
intent.putExtra("obj", (Parcelable) needToSyncPi);
IPC.sendLocalBroadcast2AllSync(mContext, intent);
}

final void newPluginFound(PluginInfo info, boolean persistNeedRestart) {
// 更新最新插件表
PluginTable.updatePlugin(info);

// 更新可加载插件表
insertNewPlugin(info);

// 清空插件的状态(解禁)
PluginStatusController.setStatus(info.getName(), info.getVersion(), PluginStatusController.STATUS_OK);

if (IPC.isPersistentProcess()) {
persistNeedRestart = mNeedRestart;
}

// 通知本进程:通知给外部使用者
Intent intent = new Intent(RePluginConstants.ACTION_NEW_PLUGIN);
intent.putExtra(RePluginConstants.KEY_PLUGIN_INFO, (Parcelable) info);
intent.putExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, persistNeedRestart);
intent.putExtra(RePluginConstants.KEY_SELF_NEED_RESTART, mNeedRestart);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}

注册PmBase.ACTION_NEW_PLUGIN监听的代码,在RePluginApplication的onCreate方法中调用:

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
// PmBase#callAppCreate()
if (!IPC.isPersistentProcess()) {
// 由于常驻进程已经在内部做了相关的处理,此处仅需要在UI进程注册并更新即可
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_NEW_PLUGIN);
intentFilter.addAction(ACTION_UNINSTALL_PLUGIN);
try {
LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, intentFilter);
} catch (Exception e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p m hlc a r e: " + e.getMessage(), e);
}
}
}

private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
return;
}

if (action.equals(intent.getAction())) {
PluginInfo info = intent.getParcelableExtra("obj");
if (info != null) {
switch (action) {
case ACTION_NEW_PLUGIN:
// 非常驻进程上下文
newPluginFound(info, intent.getBooleanExtra(RePluginConstants.KEY_PERSIST_NEED_RESTART, false));
break;
case ACTION_UNINSTALL_PLUGIN:
pluginUninstalled(info);
break;
}
}
}
}
};

Activity启动流程

启动一个Activity的入口函数是Replugin.startActivity(),然后调用Factory.startActivityWithNoInjectCN,再经过PluginCommImpl.startActivivty(),最终来到PluginLibraryInternalProxy.startActivity(),这里是真正开始工作的地方,接下来按照顺序解析这一流程。

准备工作

在启动之前,如果有必要,需要先下载插件,可以在Application中使用继承RePluginCallbacks的自定义回调来处理下载插件的需求:

1
2
3
4
5
6
7
if (download) {
if (PluginTable.getPluginInfo(plugin) == null) {
if (isNeedToDownload(context, plugin)) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}

如果插件状态不正确,或者首次加载大插件,会通过回调让用户处理,用户可以可以在回调里定制自己的行为,比如弹出提示框,加载进度条等。

1
2
3
4
5
6
7
8
9
10
11
12
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}

// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
}
}

然后会调用PluginCommImpl.loadPluginActivity来寻找坑位Activity。如果是第一次去获取信息,会首先去加载插件的Dex文件以及资源等,并创建PluginDexClassLoader。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 加载插件Activity,在startActivity之前调用
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);
ActivityInfo ai = getActivityInfo(plugin, activity, intent);

// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);

// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}

// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
// 远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);

PmBase.cleanIntentPluginParams(intent);
return new ComponentName(IPC.getPackageName(), container);
}

插件加载

PluginCommImpl.getActivityInfo调用PmBase.loadAppPlugin获取插件对象,Replugin 是支持使用 IntentFilter 来启动组件的,完美支持原生特性。

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
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
// 获取插件对象
Plugin p = mPluginMgr.loadAppPlugin(plugin);
ActivityInfo ai = null;

// activity 不为空时,从插件声明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 为空时,根据 Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}

final Plugin loadAppPlugin(String plugin) {
return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true);
}

final Plugin loadPlugin(Plugin p, int loadType, boolean useCache) {
if (p == null) {
return null;
}
if (!p.load(loadType, useCache)) {
return null;
}
return p;
}

final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
boolean rc = loadLocked(load, useCache);
// 尝试在此处调用Application.onCreate方法
// Added by Jiongxuan Zhang
if (load == LOAD_APP && rc) {
callApp();
}
// 如果info改了,通知一下常驻
// 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
if (rc && mInfo != info) {
UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
Tasks.post2Thread(task);
}
return rc;
}

PmBase.loadAppPlugin会最终调用Plugin.loadLocked()函数,这个函数有两个参数,第一个是加载类型,一共有四种加载类型,在这里使用的是Plugin.LOAD_APP,因为运行插件需要所有的东西。第二个参数是是否使用缓存,通常情况下我们会现在缓存中查找插件信息,这样会更快。

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
private boolean loadLocked(int load, boolean useCache) {
// 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作,直接返回缓存数据
if (useCache) {
boolean result = loadByCache(load);
if (result) {
return true;
}
}

PluginCommImpl manager = mPluginManager;
boolean rc = doLoad(logTag, context, parent, manager, load); // 真正的加载

if (rc) {
try {
// 至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
}
return true;
}

File odex = mInfo.getDexFile();
if (odex.exists()) {
odex.delete();
}
rc = doLoad(logTag, context, parent, manager, load);

return true;
}

Plugin.doLoad()用来加载插件的Dex文件,资源,以及so文件等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
if (mLoader == null) {
// 释放so文件,省略
// 加载Dex,获取组件信息
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
if (!mLoader.loadDex(parent, load)) {
return false;
}
// 在Persistent进程中更新插件信息,设置插件为“使用过的”
try {
PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
} catch (RemoteException e) {
}

// 若需要加载Dex,则还同时需要初始化插件里的Entry对象
if (load == LOAD_APP) {
// NOTE Entry对象是可以在任何线程中被调用到
if (!loadEntryLocked(manager)) {
return false;
}
}
}
}

Loader.loadDex函数会获取Dex中的组件的信息,包括Manifest中的组件属性,比如进程属性,TaskAffinity属性,注册静态广播等等,这里值得重点强调的是PluginDexClassLoader在此被初始化了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final boolean loadDex(ClassLoader parent, int load) {
try {
mClassLoader = Plugin.queryCachedClassLoader(mPath);
if (mClassLoader == null) {
// 创建PluginDexClassLoader
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
}
// 创建插件的Context对象
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
} catch (Throwable e) {
return false;
}

return true;
}

插件加载的具体原理会在下一节讲到。

寻找坑位

坑位初始化

LaunchModeStates

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class LaunchModeStates {

/**
* 目前的策略是,针对每一种 launchMode 分配两种坑位(透明主题(TS)和不透明主题(NTS))
* <p>
* 例:透明主题
*        <N1NRTS0, ActivityState>
* NR + TS - > <N1NRTS1, ActivityState>
*        <N1NRTS2, ActivityState>
* <p>
* 例:不透明主题
*        <N1NRNTS0, ActivityState>
* NR + NTS - > <N1NRNTS1, ActivityState>
*        <N1NRNTS2, ActivityState>
* <p>
* 其中:N1 表示当前为 UI 进程,NR 表示 launchMode 为 Standard,NTS 表示坑的 theme 为 Not Translucent。
*/
private Map<String, HashMap<String, ActivityState>> mStates = new HashMap<>();

/**
* 初始化 LaunchMode 和 Theme 对应的坑位
*
* @param containers 保存所有 activity 坑位的引用
* @param prefix 坑位前缀
* @param launchMode launchMode
* @param translucent 是否是透明的坑
* @param count 坑位数
*/
void addStates(Map<String, ActivityState> allStates, HashSet<String> containers, String prefix, int launchMode, boolean translucent, int count) {
String infix = getInfix(launchMode, translucent);
HashMap<String, ActivityState> states = mStates.get(infix);
if (states == null) {
states = new HashMap<>();
mStates.put(infix, states);
}

for (int i = 0; i < count; i++) {
String key = prefix + infix + i;
ActivityState state = new ActivityState(key);
states.put(key, state);
allStates.put(key, state);
containers.add(key);
}
}

/**
* 根据 launchMode 和 theme 获取对应的坑位集合
*/
HashMap<String, ActivityState> getStates(int launchMode, int theme) {
String infix = getInfix(launchMode, isTranslucentTheme(theme));
return mStates.get(infix);
}

/**
* 根据 launchMode 和 '是否透明' 获取中缀符
*
* @return 如果是透明主题,返回 'launchMode'_TS_,否则返回 'launchMode'_NOT_TS_
*/
private static String getInfix(int launchMode, boolean translucent) {
String launchModeInfix = getLaunchModeInfix(launchMode);
return translucent ? launchModeInfix + "TS" : launchModeInfix + "NTS";
}

/**
* 获取 launchMode 对应的前缀
*/
private static String getLaunchModeInfix(int launchMode) {
switch (launchMode) {
case ActivityInfo.LAUNCH_SINGLE_TOP:
return "STP";
case ActivityInfo.LAUNCH_SINGLE_TASK:
return "ST";
case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
return "SI";
default:
return "NR";
}
}
}

PluginContainers.init

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
PluginProcessPer(Context context, PmBase pm, int process, HashSet<String> containers) {
mContext = context;
mPluginMgr = pm;
mServiceMgr = new PluginServiceServer(context);

mACM = new PluginContainers();
mACM.init(process, containers);
}

// PluginContainers.java
final void init(int process, HashSet<String> containers) {
if (process != IPluginManager.PROCESS_UI
&& !PluginProcessHost.isCustomPluginProcess(process)
&& !PluginManager.isPluginProcess()) {
return;
}

// CONTAINER_ACTIVITY_PART = ".loader.a.Activity"
String prefix = IPC.getPackageName() + CONTAINER_ACTIVITY_PART;

// 因为自定义进程可能也会唤起使用UI进程的坑,所以这里使用'或'条件
if (process == IPluginManager.PROCESS_UI || PluginProcessHost.isCustomPluginProcess(process)) {

/* UI 进程标识为 N1 */
String suffix = "N1";

// Standard
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);

// SingleTop
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);

// SingleTask
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);

// SingleInstance
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);

// taskAffinity
mTaskAffinityStates.init(prefix, suffix, mStates, containers);

// 因为有可能会在 UI 进程启动自定义进程的 Activity,所以此处也要初始化自定义进程的坑位数据
for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) {
ProcessStates processStates = new ProcessStates();
// [":p1": state("P1"), ":p2": state("P2")]
mProcessStatesMap.put(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i, processStates);
init2(prefix, containers, processStates, PluginProcessHost.PROCESS_PLUGIN_SUFFIX + i);
}

// 从内存中加载
loadFromPref();
}
}

/**
* 初始化自定义进程坑坑位
*
* @param prefix xxx.xx.loader.a.Activity
* @param containers 保存所有 Activity 坑名称
* @param states 当前进程所有坑位的状态
* @param suffix p0, p1, p2
*/
private void init2(String prefix, HashSet<String> containers, ProcessStates states, String suffix) {
suffix = suffix.toUpperCase();

// Standard
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);

// SingleTop
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);

// SingleTask
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);

// SingleInstance
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);

// taskAffinity
states.mTaskAffinityStates.init(prefix, suffix, mStates, containers);
}

坑位示例(process, launchMode, taskAffinity, theme):

1
2
3
4
5
6
7
8
9
com.hearing.host.loader.a.ActivityN1NRTS0
com.hearing.host.loader.a.ActivityN1NRTS1
com.hearing.host.loader.a.ActivityN1STPTS0
com.hearing.host.loader.a.ActivityN1STTS0
com.hearing.host.loader.a.ActivityN1SITS0
com.hearing.host.loader.a.ActivityN1TA0STPTS0
com.hearing.host.loader.a.ActivityN1TA0STPTS1
com.hearing.host.loader.a.ActivityP0NRTS0
com.hearing.host.loader.a.ActivityP0STPTS0

allocActivityContainer

client.allocActivityContainer是一个远程调用,调用了Persistent进程中的PluginProcessPer.allocActivityContainer函数,进一步调用bindActivity函数。

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
// 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器
final String bindActivity(String plugin, int process, String activity, Intent intent) {
/* 获取插件对象 */
Plugin p = mPluginMgr.loadAppPlugin(plugin);
/* 获取 ActivityInfo */
ActivityInfo ai = p.mLoader.mComponents.getActivity(activity);

// 进程名为空则使用主进程名
if (ai.processName == null) {
ai.processName = ai.applicationInfo.processName;
}
if (ai.processName == null) {
ai.processName = ai.packageName;
}

String container;

// final PluginContainers mACM;
// 自定义进程,插件中进程名称后缀PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 = ":p"
if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
// 取进程名称中,冒号及后面的部分,如果进程名中无冒号,则返回原值。
String processTail = PluginProcessHost.processTail(ai.processName);
container = mACM.alloc2(ai, plugin, activity, process, intent, processTail);
} else {
container = mACM.alloc(ai, plugin, activity, process, intent);
}

/* 检查 activity 是否存在 */
Class<?> c = null;
try {
c = p.mLoader.mClassLoader.loadClass(activity);
} catch (Throwable e) {
}
if (c == null) {
return null;
}
return container;
}

这里mACM.alloc用来执行位于宿主进程中的坑位分配,mACM.alloc2用来执行位于插件子进程中的坑位分配。

alloc&alloc2

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
public class PluginContainers {
// 所有坑的状态集合
private HashMap<String, ActivityState> mStates = new HashMap<>();
// 非默认 TaskAffinity 下,坑位的状态信息。
private TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates();
// 默认 TaskAffinity 下,坑位的状态信息。
private LaunchModeStates mLaunchModeStates = new LaunchModeStates();
// 保存进程和进程中坑位状态的 Map
private final Map<String, ProcessStates> mProcessStatesMap = new HashMap<>();

final String alloc(ActivityInfo ai, String plugin, String activity, int process, Intent intent) {
ActivityState state;
String defaultPluginTaskAffinity = ai.applicationInfo.packageName;

/* SingleInstance 优先级最高 */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* SingleTask, SingleTop, Standard */
} else {
synchronized (mLock) {
state = allocLocked(ai, mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}

String alloc2(ActivityInfo ai, String plugin, String activity, int process, Intent intent, String processTail) {
// 根据进程名称,取得该进程对应的 PluginContainerStates
ProcessStates states = mProcessStatesMap.get(processTail);

ActivityState state;

String defaultPluginTaskAffinity = ai.applicationInfo.packageName;

/* SingleInstance */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, states.mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* other mode */
} else {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}

static final class ActivityState {
final String container;
int state;
String plugin;
String activity;
long timestamp;
final ArrayList<WeakReference<Activity>> refs;

private final boolean isTarget(String plugin, String activity) {
if (TextUtils.equals(this.plugin, plugin) && TextUtils.equals(this.activity, activity)) {
return true;
}
return false;
}

private final void occupy(String plugin, String activity) {
if (TextUtils.isEmpty(plugin) || TextUtils.isEmpty(activity)) {
return;
}

this.state = STATE_OCCUPIED;
this.plugin = plugin;
this.activity = activity;
cleanRefs();
this.timestamp = System.currentTimeMillis();
save2Pref(this.plugin, this.activity, this.container);
}

private final boolean hasRef() {
for (int i = refs.size() - 1; i >= 0; i--) {
WeakReference<Activity> ref = refs.get(i);
if (ref.get() == null) {
refs.remove(i);
}
}
return refs.size() > 0;
}

private final void cleanRefs() {
refs.clear();
}

// ...
}

private static final void save2Pref(String plugin, String activity, String container) {
String v = plugin + ":" + activity + ":" + System.currentTimeMillis();
Pref.ipcSet(container, v);
}

static final String[] resolvePluginActivity(String container) {
String v = Pref.ipcGet(container, "");
if (TextUtils.isEmpty(v)) {
return null;
}
return v.split(":");
}
}

class ProcessStates {
// 保存非默认 TaskAffinity 下,坑位的状态信息。
TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates();

// 保存默认 TaskAffinity 下,坑位的状态信息。
LaunchModeStates mLaunchModeStates = new LaunchModeStates();
}

allocLocked

上面两个方法都会调用allocLocked方法,AcitivtyState对象保存了坑位Activity和真实要启动的Activity之间的对应关系,并且这个对应关系会被保存起来,在RepluginClassLoader在加载类的时候会被拿出来使用,以获取要运行的Activity的class对象。

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
private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map, String plugin, String activity, Intent intent) {
// 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
return state;
}
}

// 新分配:找空白的,第一个
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
state.occupy(plugin, activity);
return state;
}
}

ActivityState found;

// 重用:则找最老的那个
found = null;
for (ActivityState state : map.values()) {
if (!state.hasRef()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
}
if (found != null) {
found.occupy(plugin, activity);
return found;
}

// 强挤:最后一招,挤掉:最老的那个
found = null;
for (ActivityState state : map.values()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
if (found != null) {
found.finishRefs();
found.occupy(plugin, activity);
return found;
}

// never reach here
return null;
}

启动Activity

找到坑位后,PluginLibraryInternalProxy.startActivity()中开始启动坑位Activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
intent.setComponent(cn);
context.startActivity(intent);

// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,intent为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);

return true;
}

根据之前的Activity启动原理中ActivityThread.performLaunchActivity方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;

ComponentName component = r.intent.getComponent();
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
}
// ...
return activity;
}

可知系统会调用Classloader的loadClass方法,这里就是调用Replugin提供的替代者RepluginClassLoader的方法。接着又会调用PMF.loadClass,其实就是调用Pmbase.loadClass。

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
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> c = null;
c = PMF.loadClass(className, resolve);
if (c != null) {
return c;
}
try {
return mOrig.loadClass(className);
} catch (Throwable e) {
}
return super.loadClass(className, resolve);
}

// Pmbase
final Class<?> loadClass(String className, boolean resolve) {
if (mContainerActivities.contains(className)) {
// PluginProcessPer mClient;
Class<?> c = mClient.resolveActivityClass(className);
if (c != null) {
return c;
}
// ...
}
// mContainerServices
// mContainerProviders
// ...
}

这里先从PluginContainers的实例对象mACM中去查找ActivityState,它就是在分配坑位的时候,我们用来保存坑位组件与真实组件对应关系的类。然后在缓存中找到插件名对应的插件对象,因为在分配坑位的时候插件信息已经加载过了,不需要重新加载。接着取出插件的ClassLoader对象,这个对象正是加载插件时创建的PuginDexClassLoader的实例了。然后利用插件的PuginDexClassLoader对象来加载真实Activity的class对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final Class<?> resolveActivityClass(String container) {
String plugin = null;
String activity = null;
// 先找登记的,如果找不到,则用forward activity
PluginContainers.ActivityState state = mACM.lookupByContainer(container);
if (state == null) {
// 若坑位出现丢失或错乱,则通过读取Intent.Category来做个中转
return ForwardActivity.class;
}
plugin = state.plugin;
activity = state.activity;
Plugin p = mPluginMgr.loadAppPlugin(plugin); //通过插件名从缓存中加载Plugin对象
ClassLoader cl = p.getClassLoader();
Class<?> c = null;
try {
c = cl.loadClass(activity);
} catch (Throwable e) {
}
return c;
}

找到插件Activity的类对象后,Android系统就开始运行Activity的启动流程了,这些事情由ActivityManagerService和ActivityThread负责。就这样,Replugin用插件中的Activity替换了坑位Activity,巧妙地将其运行起来了。

插件加载原理

Plugin加载过程

加载插件就是启动运行插件apk的过程,插件的真正加载从调用Plugin.load方法开始,然后调用Plugin.doLoad函数,这个函数做了三件事情:

  • 释放插件文件到相应的目录,比如so库,dex文件
  • 加载dex文件,比如组件信息,组件属性,资源等
  • 要运行Plugin,还需要初始化Plugin的运行环境

释放文件这一步很简单,就是将APK包打开以后将相应的文件放到不同的目录中。加载Dex文件是通过Loader.loadDex方法实现的,下面我们分步解析这个方法:

  1. 获取PackageInfo并缓存插件相关的信息,比如组件,组件属性,资源等,下次就可以直接从缓存中读取。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    if (mPackageInfo == null) {
    // 通过PackageManager获取PackageInfo
    mPackageInfo = pm.getPackageArchiveInfo(mPath,
    PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
    ......
    // 添加针对SO库的加载
    PluginInfo pi = mPluginObj.mInfo;
    File ld = pi.getNativeLibsDir();
    mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
    // 缓存表: pkgName -> pluginName
    synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
    Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
    }
    // 缓存表: pluginName -> fileName
    synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
    Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
    }
    // 缓存表: fileName -> PackageInfo
    synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
    Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
    }
    }
  2. 解析组件信息,注册Plugin的Manifest中声明的BroadcastReceiver,调整组件的进程名及TaskAffinity。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if (mComponents == null) {
    mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
    // 动态注册插件中声明的 receiver
    regReceivers();
    // 缓存表Components
    synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
    Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
    }
    /* 只调整一次 */
    // 调整插件中组件的进程名称,详情见进程管理部分
    adjustPluginProcess(mPackageInfo.applicationInfo);
    // 调整插件中Activity的TaskAffinity
    adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
    }
  3. 获取Plugin的资源,先查找缓存,如果找不到就通过PackageManager创建Resources对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    mPkgResources = Plugin.queryCachedResources(mPath);
    if (mPkgResources == null) {
    try {
    if (BuildConfig.DEBUG) {
    // 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
    Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
    mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
    } else {
    mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
    }
    } catch (NameNotFoundException e) {
    return false;
    }
    // 缓存Resources
    synchronized (Plugin.FILENAME_2_RESOURCES) {
    Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
    }
    }
  4. 创建Plugin的PluginDexClassLoader并将它缓存起来,不必每次都创建一个新的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    mClassLoader = Plugin.queryCachedClassLoader(mPath);
    if (mClassLoader == null) {
    String out = mPluginObj.mInfo.getDexParentDir().getPath();
    ......
    String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
    // 创建PluginDexClassLoader对象
    mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
    ......
    synchronized (Plugin.FILENAME_2_DEX) {
    Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); // 缓存ClassLoader
    }
    }
  5. 为Plugin创建一个全局的PluginContext,并用上面创建的ClassLoader以及Resources作为参数。而这个PluginContext对象会被赋值给Plugin的Application对象。其实每一个Plugin的Activity都会创建一个PluginContext对象,并使用相同的ClassLoader和Resources,因此在Plugin中就可以加载相关的类和使用资源了,跟原生程序一样。

    1
    2
    3
    // 此处mContext即是主程序Application
    mPkgContext = new PluginContext(mContext, android.R.style.Theme,
    mClassLoader, mPkgResources, mPluginName, this);

Plugin初始化

Dex文件加载完成以后,要运行Plugin还需要初始化Plugin的运行环境相关的类,在Plugin.doLoad的最后阶段调用了loadEntryLocked函数,这个函数负责初始化Plugin的运行环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
} else {
if (mLoader.loadEntryMethod2()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod(false)) {
if (!mLoader.invoke(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod3()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
return false;
}
}
return true;
}

Loader.loadEntryMethod3通过反射将Plugin中的Entry类的create函数对象得到并保存在mCreateMethod2中。注意这里的mClassLoader是插件的PluginDexClassLoader,所以才能得到插件中的类。

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
final boolean loadEntryMethod3() {
try {
String className = "com.qihoo360.replugin.Entry";
mCreateMethod2 = c.getDeclaredMethod("create", Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
}
return mCreateMethod2 != null;
}

final boolean invoke2(PluginCommImpl x) {
try {
IBinder manager = null; // TODO
// mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, getClass().getClassLoader(), manager);
if (b == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.e.r.b n");
}
return false;
}
mBinderPlugin = new ProxyPlugin(b);
mPlugin = mBinderPlugin;
if (LOG) {
LogDebug.d(PLUGIN_TAG, "Loader.invoke2(): plugin=" + mPath + ", plugin.binder.cl=" + b.getClass().getClassLoader());
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
return false;
}
return true;
}

接着Loader.invoke2函数会用反射的方式调用上面拿到的create函数。我们可以看看Plugin端的Entry.create方法,它有一个参数是ClassLoader,这个ClassLoader是Host中的RepluginClassLoader,有了它,Plugin才能找到Host当中的类并调用这些类的方法。

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
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
// 初始化一些代理类,这些代理类可以在Plugin中调用Host中的函数。
RePluginFramework.init(cl);
RePluginEnv.init(context, cl, manager);

return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}

public class RePluginEnv {
private static Context sPluginContext;
private static Context sHostContext;
private static ClassLoader sHostClassLoader;
private static IBinder sPluginManager;

static void init(Context context, ClassLoader cl, IBinder manager) {
sPluginContext = context;
// 确保获取的一定是主程序的Context
sHostContext = ((ContextWrapper) context).getBaseContext();
sHostClassLoader = cl;
// 从宿主传过来的,目前恒为null
sPluginManager = manager;
}
}

// RePluginFramework.init
static boolean init(ClassLoader cl) {
synchronized (LOCK) {
return initLocked(cl);
}
}

private static boolean initLocked(ClassLoader cl) {
RePluginInternal.ProxyRePluginInternalVar.initLocked(cl);
RePlugin.ProxyRePluginVar.initLocked(cl);
PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl);
PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl);
PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl);
IPC.ProxyIPCVar.initLocked(cl);
}

因此在Plugin中可以调用Host的一些方法,比如在Plugin中使用Replugin.startActivity时,实际上是通过MethodInvoker.call函数去执行Host中Replugin对应的startActivity函数。所以Plugin中的Replugin类的实现就只是一个空壳代理而已,可以看到它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static boolean startActivity(Context context, Intent intent) {
if (!RePluginFramework.mHostInitialized) {
return false;
}
try { // 用反射的方式调用Host中Replugin的startActivity函数来启动Activity
Object obj = ProxyRePluginVar.startActivity.call(null, context, intent);
if (obj != null) {
return (Boolean) obj;
}
} catch (Exception e) {
if (LogDebug.LOG) {
e.printStackTrace();
}
}
return false;
}

Host/Plugin类反射关系

  • RePluginInternal.ProxyRePluginInternalVar -> com.qihoo360.i.Factory2/com.qihoo360.i.Factory:Factory2中调用的是Host中PluginLibraryInternalProxy类中的方法。
  • RePlugin.ProxyRePluginVar -> com.qihoo360.replugin.RePlugin:即Host与Plugin中的RePlugin类对应。
  • PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar -> android.support.v4.content.LocalBroadcastManager
  • PluginProviderClient.ProxyRePluginProviderClientVar -> com.qihoo360.loader2.mgr.PluginProviderClient
  • PluginServiceClient.ProxyRePluginServiceClientVar -> com.qihoo360.loader2.mgr.PluginServiceClient
  • IPC.ProxyIPCVar -> com.qihoo360.replugin.base.IPC

插件中的Context

首先看一下PluginContext类:

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
public class PluginContext extends ContextThemeWrapper {

private final ClassLoader mNewClassLoader;
private final Resources mNewResources;
private final String mPlugin;
private final Loader mLoader;
// ...

public PluginContext(Context base, int themeres, ClassLoader cl, Resources r, String plugin, Loader loader) {
super(base, themeres);

mNewClassLoader = cl;
mNewResources = r;
mPlugin = plugin;
mLoader = loader;

mContextInjector = RePlugin.getConfig().getCallbacks().createContextInjector();
}

@Override
public Context getApplicationContext() {
if (mLoader.mPluginObj.mInfo.getFrameworkVersion() <= 2) {
// 仅框架版本为3及以上的,才支持直接获取PluginContext,而非卫士ApplicationContext
return super.getApplicationContext();
}
// 直接获取插件的Application对象
// entry中调用context.getApplicationContext时mApplicationClient还没被赋值,会导致空指针造成插件安装失败
if (mLoader.mPluginObj.mApplicationClient == null) {
return this;
} else {
return mLoader.mPluginObj.mApplicationClient.getObj();
}
}

public void startActivity(Intent intent) {
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent, options);
}

super.startActivity(intent, options);

if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent, options);
}
}
}

public ComponentName startService(Intent service) {
// ...
}

// ...
}

PluginContext类中也重写了startActivity等方法,最终走的也是RePlugin自定义的startActivity等方式。

在上述的loadAppPlugin方法中,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
final Plugin loadAppPlugin(String plugin) {
return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true);
}

final Plugin loadPlugin(Plugin p, int loadType, boolean useCache) {
if (p == null) {
return null;
}
if (!p.load(loadType, useCache)) {
return null;
}
return p;
}

这里调用了Plugin.load方法:

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
/**
* 用来控制插件里的Application对象
*/
PluginApplicationClient mApplicationClient;

// Plugin.load
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
boolean rc = loadLocked(load, useCache); //Plugin加载完成

if (load == LOAD_APP && rc) {
callApp();// 关于Plugin的Application的操作都在这里了,调用了callAppLocked方法
}
// ...
return rc;
}

// Plugin.callAppLocked
private void callAppLocked() {
// 获取并调用Application的几个核心方法
if (!mDummyPlugin) {
// ...
mApplicationClient = PluginApplicationClient.getOrCreate(
mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo); // 创建Plugin的Appication对象

if (mApplicationClient != null) { // 调用Application.attach
mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
mApplicationClient.callOnCreate(); //调用Application.onCreate
}
} else {
}
}

PluginApplicationClient中的逻辑如下:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public static PluginApplicationClient getOrCreate(String pn, ClassLoader plgCL, ComponentList cl, PluginInfo pi) {
PluginApplicationClient pac = getRunning(pn);
if (pac != null) {
// 已经初始化过Application?直接返回
return pac;
}

// 初始化所有需要反射的方法
// sAttachBaseContextMethod = Application.class.getDeclaredMethod("attach", Context.class);
initMethods();

final PluginApplicationClient pacNew = new PluginApplicationClient(plgCL, cl, pi);
if (pacNew.isValid()) {
sRunningClients.put(pn, new WeakReference<>(pacNew));
if (Build.VERSION.SDK_INT >= 14) {
RePluginInternal.getAppContext().registerComponentCallbacks(new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
pacNew.callOnTrimMemory(level);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
pacNew.callOnConfigurationChanged(newConfig);
}

@Override
public void onLowMemory() {
pacNew.callOnLowMemory();
}
});
}
return pacNew;
} else {
// Application对象没有初始化出来,则直接按失败处理
return null;
}
}

private PluginApplicationClient(ClassLoader plgCL, ComponentList cl, PluginInfo pi) {
mPlgClassLoader = plgCL;
mApplicationInfo = cl.getApplication();
try {
// 尝试使用自定义Application(如有)
if (mApplicationInfo != null && !TextUtils.isEmpty(mApplicationInfo.className)) {
// 此处根据mApplicationInfo中的信息实例化了Plugin中自定义的Application类
// 如果这个方法执行失败,则Plugin中自定义的Application回调方法不会走
initCustom();
}
// 若自定义有误(或没有),且框架版本为3及以上的,则可以创建空Application对象,方便插件getApplicationContext到自己
if (!isValid() && pi.getFrameworkVersion() >= 3) {
mApplication = new Application();
}
} catch (Throwable e) {
// 出现异常,表示Application有问题
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
mApplication = new Application();
}
}

public Application getObj() {
return mApplication;
}

public void callAttachBaseContext(Context c) {
try {
sAttachBaseContextMethod.setAccessible(true);
sAttachBaseContextMethod.invoke(mApplication, c); // 将PluginContext对象传递给Application对象
} catch (Throwable e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}

public void callOnCreate() {
mApplication.onCreate();
}

// Application.java
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

这里用一个PluginContext对象替换掉插件Application的Context对象,这个PluginContext对象就是在前面讲到的Dex加载过程中创建的,作为Plugin的全局Context。

实际上在Plugin中调用getApplication获取到的是Host的Application对象,因为别忘了我们是利用坑位原理运行插件组件的,所以在插件中我们用到的都是Host的Application,这里可以在Activity启动的源码调用中看出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Activity.java
public final Application getApplication() {
// mApplication在Activity#attach方法中赋值
return mApplication;
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
}

但是万一Plugin的Application中有客户定制的任何动作要完成呢?所以在加载Plugin之后,Host也会通过反射创建Plugin的Application对象,并反射调用它的attach和create函数,并在PluginContext.getApplicationContext方法中返回这个Application对象。

在宿主中可以通过RePlugin.fetchContext(String pluginName)方法获取插件的Context:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Context fetchContext(String pluginName) {
return Factory.queryPluginContext(pluginName);
}

public static final Context queryPluginContext(String name) {
return sPluginManager.queryPluginContext(name);
}

public Context queryPluginContext(String name) {
Plugin p = mPluginMgr.loadAppPlugin(name);
if (p != null) {
return p.mLoader.mPkgContext;
}
return null;
}

在插件中可以通过RePlugin.fetchContext(String pluginName)方法获取插件的Context,其实就是反射调用了宿主的RePlugin.fetchContext(String pluginName)方法。

在插件中可以通过RePlugin.getHostContext()方法获取宿主的Context:

1
2
3
public static Context getHostContext() {
return RePluginEnv.getHostContext();
}

在插件中可以通过RePlugin.getPluginContext()方法获取本插件的Context:

1
2
3
public static Context getPluginContext() {
return RePluginEnv.getPluginContext();
}

插件中启动Activity

Android中Activity和Context都有startActivity函数,因为我们插件的组件在Host的Manifest中是没有声明的,只能通过坑位来启动,而启动坑位的动作只能在Host中完成。为了在插件中能正常启动Activity,我们要将这些函数都屏蔽掉,转而使用Replugin提供的startActivity。

replugin-plugin-gradle在编译阶段会将所有继承了Activity的类都被强制修改成继承PluginActivity,在上面已经给出过替换的对应规则。

首先PluginActivity重写了startActivity函数,并在其中通过反射调用Host中的Replugin.startActivity函数,这样就能做到在插件中启动Activity了。这里的反射调用指的就是调用前面讲过的RePlugin.ProxyRePluginVar类在初始化时得到的函数。

然后,PluginActivity重写了attachBaseContext函数,同样在代理类中通过反射调用Host中Factory2.createActivityContext函数创建一个PluginContext对象,并用这个对象替换掉原生的Context对象。

1
2
3
4
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase); //创建PluginContext对象
super.attachBaseContext(newBase); //替换原生的Context
}

PluginContext也重写了startActivity函数,并且在其中调用了Factory2.startActivity函数,接着又会调用PluginLibraryInternalProxy.startActivity。

另外,PluginApplicationClient为插件创建了Application对象,在PluginApplicationClient.callAttachBaseContext函数中通过反射调用Applicaiton.attach函数,用一个PluginContext对象替换掉原来Application的Context对象。

到这里我们在插件中就能正常的使用Replugin的启动流程在插件中启动Activity了。

进程管理

初始化

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
// IPC.java
public static void init(Context context) {
sCurrentProcess = SysUtils.getCurrentProcessName();
sCurrentPid = Process.myPid();
sPackageName = context.getApplicationInfo().packageName;

// 判断是否使用“常驻进程”(见PERSISTENT_NAME)作为插件的管理进程
// 并设置常驻进程名称,默认常驻进程名称是以:GuardService结尾的,可以通过
// 宿主module下的build.gradle的repluginHostConfig{}中设置
if (HostConfigHelper.PERSISTENT_ENABLE) {
// public static String PERSISTENT_NAME = ":GuardService";
String cppn = HostConfigHelper.PERSISTENT_NAME;
if (!TextUtils.isEmpty(cppn)) {
if (cppn.startsWith(":")) {
sPersistentProcessName = sPackageName + cppn;
} else {
sPersistentProcessName = cppn;
}
}
} else {
sPersistentProcessName = sPackageName;
}

sIsUIProcess = sCurrentProcess.equals(sPackageName);
sIsPersistentProcess = sCurrentProcess.equals(sPersistentProcessName);
}

进程分类

先来看几个类:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public interface IPluginManager {
// ...

// 自动分配插件进程
int PROCESS_AUTO = Integer.MIN_VALUE;

// UI进程
int PROCESS_UI = -1;

// 常驻进程
int PROCESS_PERSIST = -2;
}

// 用于在宿主中,处理插件自定义多进程相关业务
public class PluginProcessHost {
// 自定义插件的数量,暂时只支持3个自定义进程
public static final int PROCESS_COUNT = 3;

// 自定义进程,int 标识,从 -100 开始,每次加 1;目前只支持 3 个进程,即到 -98
public static final int PROCESS_INIT = -100;

// 插件中,进程名称后缀,进程名称类似 xxx.xx:p0, xxx.xx:p1, xxx.xx:p2
public static final String PROCESS_PLUGIN_SUFFIX = "p";

// 插件中,进程名称后缀(带冒号)
public static final String PROCESS_PLUGIN_SUFFIX2 = ":" + PROCESS_PLUGIN_SUFFIX;

// 保存进程后缀和其 Int 值,如:{":p1":-99, ":p2":-98}
public static final Map<String, Integer> PROCESS_INT_MAP = new HashMap<>();

/**
* 保存进程映射时,符号和实际进程名称的关系
* 如:{"$p1":"com.xx.xxx:p1", "$p2":"com.xx.xxx:p2"}
*/
public static final Map<String, String> PROCESS_ADJUST_MAP = new HashMap<>();

/**
* 保存进程 Int 值与对应 Provider 的 Authority 的关系
* 如:{-99:"com.qihoo360.mobilesafe.Plugin.NP.1", -98:"com.qihoo360.mobilesafe.Plugin.NP.2"}
*/
public static final SparseArray<String> PROCESS_AUTHORITY_MAP = new SparseArray<>();

static {
for (int i = 0; i < PROCESS_COUNT; i++) {
PROCESS_INT_MAP.put(PROCESS_PLUGIN_SUFFIX2 + i, PROCESS_INIT + i);
PROCESS_ADJUST_MAP.put("$" + PROCESS_PLUGIN_SUFFIX + i, IPC.getPackageName() + ":" + PROCESS_PLUGIN_SUFFIX + i);
PROCESS_AUTHORITY_MAP.put(PROCESS_INIT + i, PluginPitProviderBase.AUTHORITY_PREFIX + i);
}
}

// 取进程名称中,冒号及后面的部分,如果进程名中无冒号,则返回原值。
public static String processTail(String processName) {
int indexOfColon = processName.indexOf(':');
if (indexOfColon >= 0) {
processName = processName.toLowerCase();
return processName.substring(indexOfColon);
} else {
return processName;
}
}

// 是否是用户自定义的进程
public static boolean isCustomPluginProcess(int index) {
return index >= PROCESS_INIT && index < PROCESS_INIT + PROCESS_COUNT;
}
}

// Constant.java
// Stub进程数
public static final int STUB_PROCESS_COUNT = 2;
// Stub进程后缀
public static final String STUB_PROCESS_SUFFIX_PATTERN = "^(.*):loader([0-" + (STUB_PROCESS_COUNT - 1) + "])$";

// StubProcessManager.java
static final ProcessRecord STUB_PROCESSES[] = new ProcessRecord[Constant.STUB_PROCESS_COUNT];
static {
for (int i = 0; i < Constant.STUB_PROCESS_COUNT; i++) {
ProcessRecord r = new ProcessRecord(i, StubProcessState.STATE_UNUSED);
STUB_PROCESSES[i] = r;
}
}

// PluginManager.java
static final boolean isPluginProcess(int index) {
return index >= 0 && index < Constant.STUB_PROCESS_COUNT;
}

RePlugin中进程类别:

  • UI进程:process = -1;
  • Persist进程:process = -1;
  • 自定义进程(用户自定义进程):process = -100~-98;
  • Stub进程(插件进程):process = 0~1。

调整插件进程名称

在第一次加载插件时,RePlugin会调整一次插件中进程的名称,用宿主中的进程坑位来接收插件中的自定义进程,如果插件中没有配置静态的meta-data:process_map进行静态的进程映射,则自动为插件中组件分配进程。

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
private void adjustPluginProcess(ApplicationInfo appInfo) {
HashMap<String, String> processMap = getConfigProcessMap(appInfo);
if (processMap == null || processMap.isEmpty()) {
PluginInfo pi = MP.getPlugin(mPluginName, false);
if (pi != null && pi.getFrameworkVersion() >= 4) {
processMap = genDynamicProcessMap();
}
}

doAdjust(processMap, mComponents.getActivityMap());
doAdjust(processMap, mComponents.getServiceMap());
doAdjust(processMap, mComponents.getReceiverMap());
doAdjust(processMap, mComponents.getProviderMap());
}

private HashMap<String, String> getConfigProcessMap(ApplicationInfo appInfo) {
HashMap<String, String> processMap = new HashMap<>();
Bundle bdl = appInfo.metaData;
if (bdl == null || TextUtils.isEmpty(bdl.getString("process_map"))) {
return processMap;
}
try {
String processMapStr = bdl.getString("process_map");
JSONArray ja = new JSONArray(processMapStr);
for (int i = 0; i < ja.length(); i++) {
JSONObject jo = (JSONObject) ja.get(i);
if (jo != null) {
String to = jo.getString("to").toLowerCase();
if (to.equals("$ui")) {
to = IPC.getPackageName();
} else {
// 非 UI 进程,且是用户自定义的进程
if (to.contains("$" + PluginProcessHost.PROCESS_PLUGIN_SUFFIX)) {
to = PluginProcessHost.PROCESS_ADJUST_MAP.get(to);
}
}
processMap.put(jo.getString("from"), to);
}
}
} catch (JSONException e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
return processMap;
}

getConfigProcessMap方法获取插件AndroidMainfest中配置的静态进程映射表meta-data:”process_map”。当配置如下时:

1
2
3
4
5
6
7
8
9
<meta-data
android:name="process_map"
android:value="[
{'from':'com.hearing.plugin1:activity1', 'to':'$ui'},
{'from':'com.hearing.plugin1:activity2', 'to':'$p1'},
{'from':'com.hearing.plugin1:activity3', 'to':'$p0'},
{'from':'com.hearing.plugin1:activity4', 'to':'$p2'},
{'from':'com.hearing.plugin1:activity5', 'to':'$p3'},
]" />

getConfigProcessMap返回如下:

1
2
3
4
5
com.hearing.plugin1:activity1 -> com.hearing.host
com.hearing.plugin1:activity2 -> com.hearing.host:p1
com.hearing.plugin1:activity3 -> com.hearing.host:p0
com.hearing.plugin1:activity4 -> com.hearing.host:p2
com.hearing.plugin1:activity5 -> null

静态/动态配置源码如下:

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
64
65
66
67
68
69
70
71
private HashMap<String, String> genDynamicProcessMap() {
HashMap<String, String> processMap = new HashMap<>();

List<String> hostProcessList = getHostProcessList();
List<String> pluginProcessList = getPluginProcessList();

int hostProcessCount = hostProcessList != null ? hostProcessList.size() : 0;

if (hostProcessCount <= 0) {
return processMap;
}

int pluginProcessCount = pluginProcessList != null ? pluginProcessList.size() : 0;

for (int i = 0; i < pluginProcessCount; i++) {
int hostProcessIndex = i % hostProcessCount;
processMap.put(pluginProcessList.get(i), hostProcessList.get(hostProcessIndex));
}

return processMap;
}

// 获取宿主中可分配的自定义进程列表
private List<String> getHostProcessList() {
List<String> pluginProcessList = new ArrayList<>();
for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) {
pluginProcessList.add(IPC.getPackageName() + PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i);
}
return pluginProcessList;
}

// 读取插件中自定义进程列表
private List<String> getPluginProcessList() {
Set<String> processSet = new HashSet<>();

String pluginUIProcess = mComponents.getApplication().packageName;

getPluginProcess(processSet, mComponents.getProviders());
getPluginProcess(processSet, mComponents.getActivities());
getPluginProcess(processSet, mComponents.getServices());
getPluginProcess(processSet, mComponents.getReceivers());

processSet.remove(pluginUIProcess);

return Arrays.asList(processSet.toArray(new String[0]));
}

private void getPluginProcess(Set<String> processSet, ComponentInfo[] componentInfos) {
if (componentInfos != null) {
for (ComponentInfo componentInfo : componentInfos) {
processSet.add(componentInfo.processName);
}
}
}

private void doAdjust(HashMap<String, String> processMap, HashMap<String, ? extends ComponentInfo> infos) {

if (processMap == null || processMap.isEmpty()) {
return;
}

for (HashMap.Entry<String, ? extends ComponentInfo> entry : infos.entrySet()) {
ComponentInfo info = entry.getValue();
if (info != null) {
String targetProcess = processMap.get(info.processName);
if (!TextUtils.isEmpty(targetProcess)) {
info.processName = targetProcess;
}
}
}
}

请求进程:loadPluginActivity

在插件的Activity启动过程中,会调用loadPluginActivity方法(删减),该方法会先启动目标进程:

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
// PluginCommImpl.java
// process = IPluginManager.PROCESS_AUTO
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);
ActivityInfo ai = getActivityInfo(plugin, activity, intent);
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
container = client.allocActivityContainer(plugin, process, ai.name, intent);
}

// 通过插件中的进程映射,得到自定义的进程号
public static Integer getProcessInt(String processName) {
if (!TextUtils.isEmpty(processName)) {
// 插件若想将组件定义成在"常驻进程"中运行,则可以在android:process中定义:

// 1. (推荐)":GuardService"。这样无论宿主的常驻进程名是什么,都会定向到"常驻进程"
String pntl = processName.toLowerCase();
String ppdntl = HostConfigHelper.PERSISTENT_NAME.toLowerCase();
if (pntl.contains(ppdntl)) {
return IPluginManager.PROCESS_PERSIST;
}

// 2. 和宿主常驻进程名相同,这样也会定向到"常驻进程",但若移植到其它宿主上则会出现问题
String ppntl = IPC.getPersistentProcessName().toLowerCase();
if (TextUtils.equals(pntl, ppntl)) {
return IPluginManager.PROCESS_PERSIST;
}

// 3. 用户自定义进程时,从 Map 中取数据
// 取进程名称中,冒号及后面的部分,如果进程名中无冒号,则返回原值。
processName = PluginProcessHost.processTail(processName.toLowerCase());
if (PROCESS_INT_MAP.containsKey(processName)) {
return PROCESS_INT_MAP.get(processName);
}
}
return IPluginManager.PROCESS_UI;
}

常驻进程:启动目标进程

startPluginProcess

通过Binder调用,启动插件进程的工作交由常驻进程完成。

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
// MP.java
public static final IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
return PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);
}

class PmHostSvc extends IPluginHost.Stub {
@Override
public IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
return mPluginMgr.startPluginProcessLocked(plugin, process, info);
}
}

// Pmbase.java
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
// 强制使用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
if (info.request == PluginBinderInfo.BINDER_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
}

StubProcessManager.schedulePluginProcessLoop(StubProcessManager.CHECK_STAGE1_DELAY);

// 获取
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client != null) {
return client;
}

// 分配
int index = IPluginManager.PROCESS_AUTO;
try {
index = PluginProcessMain.allocProcess(plugin, process);
} catch (Throwable e) {
}
// 分配的坑位不属于UI、自定义进程或Stub坑位进程,就返回。(没找到有效进程)
if (!(index == IPluginManager.PROCESS_UI
|| PluginProcessHost.isCustomPluginProcess(index)
|| PluginManager.isPluginProcess(index))) {
return null;
}

// 启动
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
if (!rc) {
return null;
}

// 再次获取
client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client == null) {
return null;
}
return client;
}

PluginProcessMain.allocProcess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PLUGIN_NAME_UI = "ui"
static final int allocProcess(String plugin, int process) {
if (Constant.PLUGIN_NAME_UI.equals(plugin) || process == IPluginManager.PROCESS_UI) {
return IPluginManager.PROCESS_UI;
}

if (PluginProcessHost.isCustomPluginProcess(process)) {
return process;
}

PluginInfo info = PluginTable.getPluginInfo(plugin);
if (info == null) {
return IPluginManager.PROCESS_AUTO;
}
return StubProcessManager.allocProcess(plugin);
}

下面是Stub进程的分配:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
static final int allocProcess(String plugin) {
// 取运行列表
List<ActivityManager.RunningAppProcessInfo> processes = AMSUtils.getRunningAppProcessesNoThrows(RePluginInternal.getAppContext());
// 取运行列表失败,则直接返回失败
if (processes == null || processes.isEmpty()) {
return IPluginManager.PROCESS_AUTO;
}
// 根据优先级分配坑位进程
int prevMatchPriority = -1; // 临时变量,保存上一个ProcessRecord的进程分配优先级
ProcessRecord selectRecord = null; // 被选中的坑位进程
for (ProcessRecord r : STUB_PROCESSES) {
synchronized (r) {
if (r.calculateMatchPriority(plugin) > prevMatchPriority) {
prevMatchPriority = r.calculateMatchPriority(plugin);
selectRecord = r;
} else if (r.calculateMatchPriority(plugin) == prevMatchPriority) {
if (r.mobified < selectRecord.mobified) {
selectRecord = r;
}
}
}
}
if (selectRecord == null) { // 不应该出现
return IPluginManager.PROCESS_AUTO;
}
synchronized (selectRecord){
// 插件已在分配进程中运行,直接返回
if (selectRecord.calculateMatchPriority(plugin) == Integer.MAX_VALUE && (selectRecord.state == StubProcessState.STATE_ALLOCATED || selectRecord.state == StubProcessState.STATE_RUNNING))
{
return selectRecord.index;
}
selectRecord.resetAllocate(plugin, processes);
return selectRecord.index;
}
}

int calculateMatchPriority(String newPluginName) {
int priority = Integer.MAX_VALUE;
if (TextUtils.equals(newPluginName, plugin)) { // 插件可能用过的进程
return priority;
}
if (state == StubProcessState.STATE_UNUSED) { // 空闲的进程
priority = Integer.MAX_VALUE - 1;
return priority;
}
if (state == StubProcessState.STATE_STOPED) { // 已停止的进程
priority = Integer.MAX_VALUE - 2;
return priority;
}
if ((System.currentTimeMillis() - mobified) > 10 * 1000) { // 分配时间超过10秒的
priority = Integer.MAX_VALUE - 3;
return priority;
}
if ((activities <= 0) && (services <= 0) && (binders <= 0)) { // 组件为空的
priority = Integer.MAX_VALUE - 4;
return priority;
}
priority = 0; // 默认值
return priority;
}

private static final class ProcessRecord {
void resetAllocate(String plugin, List<ActivityManager.RunningAppProcessInfo> processes) {
killProcess(processes);
allocate(plugin);
}

void allocate(String plugin) {
this.state = StubProcessState.STATE_ALLOCATED;
this.mobified = System.currentTimeMillis();
this.plugin = plugin;
this.pid = 0;
this.binder = null;
this.client = null;
this.activities = 0;
this.services = 0;
this.binders = 0;
}
}

PluginProviderStub.proxyStartPluginProcess

PluginProviderStub.proxyStartPluginProcess方法用于启动进程:

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
static final boolean proxyStartPluginProcess(Context context, int index) {
ContentValues values = new ContentValues();
values.put(KEY_METHOD, METHOD_START_PROCESS);
values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie);
Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values);
if (uri == null) {
return false;
}
return true;
}

public class ProcessPitProviderBase extends ContentProvider {
private static final String TAG = "ProviderBase";
public static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".loader.p.main";

public static final Uri buildUri(int index) {
String str = "";
if (index < 0) {
str += "N";
index *= -1;
}
str += index;
Uri uri = Uri.parse("content://" + AUTHORITY_PREFIX + str + "/main");
if (BuildConfig.DEBUG) {
Log.d(TAG, "buildUri: uri=" + uri);
}
return uri;
}

@Override
public boolean onCreate() {
return true;
}

@Override
public String getType(Uri uri) {
return null;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
return PluginProviderStub.stubPlugin(uri, values);
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
}

ProcessPitProviderBase有几个空子类,host gradle插件会将这些空子类注册在对应的进程:

1
2
3
4
5
6
7
8
ContentProvider (android.content)
ProcessPitProviderBase (com.qihoo360.replugin.component.process)
ProcessPitProviderUI (com.qihoo360.replugin.component.process)
ProcessPitProviderP0 (com.qihoo360.replugin.component.process)
ProcessPitProviderLoader1 (com.qihoo360.replugin.component.process)
ProcessPitProviderLoader0 (com.qihoo360.replugin.component.process)
ProcessPitProviderP2 (com.qihoo360.replugin.component.process)
ProcessPitProviderP1 (com.qihoo360.replugin.component.process)

将host apk解包可以看到:

1
2
3
4
5
6
7
8
9
10
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderUI"
android:exported="false"
android:authorities="com.hearing.host.loader.p.mainN1" />
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP1"
android:exported="false"
android:process=":p1"
android:authorities="com.hearing.host.loader.p.mainN99" />
<!-- -->

通过调用注册在指定进程的ContentProvider的insert方法,如果目标进程没有被启动,则会被先启动,然后执行insert方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// PluginProviderStub.java
public static final Uri stubPlugin(Uri uri, ContentValues values) {
String method = values.getAsString(KEY_METHOD);

if (TextUtils.equals(method, METHOD_START_PROCESS)) {
Uri rc = new Uri.Builder().scheme("content").authority("process").encodedPath("status").encodedQuery(URL_PARAM_KEY_LOADED + "=" + 1).build();
long cookie = values.getAsLong(KEY_COOKIE);

// 首次
if (PMF.sPluginMgr.mLocalCookie == 0L) {
PMF.sPluginMgr.mLocalCookie = cookie;
} else {
// 常驻进程重新启动了
if (PMF.sPluginMgr.mLocalCookie != cookie) {
PMF.sPluginMgr.mLocalCookie = cookie;
PluginProcessMain.connectToHostSvc();
}
}
return rc;
}
return null;
}

借用ContentProvider,目标进程就被启动了,然后在目标进程中通过PluginProcessMain.connectToHostSvc()获取常驻进程的IPluginHost的Binder对象,用来进行IPC调用。

PluginProcessMain.probePluginClient

PluginProcessMain.probePluginClient方法用来获取IPluginClient对象,其实现是PluginProcessPer类。

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
// 当前运行的所有进程的列表(常驻进程除外)
// 在目标进程启动后,PluginProcessMain.connectToHostSvc()方法中会将ProcessClientRecord插入ALL中
private static final Map<String, ProcessClientRecord> ALL = new HashMap<String, ProcessClientRecord>();

static final IPluginClient probePluginClient(final String plugin, final int process, final PluginBinderInfo info) {
return readProcessClientLock(new Action<IPluginClient>() {
@Override
public IPluginClient call() {
for (ProcessClientRecord r : ALL.values()) {
if (process == IPluginManager.PROCESS_UI) {
if (!TextUtils.equals(r.plugin, Constant.PLUGIN_NAME_UI)) {
continue;
}
/* 是否是用户自定义进程 */
} else if (PluginProcessHost.isCustomPluginProcess(process)) {
if (!TextUtils.equals(r.plugin, getProcessStringByIndex(process))) {
continue;
}
} else {
if (!TextUtils.equals(r.plugin, plugin)) {
continue;
}
}
if (!isBinderAlive(r)) {
return null;
}
if (!r.binder.pingBinder()) {
return null;
}
info.pid = r.pid;
info.index = r.index;
return r.client;
}
return null;
}
});
}

图解

自定义进程Activity

  1. 在宿主中或插件中,通过RePlugin.startActivity()启动插件中的Activity,会先入常驻进程(或者叫插件管理进程)。
  2. 在插件管理进程中,插件管理器,会为该Activity分配一个目标进程。并启动该目标进程(通过ContentProvider)。
  3. 在插件管理进程中,通过Binder调用,使用目标进程留在插件管理进程中的Binder代理,在目标进程中为该Activity动态分配坑位(每个进程中独立管理自己的Activity坑位信息,此时该坑位Activity还没有被AMS打开)。
  4. 把在目标进程中分配好的Activity坑位信息返回给调用方。
  5. 调用者获取到了一个坑位Activity,然后会把这个坑位Activity(带着自定义进程标签),会交给AMS,由于AMS去启动。
  6. 通过类加载器机制,加载插件Activity类。

插件/宿主间Binder

宿主RePlugin类中,在宿主内注册一个可被其它插件所获取的Binder的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void registerHostBinder(IHostBinderFetcher hbf) {
MP.installBuiltinPlugin("main", hbf);
}

public static final void installBuiltinPlugin(String name, IHostBinderFetcher p) {
PMF.sPluginMgr.installBuiltinPlugin(name, p);
}

private final HashMap<String, IHostBinderFetcher> mBuiltinPlugins;
final void installBuiltinPlugin(String name, IHostBinderFetcher p) {
synchronized (mBuiltinPlugins) {
mBuiltinPlugins.put(name, p);
}
}

获取上面的Binder对象:

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
public static IBinder fetchBinder(String pluginName, String module) {
return Factory.query(pluginName, module);
}

public static final IBinder query(String name, String binder) {
return sPluginManager.query(name, binder);
}

public IBinder query(String name, String binder) {
// 先用仿插件对象判断
{
IHostBinderFetcher p = mPluginMgr.getBuiltinPlugin(name);
if (p != null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "use buildin plugin");
}
return p.query(binder);
}
}

Plugin p = mPluginMgr.loadAppPlugin(name);
if (p == null) {
return null;
}

return p.query(binder);
}

final IHostBinderFetcher getBuiltinPlugin(String plugin) {
synchronized (mBuiltinPlugins) {
return mBuiltinPlugins.get(plugin);
}
}

注册一个无需插件名,可被全局使用的Binder对象:

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
public static boolean registerGlobalBinder(String name, IBinder binder) {
return QihooServiceManager.addService(RePluginInternal.getAppContext(), name, binder);
}

public static boolean addService(Context context, String serviceName, IBinder service) {
IServiceChannel serviceChannel = getServerChannel(context);
if (serviceChannel == null) {
return false;
}

try {
serviceChannel.addService(serviceName, service);
} catch (RemoteException e) {
}
return true;
}

static IServiceChannel getServerChannel(Context context) {
IServiceChannel serviceChannel = null;
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(getServiceChannelUri(), null, null, null, null);
IBinder binder = ServiceChannelCursor.getBinder(cursor);
serviceChannel = IServiceChannel.Stub.asInterface(binder);
sServerChannel = serviceChannel;
} catch (Exception e) {
} finally {
}
return serviceChannel;
}

public class ServiceProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return ServiceChannelImpl.sServiceChannelCursor;
}
}

sServiceChannelCursor对象实现如下:

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
static IServiceChannel.Stub sServiceChannelImpl = new IServiceChannel.Stub() {

@Override
public IBinder getService(String serviceName) throws RemoteException {
IBinder service = sServices.get(serviceName);

// 若没有注册此服务,则尝试从“延迟IBinder”中获取
if (service == null) {
return fetchFromDelayedMap(serviceName);
}

// 判断Binder是否挂掉
if (!service.isBinderAlive() || !service.pingBinder()) {
sServices.remove(serviceName);
return null;
} else {
return service;
}
}

@Override
public void addService(String serviceName, IBinder service) throws RemoteException {
sServices.put(serviceName, service);
}

@Override
public void removeService(String serviceName) throws RemoteException {
sServices.remove(serviceName);
}
};

static MatrixCursor sServiceChannelCursor = ServiceChannelCursor.makeCursor(sServiceChannelImpl);

获取上面的全局Binder:

1
2
3
public static IBinder getGlobalBinder(String name) {
return QihooServiceManager.getService(RePluginInternal.getAppContext(), name);
}

在插件RePlugin中调用的是反射宿主中的方法。

资源

1
2
3
4
5
// RePlugin
// 加载插件,并获取插件的资源信息
public static Resources fetchResources(String pluginName) {
return Factory.queryPluginResouces(pluginName);
}

这里插件的 Resources 是通过如下代码获取的:

1
2
3
4
5
6
7
8
9
mPackageInfo = pm.getPackageArchiveInfo(mPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;

if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) {
mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName;
}

mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);

拿到 Resources 后即可通过 Resources.getIdentifier() 方法拿到资源 id 了。

可以自己尝试用这种方式加载未安装 apk 中的资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(APK_PATH, PackageManager.GET_ACTIVITIES);
packageInfo.applicationInfo.sourceDir = APK_PATH;
packageInfo.applicationInfo.publicSourceDir = APK_PATH;
if (TextUtils.isEmpty(packageInfo.applicationInfo.processName)) {
packageInfo.applicationInfo.processName = packageInfo.applicationInfo.packageName;
}
try {
Resources resources = getPackageManager().getResourcesForApplication(packageInfo.applicationInfo);
int drawable = resources.getIdentifier("mytest", "drawable", packageInfo.packageName);
ImageView imageView = findViewById(R.id.image_view);
imageView.setImageDrawable(resources.getDrawable(drawable));
// imageView.setImageResource(drawable); // 加载不了
} catch (Exception e) {
e.printStackTrace();
}

在 RePlugin 中都是通过指定插件名去获取对应插件里的资源的,并没有直接查询所有插件的 Resource 中的资源,插件资源之间比较独立,因此不存在插件之间资源 id 冲突的问题,也就不需要修改 aapt 的方式解决资源冲突。

replugin-host-gradle

概述

replugin-host-gradle 是 RePlugin 插件框架中的宿主gradle插件,主要用于在宿主应用的编译期常规构建任务流中,插入一些定制化的构建任务,以便实现自动化编译期修改宿主应用的目的。

  • 生成带 RePlugin 插件坑位的 AndroidManifest.xml(允许自定义数量)
  • 生成 RepluginHostConfig 类,方便插件框架读取并自定义其属性
  • 生成 plugins-builtin.json,json中含有插件应用的信息,包名,插件名,插件路径等。

目录概览:

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
\qihoo\RePlugin\replugin-host-gradle\src

└─main
├─groovy
│ └─com.qihoo360.replugin.gradle.host
│ │ AppConstant.groovy # 程序常量定义区
│ │ RePlugin.groovy # 针对宿主的特定构建任务创建及调度
│ │
│ ├─creator
│ │ │ FileCreators.groovy # 组装生成器
│ │ │ IFileCreator.groovy # 文件生成器接口
│ │ │
│ │ └─impl
│ │ ├─java
│ │ │ RePluginHostConfigCreator.groovy # RePluginHostConfig.java 生成器
│ │ │
│ │ └─json
│ │ PluginBuiltinJsonCreator.groovy # plugins-builtin.json 生成器
│ │ PluginInfo.groovy # 插件信息模型
│ │ PluginInfoParser.groovy # 从 manifest 的 xml 中抽取 PluginInfo信息
│ │
│ └─handlemanifest
│ ComponentsGenerator.groovy # 动态生成插件化框架中需要的组件

└─resources
└─META-INF
└─gradle-plugins
replugin-host-gradle.properties # 指定 gradle 插件实现类

与gradle插件相关的可以参考:gradle自定义插件,与自定义gradle Extension相关的可以参考:gradle自定义Extension。gradle插件的入口自然是 class Replugin implements Plugin<Project> 的apply方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class Replugin implements Plugin<Project> {

def static TAG = AppConstant.TAG
def project
def config

@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "
// ...
}
}

生成带坑位AndroidManifest.xml

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
@Override
public void apply(Project project) {
println "${TAG} Welcome to replugin world ! "

this.project = project
// 创建自定义的extension,用于在gradle脚本中添加名为repluginHostConfig的命名空间
project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)

if (project.plugins.hasPlugin(AppPlugin)) {

def android = project.extensions.getByType(AppExtension)
// 遍历android命名空间下的内容
android.applicationVariants.all { variant ->
// 添加 【查看所有插件信息】 任务
addShowPluginTask(variant)

if (config == null) {
config = project.extensions.getByName(AppConstant.USER_CONFIG)
// 检验repluginHostConfig命名空间下的参数配置
checkUserConfig(config)
}

def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
def appID = generateBuildConfigTask.appPackageName
// 生成组件坑位的xml代码,最终的xml文件是在后续的task中拼装出来的,稍后会讲到
def newManifest = ComponentsGenerator.generateComponent(appID, config)
println "${TAG} countTask=${config.countTask}"
// ...
}
}
}
  • project.plugins.hasPlugin(AppPlugin):判断project中是否含有AppPlugin类型插件,即是否有’application’ projects类型的Gradle plugin。
  • project.plugins.hasPlugin(LibraryPlugin):判断project中是否含有LibraryPlugin类型插件,即是否有’library’ projects类型的Gradle plugin。

ComponentsGenerator.generateComponent方法是使用了基于Groovy的MarkupBuilder api生成坑位。

生成RePluginHostConfig配置文件

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
@Override
public void apply(Project project) {
// ...
if (project.plugins.hasPlugin(AppPlugin)) {

def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
// ...

def variantData = variant.variantData
def scope = variantData.scope

// 指定task名字并创建此Task:rpGenerateHostConfig
def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
def generateHostConfigTask = project.task(generateHostConfigTaskName)

generateHostConfigTask.doLast {
// 自动创建RePluginHostConfig.java至BuildConfig目录
FileCreators.createHostConfig(project, variant, config)
}
generateHostConfigTask.group = AppConstant.TASKS_GROUP

//depends on build config task
if (generateBuildConfigTask) {
// 此task依赖于生成 BuildConfig.java 的task
generateHostConfigTask.dependsOn generateBuildConfigTask
// 设置为 BuildConfigTask 执行完后,就执行HostConfigTask
generateBuildConfigTask.finalizedBy generateHostConfigTask
}
// ...
}
}
}

生成插件信息json文件

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
@Override
public void apply(Project project) {
// ...
if (project.plugins.hasPlugin(AppPlugin)) {

def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
// ...
// 指定task名字并创建此Task:rpGenerateBuiltinJson
def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)

generateBuiltinJsonTask.doLast {
// 扫描宿主/assets/plugins目录下的插件文件,并基于apk文件规则解析出插件信息,包名,版本号等,然后拼装成json文件
FileCreators.createBuiltinJson(project, variant, config)
}
generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP

// 此task依赖于merge assets文件 的task并设置为 mergeAssetsTask 执行完后,就执行BuiltinJsonTask
def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant)
if (mergeAssetsTask) {
generateBuiltinJsonTask.dependsOn mergeAssetsTask
mergeAssetsTask.finalizedBy generateBuiltinJsonTask
}
// ...
}
}
}

拼装AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void apply(Project project) {
// ...
if (project.plugins.hasPlugin(AppPlugin)) {

def android = project.extensions.getByType(AppExtension)
android.applicationVariants.all { variant ->
// ...
// 将坑位 xml 字符串 与 原有xml <application></application> 标签内的配置信息合二为一
variant.outputs.each { output ->
VariantCompat.getProcessManifestTask(output).doLast {
println "${AppConstant.TAG} processManifest: ${it.outputs.files}"
it.outputs.files.each { File file ->
updateManifest(file, newManifest)
}
}
}
}
}
}

replugin-plugin-gradle

概述

replugin-plugin-gradle 是 RePlugin 插件框架中提供给replugin插件用的gradle插件,是一种动态编译方案实现。主要在插件应用的编译期,基于Transform api 注入到编译流程中, 再通过Java字节码类库对编译中间环节的 Java 字节码文件进行修改,以便实现编译期动态修改插件应用的目的。

  • LoaderActivityInjector: 动态将插件中的Activity的继承相关代码 修改为 replugin-plugin-library 中的XXPluginActivity父类
  • LocalBroadcastInjector: 替换 插件中的LocalBroadcastManager调用代码 为 插件库的调用代码。
  • ProviderInjector: 替换 插件中的 ContentResolver 调用代码 为 插件库的调用代码
  • ProviderInjector2: 替换 插件中的 ContentProviderClient 调用代码 为 插件库的调用代码
  • GetIdentifierInjector: 替换 插件中的 Resource.getIdentifier 调用代码的参数 为 动态适配的参数

目录概览:

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
\qihoo\replugin\replugin-plugin-gradle\src
└─main
├─groovy
│ └─com.qihoo360.replugin.gradle.plugin
│ │ AppConstant.groovy # 程序常量定义区
│ │ ReClassPlugin.groovy # 插件动态编译方案入口
│ │
│ ├─debugger
│ │ PluginDebugger.groovy # 用于插件调试的gradle task实现
│ │
│ ├─injector
│ │ │ BaseInjector.groovy # 注入器基类
│ │ │ IClassInjector.groovy # 注入器接口类
│ │ │ Injectors.groovy # 注入器枚举类,定义了全部注入器
│ │ │
│ │ ├─identifier
│ │ │ GetIdentifierExprEditor.groovy # javassist 允许修改方法里的某个表达式,此类为替换 getIdentifier 方法中表达式的实现类
│ │ │ GetIdentifierInjector.groovy # GetIdentifier 方法注入器
│ │ │
│ │ ├─loaderactivity
│ │ │ LoaderActivityInjector.groovy # Activity代码注入器
│ │ │
│ │ ├─localbroadcast
│ │ │ LocalBroadcastExprEditor.groovy # 替换几个广播相关方法表达式的实现类
│ │ │ LocalBroadcastInjector.groovy # 广播代码注入器
│ │ │
│ │ └─provider
│ │ ProviderExprEditor.groovy # 替换ContentResolver类的几个方法表达式
│ │ ProviderExprEditor2.groovy # 替换ContentProviderClient类的几个方法表达式
│ │ ProviderInjector.groovy # Provider之ContentResolver代码注入器
│ │ ProviderInjector2.groovy # Provider之ContentProviderClient代码注入器
│ │
│ ├─inner
│ │ ClassFileVisitor.groovy # 类文件遍历类
│ │ CommonData.groovy # 实体类
│ │ ReClassTransform.groovy # 核心类,基于 transform api 实现动态修改class文件的总调度入口
│ │ Util.groovy # 工具类
│ │
│ ├─manifest
│ │ IManifest.groovy # 接口类
│ │ ManifestAPI.groovy # 操作Manifest的API类
│ │ ManifestReader.groovy # Manifest读取工具类
│ │
│ └─util
│ CmdUtil.groovy # 命令行工具类

└─resources
└─META-INF.gradle-plugins
replugin-plugin-gradle.properties # 指定 gradle 插件实现类

replugin-plugin-gradle插件的工作流:基于Gradle的Transform API,在编译期的构建任务流中,class转为dex之前,插入一个Transform,并在此Transform流中,基于Javassist实现对字节码文件的注入。

生成用于调试的task

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
@Override
public void apply(Project project) {

println "${AppConstant.TAG} Welcome to replugin world ! "

// 创建repluginPluginConfig命名空间的extension
project.extensions.create(AppConstant.USER_CONFIG, ReClassConfig)

def isApp = project.plugins.hasPlugin(AppPlugin)
if (isApp) {

def config = project.extensions.getByName(AppConstant.USER_CONFIG)

def android = project.extensions.getByType(AppExtension)

def forceStopHostAppTask = null
def startHostAppTask = null
def restartHostAppTask = null
// 遍历android命名空间下的variants组合
android.applicationVariants.all { variant ->
PluginDebugger pluginDebugger = new PluginDebugger(project, config, variant)

def variantData = variant.variantData
def scope = variantData.scope

def assembleTask = VariantCompat.getAssembleTask(variant)

def installPluginTaskName = scope.getTaskName(AppConstant.TASK_INSTALL_PLUGIN, "")
def installPluginTask = project.task(installPluginTaskName)

installPluginTask.doLast {
pluginDebugger.startHostApp()
pluginDebugger.uninstall()
pluginDebugger.forceStopHostApp()
pluginDebugger.startHostApp()
pluginDebugger.install()
}
installPluginTask.group = AppConstant.TASKS_GROUP
// ...
}
// ...
}
}
  • new PluginDebugger(project, config, variant): 初始化PluginDebugger类实例,主要配置了最终生成的插件应用的文件路径,以及adb文件的路径,是为了后续基于adb命令做push apk到SD卡上做准备。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public PluginDebugger(Project project, def config, def variant) {
    // ...
    File apkDir = new File(globalScope.getBuildDir(), "outputs" + File.separator + "apk")
    String unsigned = (variantConfiguration.getSigningConfig() == null
    ? "-unsigned.apk"
    : ".apk");
    String apkName = apkBaseName + unsigned
    apkFile = new File(apkDir, apkName)

    if (!apkFile.exists() || apkFile.length() == 0) {
    apkFile = new File(apkDir, variantConfiguration.getBaseName() + File.separator + apkName)
    }

    adbFile = ScopeCompat.getAdbExecutable(globalScope)
    }
  • def assembleTask = VariantCompat.getAssembleTask(variant),获取assemble task(即打包apk的task),后续的task需要依赖此task,比如安装插件的task,肯定要等到assemble task打包生成apk后,才能去执行。
  • 生成installPluginTask的gradle task名字: rpInstallPlugin,并创建此Task。然后指定此task的任务内容:
    1
    2
    3
    4
    5
    6
    7
    installPluginTask.doLast {
    pluginDebugger.startHostApp()
    pluginDebugger.uninstall()
    pluginDebugger.forceStopHostApp()
    pluginDebugger.startHostApp()
    pluginDebugger.install()
    }
  • 流程:启动宿主 -> 卸载插件 -> 强制停止宿主 -> 启动宿主 -> 安装插件
  • pluginDebugger 内的方法实现:基于adb shell + am 命令,实现 发送广播,push apk 等功能。比如:pluginDebugger.startHostApp()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public boolean startHostApp() {
    if (isConfigNull()) {
    return false
    }

    String cmd = "${adbFile.absolutePath} shell am start -n \"${config.hostApplicationId}/${config.hostAppLauncherActivity}\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER"
    if (0 != CmdUtil.syncExecute(cmd)) {
    return false
    }
    return true
    }

调试方案的整体原理:

  1. replugin-host-lib 的DebuggerReceivers类中,注册了一系列用于快速调试的广播,而replugin-host-lib是会内置在宿主应用中的。
  2. replugin-plugin-gradle 中创建了一系列gradle task,用于启动停止重启宿主应用,安装卸载运行插件应用。这些gradle task都是被动型task,需要通过命令行主动的运行这些task。
  3. 打开命令行终端,执行replugin插件项目的某个gradle task,以实现快速调试功能。比如:gradlew.bat rpInstallPluginDebug,最终就会将宿主和插件运行起来。
  4. 这些gradle task被手动执行后,task会执行一系列任务,比如通过adb push 插件到sdcard,或通过am命令发送广播,启动activity等。当发送一系列步骤1中注册的广播后,宿主应用收到广播后会执行对应的操作,比如启动插件的activity等。

动态编译方案

transform

1
2
3
4
5
6
7
8
9
@Override
public void apply(Project project) {
// ...
if (isApp) {
// ...
def transform = new ReClassTransform(project)
android.registerTransform(transform)
}
}

看一下ReClassTransform的核心方法transform():

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
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException {

welcome()

/* 读取用户配置 */
def config = project.extensions.getByName('repluginPluginConfig')

File rootLocation = null
try {
rootLocation = outputProvider.rootLocation
} catch (Throwable e) {
//android gradle plugin 3.0.0+ 修改了私有变量,将其移动到了IntermediateFolderUtils中去
rootLocation = outputProvider.folderUtils.getRootFolder()
}
if (rootLocation == null) {
throw new GradleException("can't get transform root location")
}
println ">>> rootLocation: ${rootLocation}"
// Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote'
def variantDir = rootLocation.absolutePath.split(getName() + Pattern.quote(File.separator))[1]
println ">>> variantDir: ${variantDir}"

CommonData.appModule = config.appModule
CommonData.ignoredActivities = config.ignoredActivities
// 返回用户未忽略的注入器的集合,因为在repluginPluginConfig中可以配置忽略配置
def injectors = includedInjectors(config, variantDir)
if (injectors.isEmpty()) {
copyResult(inputs, outputProvider) // 跳过 reclass
} else {
doTransform(inputs, outputProvider, config, injectors) // 执行 reclass
}
}

doTransform

这里会遍历除了用户已忽略过的全部代码注入器,依次执行每个注入器的特定注入任务。

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
def doTransform(Collection<TransformInput> inputs,
TransformOutputProvider outputProvider,
Object config,
def injectors) {

/* 初始化 ClassPool */
Object pool = initClassPool(inputs)

/* 进行注入操作 */
Util.newSection()
Injectors.values().each {
if (it.nickName in injectors) {
println ">>> Do: ${it.nickName}"
// 将 NickName 的第 0 个字符转换成小写,用作对应配置的名称
def configPre = Util.lowerCaseAtIndex(it.nickName, 0)
doInject(inputs, pool, it.injector, config.properties["${configPre}Config"])
} else {
println ">>> Skip: ${it.nickName}"
}
}

if (config.customInjectors != null) {
config.customInjectors.each {
doInject(inputs, pool, it)
}
}

/* 重打包 */
repackage()

/* 拷贝 class 和 jar 包 */
copyResult(inputs, outputProvider)

Util.newSection()
}

doInject

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
def doInject(Collection<TransformInput> inputs, ClassPool pool,
IClassInjector injector, Object config) {
try {
inputs.each { TransformInput input ->
input.directoryInputs.each {
handleDir(pool, it, injector, config)
}
input.jarInputs.each {
handleJar(pool, it, injector, config)
}
}
} catch (Throwable t) {
println t.toString()
}
}

// 处理目录中的 class 文件
def handleDir(ClassPool pool, DirectoryInput input, IClassInjector injector, Object config) {
println ">>> Handle Dir: ${input.file.absolutePath}"
injector.injectClass(pool, input.file.absolutePath, config)
}

def handleJar(ClassPool pool, JarInput input, IClassInjector injector, Object config) {
File jar = input.file
if (jar.absolutePath in includeJars) {
println ">>> Handle Jar: ${jar.absolutePath}"
String dirAfterUnzip = map.get(jar.getParent() + File.separatorChar + jar.getName()).replace('.jar', '')
injector.injectClass(pool, dirAfterUnzip, config)
}
}

LoaderActivityInjector

用来修改插件中XXActivity类中的顶级XXActivity父类 为 XXPluginActivity父类:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class LoaderActivityInjector extends BaseInjector {

/* LoaderActivity 替换规则 */
def private static loaderActivityRules = [
'android.app.Activity' : 'com.qihoo360.replugin.loader.a.PluginActivity',
'android.app.TabActivity' : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
'android.app.ListActivity' : 'com.qihoo360.replugin.loader.a.PluginListActivity',
'android.app.ActivityGroup' : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
'android.preference.PreferenceActivity' : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
'android.app.ExpandableListActivity' : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
]

@Override
def injectClass(ClassPool pool, String dir, Map config) {
init()

/* 遍历程序中声明的所有 Activity */
// 每次都new一下,否则多个variant一起构建时只会获取到首个manifest
new ManifestAPI().getActivities(project, variantDir).each {
// 处理没有被忽略的 Activity
if (!(it in CommonData.ignoredActivities)) {
handleActivity(pool, it, dir)
}
}
}

private def handleActivity(ClassPool pool, String activity, String classesDir) {
def clsFilePath = classesDir + File.separatorChar + activity.replaceAll('\\.', '/') + '.class'
if (!new File(clsFilePath).exists()) {
return
}

println ">>> Handle $activity"

def stream, ctCls
try {
stream = new FileInputStream(clsFilePath)
ctCls = pool.makeClass(stream);

// ctCls 之前的父类
def originSuperCls = ctCls.superclass

/* 从当前 Activity 往上回溯,直到找到需要替换的 Activity */
def superCls = originSuperCls
while (superCls != null && !(superCls.name in loaderActivityRules.keySet())) {
// println ">>> 向上查找 $superCls.name"
ctCls = superCls
superCls = ctCls.superclass
}

// 如果 ctCls 已经是 LoaderActivity,则不修改
if (ctCls.name in loaderActivityRules.values()) {
return
}

/* 找到需要替换的 Activity, 修改 Activity 的父类为 LoaderActivity */
if (superCls != null) {
def targetSuperClsName = loaderActivityRules.get(superCls.name)
CtClass targetSuperCls = pool.get(targetSuperClsName)

if (ctCls.isFrozen()) {
ctCls.defrost()
}
ctCls.setSuperclass(targetSuperCls)

// 修改声明的父类后,还需要方法中所有的 super 调用。
ctCls.getDeclaredMethods().each { outerMethod ->
outerMethod.instrument(new ExprEditor() {
@Override
void edit(MethodCall call) throws CannotCompileException {
if (call.isSuper()) {
if (call.getMethod().getReturnType().getName() == 'void') {
call.replace('{super.' + call.getMethodName() + '($$);}')
} else {
call.replace('{$_ = super.' + call.getMethodName() + '($$);}')
}
}
}
})
}

ctCls.writeFile(CommonData.getClassPath(ctCls.name))
println " Replace ${ctCls.name}'s SuperClass ${superCls.name} to ${targetSuperCls.name}"
}
} catch (Throwable t) {
println " [Warning] --> ${t.toString()}"
} finally {
if (ctCls != null) {
ctCls.detach()
}
if (stream != null) {
stream.close()
}
}
}
}

关于Jar包中的class注入:在initClassPool时已经把Jar做了unzip,解压出也是一堆.class文件,其他处理逻辑同上。也就是说,你引用的第三方sdk中的jar,以及你依赖的库中的jar,都会被注入器撸一遍。

注意:在Androidx中其包名不一样。

LocalBroadcastInjector

替换插件中的 LocalBroadcastManager的方法调用 为 插件库的PluginLocalBroadcastManager中的方法调用。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class LocalBroadcastInjector extends BaseInjector {

@Override
def injectClass(ClassPool pool, String dir, Map config) {

if (editor == null) {
editor = new LocalBroadcastExprEditor()
}

Util.newSection()
println dir

Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

String filePath = file.toString()
editor.filePath = filePath

def stream, ctCls
try {
// 不处理 LocalBroadcastManager.class
// 保护性逻辑,避免替换掉v4包中的源码实现
if (filePath.contains('android/support/v4/content/LocalBroadcastManager')) {
println "Ignore ${filePath}"
return super.visitFile(file, attrs)
}

stream = new FileInputStream(filePath)
ctCls = pool.makeClass(stream);

// println ctCls.name
if (ctCls.isFrozen()) {
ctCls.defrost()
}

/* 检查方法列表 */
ctCls.getDeclaredMethods().each {
it.instrument(editor)
}

ctCls.getMethods().each {
it.instrument(editor)
}

ctCls.writeFile(dir)
} catch (Throwable t) {
println " [Warning] --> ${t.toString()}"
// t.printStackTrace()
} finally {
if (ctCls != null) {
ctCls.detach()
}
if (stream != null) {
stream.close()
}
}

return super.visitFile(file, attrs)
}
})
}
}

public class LocalBroadcastExprEditor extends ExprEditor {

static def TARGET_CLASS = 'android.support.v4.content.LocalBroadcastManager'
static def PROXY_CLASS = 'com.qihoo360.replugin.loader.b.PluginLocalBroadcastManager'

/** 处理以下方法 */
static def includeMethodCall = ['getInstance',
'registerReceiver',
'unregisterReceiver',
'sendBroadcast',
'sendBroadcastSync']

/** 待处理文件的物理路径 */
public def filePath

@Override
void edit(MethodCall call) throws CannotCompileException {
if (call.getClassName().equalsIgnoreCase(TARGET_CLASS)) {
if (!(call.getMethodName() in includeMethodCall)) {
// println "Skip $methodName"
return
}

replaceStatement(call)
}
}

def private replaceStatement(MethodCall call) {
String method = call.getMethodName()
if (method == 'getInstance') {
call.replace('{$_ = ' + PROXY_CLASS + '.' + method + '($$);}')
} else {
def returnType = call.method.returnType.getName()
// getInstance 之外的调用,要增加一个参数,请参看 i-library 的 LocalBroadcastClient.java
if (returnType == 'void') {
call.replace('{' + PROXY_CLASS + '.' + method + '($0, $$);}')
} else {
call.replace('{$_ = ' + PROXY_CLASS + '.' + method + '($0, $$);}')
}
}
println ">>> Replace: ${filePath} <line:${call.lineNumber}> ${TARGET_CLASS}.${method}() <With> ${PROXY_CLASS}.${method}()\n"
}
}

注意:在Androidx中android.support.v4.content.LocalBroadcastManagerandroidx.localbroadcastmanager.content.LocalBroadcastManager

ProviderInjector

替换 插件中的 ContentResolver相关的方法调用 为 插件库的PluginProviderClient中的对应方法调用。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class ProviderInjector extends BaseInjector {

// 处理以下方法
public static def includeMethodCall = ['query',
'getType',
'insert',
'bulkInsert',
'delete',
'update',
/// 以下方法 replugin plugin lib 暂未支持,导致字节码修改失败。
'openInputStream',
'openOutputStream',
'openFileDescriptor',
'registerContentObserver',
'acquireContentProviderClient',
'notifyChange',
'toCalledUri',
]

// 表达式编辑器
def editor

@Override
def injectClass(ClassPool pool, String dir, Map config) {
if (editor == null) {
editor = new ProviderExprEditor()
}

Util.newSection()
println dir

Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

String filePath = file.toString()
def stream, ctCls
try {
if (filePath.contains('PluginProviderClient.class')) {
throw new Exception('can not replace self ')
}

stream = new FileInputStream(filePath)
ctCls = pool.makeClass(stream);

// println ctCls.name
if (ctCls.isFrozen()) {
ctCls.defrost()
}

editor.filePath = filePath

(ctCls.getDeclaredMethods() + ctCls.getMethods()).each {
it.instrument(editor)
}

ctCls.writeFile(dir)
} catch (Throwable t) {
println " [Warning] --> ${t.toString()}"
// t.printStackTrace()
} finally {
if (ctCls != null) {
ctCls.detach()
}
if (stream != null) {
stream.close()
}
}

return super.visitFile(file, attrs)
}
})
}
}

public class ProviderExprEditor extends ExprEditor {

static def PROVIDER_CLASS = 'com.qihoo360.replugin.loader.p.PluginProviderClient'

public def filePath

@Override
void edit(MethodCall m) throws CannotCompileException {
final String clsName = m.getClassName()
final String methodName = m.getMethodName()
if (!clsName.equalsIgnoreCase('android.content.ContentResolver')) {
return
}
if (!(methodName in ProviderInjector.includeMethodCall)) {
return
}
try {
replaceStatement(m, methodName, m.lineNumber)
} catch (Exception e) { //确保不影响其他 MethodCall
println " [Warning] --> ProviderExprEditor : ${e.toString()}"
}
}

def private replaceStatement(MethodCall methodCall, String method, def line) {
if (methodCall.getMethodName() == 'registerContentObserver' || methodCall.getMethodName() == 'notifyChange') {
methodCall.replace('{' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}')
} else {
methodCall.replace('{$_ = ' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}')
}
println ">>> Replace: ${filePath} Provider.${method}():${line}"
}
}

ProviderInjector2

替换 插件中的 ContentProviderClient 相关的方法调用。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class ProviderInjector2 extends BaseInjector {

// 处理以下方法
public static def includeMethodCall = ['query', 'update']

// 表达式编辑器
def editor

@Override
def injectClass(ClassPool pool, String dir, Map config) {
if (editor == null) {
editor = new ProviderExprEditor2()
}

Util.newSection()
println dir

Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

String filePath = file.toString()
def stream, ctCls
try {
if (filePath.contains('PluginProviderClient2.class')) {
throw new Exception('can not replace self ')
}

stream = new FileInputStream(filePath)
ctCls = pool.makeClass(stream);

// println ctCls.name
if (ctCls.isFrozen()) {
ctCls.defrost()
}

editor.filePath = filePath
ctCls.getDeclaredMethods().each {
it.instrument(editor)
}

ctCls.getMethods().each {
it.instrument(editor)
}

ctCls.writeFile(dir)
} catch (Throwable t) {
println " [Warning] --> ${t.toString()}"
// t.printStackTrace()
} finally {
if (ctCls != null) {
ctCls.detach()
}
if (stream != null) {
stream.close()
}
}

return super.visitFile(file, attrs)
}
})
}
}

public class ProviderExprEditor2 extends ExprEditor {

static def PROVIDER_CLASS = 'com.qihoo360.loader2.mgr.PluginProviderClient2'

public def filePath

@Override
void edit(MethodCall m) throws CannotCompileException {
String clsName = m.getClassName()
String methodName = m.getMethodName()

if (clsName.equalsIgnoreCase('android.content.ContentProviderClient')) {
println " ${filePath} ContentProviderClient.${methodName}():${m.lineNumber}"
if (!(methodName in ProviderInjector2.includeMethodCall)) {
// println "跳过$methodName"
return
}
replaceStatement(m, methodName, m.lineNumber)
}
}

def private replaceStatement(MethodCall methodCall, String method, def line) {
methodCall.replace('{$_ = ' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}')
println ">>> Replace: ${filePath} Provider.${method}():${line}"
}
}

GetIdentifierInjector

替换 插件中的 Resource.getIdentifier 方法调用的参数 为 动态适配的参数。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class GetIdentifierInjector extends BaseInjector {

// 表达式编辑器
def editor

@Override
def injectClass(ClassPool pool, String dir, Map config) {

if (editor == null) {
editor = new GetIdentifierExprEditor()
}

Util.newSection()
println dir

Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
@Override
FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

//todo only .class
String filePath = file.toString()

editor.filePath = filePath

def stream, ctCls
try {
stream = new FileInputStream(filePath)
ctCls = pool.makeClass(stream);

// println ctCls.name
if (ctCls.isFrozen()) {
ctCls.defrost()
}

ctCls.getDeclaredMethods().each {
it.instrument(editor)
}

ctCls.getMethods().each {
it.instrument(editor)
}

ctCls.writeFile(dir)
} catch (Throwable t) {
println " [Warning] --> ${t.toString()}"
} finally {
if (ctCls != null) {
ctCls.detach()
}
if (stream != null) {
stream.close()
}
}

return super.visitFile(file, attrs)
}
})
}
}

public class GetIdentifierExprEditor extends ExprEditor {

public def filePath

@Override
void edit(MethodCall m) throws CannotCompileException {
String clsName = m.getClassName()
String methodName = m.getMethodName()

if (clsName.equalsIgnoreCase('android.content.res.Resources')) {
if (methodName == 'getIdentifier') {
// 将传入的第三个参数替换成 固定包名,防止找错资源
m.replace('{ $3 = \"' + CommonData.appPackage + '\"; ' +
'$_ = $proceed($$);' +
' }')
println " GetIdentifierCall => ${filePath} ${methodName}():${m.lineNumber}"
}
}
}
}

ReclassTansfrom任务完成后,将会把输出继续传递给下一个TransfromtransformClassesWithDexFor{ProductFlavor}{BuildType},把处理权交还给android gradle插件。至此,replugin-plugin-gradle 插件的工作就全部结束了。

gradle高版本问题

如果在某些gradle版本上replugin-plugin-gradle插件的LoaderActivityInjector在编译时调用Exception(读取Manifest文件出错等,应该是高版本gradle api不兼容的问题),从而造成没有自动替换PluginXXXActivity,可以尝试在插件app module的build.gradle末尾添加以下代码:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import java.lang.reflect.Method
import java.util.regex.Pattern

import javassist.ClassPool

import com.qihoo360.replugin.gradle.plugin.inner.CommonData
import com.qihoo360.replugin.gradle.plugin.injector.loaderactivity.LoaderActivityInjector
import com.qihoo360.replugin.gradle.plugin.manifest.ManifestReader
import com.qihoo360.replugin.gradle.plugin.injector.Injectors

Injectors.LOADER_ACTIVITY_CHECK_INJECTOR.injector = new FixedLoaderActivityInjector()


class FixedLoaderActivityInjector extends LoaderActivityInjector {

@Override
def injectClass(ClassPool pool, String dir, Map config) {
def reader = new ManifestReader(manifestPath(project, variantDir))
Method method = LoaderActivityInjector.class.getDeclaredMethod("handleActivity", ClassPool.class, String.class, String.class)
method.accessible = true
reader.activities.each {
// 处理没有被忽略的 Activity
if (!(it in CommonData.ignoredActivities)) {
//handleActivity(pool, it, dir)
method.invoke(this, pool, it, dir)
}
}
}

/**
* 获取 AndroidManifest.xml 路径
*/
def static manifestPath(Project project, String variantDir) {
// Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote'
def variantDirArray = variantDir.split(Pattern.quote(File.separator))
String variantName = ""
variantDirArray.each {
//首字母大写进行拼接
variantName += it.capitalize()
}
println ">>> variantName:${variantName}"

//获取processManifestTask
def processManifestTask = project.tasks.getByName("process${variantName}Manifest")

//如果processManifestTask存在的话
//transform的task目前能保证在processManifestTask之后执行
if (processManifestTask) {
File result = null
//正常的manifest
File manifestOutputFile = null
//instant run的manifest
File instantRunManifestOutputFile = null
try {
manifestOutputFile = processManifestTask.getManifestOutputFile()
instantRunManifestOutputFile = processManifestTask.getInstantRunManifestOutputFile()
} catch (Exception e) {
def dir = processManifestTask.getManifestOutputDirectory()
if (dir instanceof File || dir instanceof String) {
manifestOutputFile = new File(dir, "AndroidManifest.xml")
} else {
manifestOutputFile = new File(dir.getAsFile().get(), "AndroidManifest.xml")
}
dir = processManifestTask.getInstantRunManifestOutputDirectory()
if (dir instanceof File || dir instanceof String) {
instantRunManifestOutputFile = new File(dir, "AndroidManifest.xml")
} else {
instantRunManifestOutputFile = new File(dir.getAsFile().get(), "AndroidManifest.xml")
}
}

if (manifestOutputFile == null && instantRunManifestOutputFile == null) {
throw new GradleException("can't get manifest file")
}

//打印
println " manifestOutputFile:${manifestOutputFile} ${manifestOutputFile.exists()}"
println " instantRunManifestOutputFile:${instantRunManifestOutputFile} ${instantRunManifestOutputFile.exists()}"

//先设置为正常的manifest
result = manifestOutputFile

try {
//获取instant run 的Task
def instantRunTask = project.tasks.getByName("transformClassesWithInstantRunFor${variantName}")
//查找instant run是否存在且文件存在
if (instantRunTask && instantRunManifestOutputFile.exists()) {
println ' Instant run is enabled and the manifest is exist.'
if (!manifestOutputFile.exists()) {
//因为这里只是为了读取activity,所以无论用哪个manifest差别不大
//正常情况下不建议用instant run的manifest,除非正常的manifest不存在
//只有当正常的manifest不存在时,才会去使用instant run产生的manifest
result = instantRunManifestOutputFile
}
}
} catch (ignored) {
// transformClassesWithInstantRunForXXX may not exists
}

//最后检测文件是否存在,打印
if (!result.exists()) {
println ' AndroidManifest.xml not exist'
}
//输出路径
println " AndroidManifest.xml 路径:$result"

return result.absolutePath
}

return ""
}
}

在更高的gradle版本(com.android.tools.build:gradle:3.6.3)上,可能直接编译报红,这里由于replugin-plugin-gradle插件最新版本用的gradle插件版本是2.1.3,两者之间的版本跨度太大了,api肯定有不兼容的问题。

AndroidX问题

在Androidx上replugin-plugin-gradle插件的Transform API不会替换Androidx包中的Activity类,因为在LoaderActivityInjector中没有指定AndroidX包,可以这样修改:

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
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.regex.Pattern

import javassist.ClassPool

import com.qihoo360.replugin.gradle.plugin.inner.CommonData
import com.qihoo360.replugin.gradle.plugin.injector.loaderactivity.LoaderActivityInjector
import com.qihoo360.replugin.gradle.plugin.manifest.ManifestReader
import com.qihoo360.replugin.gradle.plugin.injector.Injectors

Injectors.LOADER_ACTIVITY_CHECK_INJECTOR.injector = new FixedLoaderActivityInjector()

class FixedLoaderActivityInjector extends LoaderActivityInjector {

def private static loaderActivityRules = [
'android.app.Activity' : 'com.qihoo360.replugin.loader.a.PluginActivity',
'android.app.TabActivity' : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
'android.app.ListActivity' : 'com.qihoo360.replugin.loader.a.PluginListActivity',
'android.app.ActivityGroup' : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
'android.preference.PreferenceActivity' : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
'android.app.ExpandableListActivity' : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity',
'androidx.appcompat.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
'androidx.fragment.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
// 其它需要替换的androidx规则
]

@Override
def injectClass(ClassPool pool, String dir, Map config) {
def reader = new ManifestReader(manifestPath(project, variantDir))
Field field = LoaderActivityInjector.class.getDeclaredField("loaderActivityRules")
field.setAccessible(true)
field.set(null, loaderActivityRules)

println("activities = ${reader.activities}")

Method method = LoaderActivityInjector.class.getDeclaredMethod("handleActivity", ClassPool.class, String.class, String.class)
method.accessible = true

reader.activities.each {
// 处理没有被忽略的 Activity
if (!(it in CommonData.ignoredActivities)) {
println("begin to invoke: ${it}")
method.invoke(this, pool, it, dir)
}
}
}

// ...
}

对于LocalBroadcastInjector也可以参照这个修改。

总结

RePlugin初始化以及插件加载流程如下图:

RePlugin

Activity简要启动流程如下图:

Activity启动流程