插件化之 Shadow - 初识
Overview
假设我手上现在有猫眼、外卖、打车、买菜等好几个垂直领域的 APP,现在呢要开发一个集所有功能于一身的超级 APP 叫做美团,用 Shadow 该如何改造已有的垂直 APP 并集成到 meituan-app 这个新的 APP 内呢?
将猫眼 app project 拆分为 library project 和 app project
- library project 实际上就是原来的 app project 将 gradle plugin 从 com.android.application 改为 com.android.library
- 新的 app project 不包含任何业务代码,就是一个空的 gradle project 用来打包出 apk,后续想要构建猫眼时就从这里打包
- maoyan-lib
- maoyan-app
为了将猫眼 APP 集成到 meituan-app 里,需要将猫眼打包为一个 Shadow 插件,相关的项目有三个:
- maoyan-runtime 和 maoyan-loader,它们都是 com.android.application 并且是有业务代码的(并不是空项目),先不用管它们有什么用,现在只需要知道每个 plugin 都需要这两个 project
- maoyan-plugin,它是一个空项目没有业务代码,是为了打包出 plugin;它与 maoyan-app 共用构建脚本,并在此基础上加上了 Shadow Gradle Plugin 及其配置,任务 packagePlugin 可以构建出插件包(zip)
- maoyan-lib
- maoyan-app
- maoyan-runtime
- maoyan-loader
- maoyan-plugin
plugin 打包后是一个 zip 文件,不能直接使用,需要解压、加载、初始化等各种前置操作,为了将这些 plugin 相关的业务与 meituan-app 业务代码隔离,以便 meituan-app 能透明地使用 plugin,还需要添加一个项目 meituan-manager;它作为桥梁连接主 APP 和各个 plugin,承担 plugin 的管理工作,虽然它是一个 com.android.application 但不会也不能作为 APP 独立运行
其实我们能够很容易地想到,meituan-manager 不应该就是 Shadow Library 吗?它不是应该作为 dependency 打包进主 APP 吗?
对!Shadow 作为一个普通的 dependency 确实是可以这样做,但这样的话 Shadow 一旦需要升级只能升级同时主 APP;那能不能把框架本身也像 plugin 那样动态加载随时升级呢?真是个大胆的想法,不过确实可以做到,就是把框架代码 meituan-manager 独立在主 APP 之外,单独打包并动态加载
一次性实现完美的插件框架很难,但 Shadow 将这些实现全部动态化起来,使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制
- maoyan-lib
- maoyan-app
- maoyan-runtime
- maoyan-loader
- maoyan-plugin
- meituan-manager
最后就是构建主 APP:meituan-app,它是一个普通的 com.android.application,可以有自己的源码依赖、第三方依赖、业务代码(比如 Slash Page、主页)以及自定义构建脚本,assemble 打包出来的安装包即使没有 plugins 也是可以正常运行的,只不过 plugins 提供的功能就无法使用了
meituan-app 与各个 plugins 之间通过一个方法进行通讯:
public interface PluginManager {
void enter(Context context, long formId, Bundle bundle, EnterCallback callback);
}
meituan-app 除了业务代码之外需要添加的改动有几处:
- meituan-manager 和 plugin 的下载/更新逻辑(plugin 是个 zip 文件,manager 是个 apk 文件)
- 注册 PluginProcessService 子类,有几个 plugin 就添加几个,用来 fork 出 plugin 所在的子进程
- 注册 PluginDefaultProxyActivity 等几个桩 Activity
最后项目结构如下:
# maoyan 就是个正常可运行的 APP,可以是独立项目组开发,它完全不需要知道 meituan-app 的存在
- maoyan-lib
- maoyan-app
# 把 maoyan 打包为 plugin 所需,业务代码都在 maoyan-lib 里,这里仅仅作为中间层/胶水层,打包出 plugin zip
- maoyan-runtime
- maoyan-loader
- maoyan-plugin
# 框架代码
- meituan-manager
# 主 APP,有自己的业务和页面可独立运行
- meituan-app
PluginManager
APP 与 plugin 之间通过 PluginManager 通讯,默认实现是 DynamicPluginManager,它需要一个 PluginManagerUpdater
/**
* PluginManager文件升级器
* <p>
* 注意这个类不负责什么时候该升级PluginManager,
* 它只提供需要升级时的功能,如下载和向远端查询文件是否还可用。
*/
public interface PluginManagerUpdater {
/**
* @return <code>true</code>表示之前更新过程中意外中断了
*/
boolean wasUpdating();
/**
* 更新
*
* @return 当前最新的PluginManager,可能是之前已经返回过的文件,但它是最新的了。
*/
Future<File> update();
/**
* 获取本地最新可用的
*
* @return <code>null</code>表示本地没有可用的
*/
File getLatest();
/**
* 查询是否可用
*
* @param file PluginManagerUpdater返回的file
* @return <code>true</code>表示可用,<code>false</code>表示不可用
*/
Future<Boolean> isAvailable(File file);
}
PluginManagerUpdater 提供了动态更新 manager 的机制比如修复线上的框架缺陷(hotfix),注意这里 hotfix 不是针对 plugin 的而是针对框架的(也就是 manager)
public final class DynamicPluginManager implements PluginManager {
private void updateManagerImpl(Context context) {
File latestManagerImplApk = mUpdater.getLatest();
String md5 = md5File(latestManagerImplApk);
if (mLogger.isInfoEnabled()) {
mLogger.info("TextUtils.equals(mCurrentImplMd5, md5) : " + (TextUtils.equals(mCurrentImplMd5, md5)));
}
if (!TextUtils.equals(mCurrentImplMd5, md5)) {
ManagerImplLoader implLoader = new ManagerImplLoader(context, latestManagerImplApk);
PluginManagerImpl newImpl = implLoader.load();
Bundle state;
if (mManagerImpl != null) {
state = new Bundle();
mManagerImpl.onSaveInstanceState(state);
mManagerImpl.onDestroy();
} else {
state = null;
}
newImpl.onCreate(state);
mManagerImpl = newImpl;
mCurrentImplMd5 = md5;
}
}
}
PluginManagerUpdater 要返回一个包含 manager 代码的文件(manager.apk),里面必须包含 com.tencent.shadow.dynamic.impl.ManagerFactoryImpl
可以看到 manager.apk 并不会污染 APP ClassLoader,确保了 APP 和 manager 之间的独立性
final class ManagerImplLoader extends ImplLoader {
private static final String MANAGER_FACTORY_CLASS_NAME = "com.tencent.shadow.dynamic.impl.ManagerFactoryImpl";
private static final String[] REMOTE_PLUGIN_MANAGER_INTERFACES = new String[]
{
"com.tencent.shadow.core.common",
"com.tencent.shadow.dynamic.host"
};
final private Context applicationContext;
final private InstalledApk installedApk;
ManagerImplLoader(Context context, File apk) {
applicationContext = context.getApplicationContext();
File root = new File(applicationContext.getFilesDir(), "ManagerImplLoader");
File odexDir = new File(root, Long.toString(apk.lastModified(), Character.MAX_RADIX));
odexDir.mkdirs();
installedApk = new InstalledApk(apk.getAbsolutePath(), odexDir.getAbsolutePath(), null);
}
PluginManagerImpl load() {
ApkClassLoader apkClassLoader = new ApkClassLoader(
installedApk,
getClass().getClassLoader(),
loadWhiteList(installedApk),
1
);
Context pluginManagerContext = new ChangeApkContextWrapper(
applicationContext,
installedApk.apkFilePath,
apkClassLoader
);
try {
ManagerFactory managerFactory = apkClassLoader.getInterface(
ManagerFactory.class,
MANAGER_FACTORY_CLASS_NAME
);
return managerFactory.buildManager(pluginManagerContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
contract
meituan-contract 契约
enter(context, fromId, bundle, callback) 是 APP 与 plugin 通讯的唯一渠道,实现在 manager 里,看下官方例子:
public class SamplePluginManager extends FastPluginManager {
@Override
public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {
if (fromId == Constant.FROM_ID_NOOP) {
//do nothing.
} else if (fromId == Constant.FROM_ID_START_ACTIVITY) {
onStartActivity(context, bundle, callback);
} else {
throw new IllegalArgumentException("不认识的fromId==" + fromId);
}
}
}
可以这么理解:
- fromId: 用一个 long 值标识 plugin 要执行的方法(startActivity、startService 等等),相当于 method id
- bundle: 作为容器存放 method 所需的参数
- callback: 接受 method complete 消息,执行时间较长的方法还可以用它来显示 loading
虽然上面说过 APP 和 manager 之间是相互独立的(plugin 才是真正独立的,manager 作为胶水层粘合了 APP 和 plugin),但 method id 和 method params 最好还是得有个规范才不容易写错,于是就有了 meituan-contract,manager 和 APP 都引用它
final public class Constant {
public static final String KEY_PLUGIN_ZIP_PATH = "pluginZipPath";
public static final String KEY_ACTIVITY_CLASSNAME = "KEY_ACTIVITY_CLASSNAME";
public static final String KEY_EXTRAS = "KEY_EXTRAS";
public static final String KEY_PLUGIN_PART_KEY = "KEY_PLUGIN_PART_KEY";
public static final String PART_KEY_PLUGIN_MAIN_APP = "sample-plugin-app";
public static final String PART_KEY_PLUGIN_ANOTHER_APP = "sample-plugin-app2";
public static final int FROM_ID_NOOP = 1000;
public static final int FROM_ID_START_ACTIVITY = 1002;
}
这样项目架构就变为:
# maoyan 就是个正常可运行的 APP,可以是独立项目组开发,它完全不需要知道 meituan-app 的存在
- maoyan-lib
- maoyan-app
# 把 maoyan 打包为 plugin 所需,业务代码都在 maoyan-lib 里,这里仅仅作为中间层/胶水层,打包出 plugin zip
- maoyan-runtime
- maoyan-loader
- maoyan-plugin
# 框架代码
- meituan-manager
- meituan-contract
# 主 APP,有自己的业务和页面可独立运行
- meituan-app
- meituan-contract
plugin.zip
安装 plugin
plugin.zip 文件结构如下,一个 plugin.zip 实际上可以包含多个 plugin apk(maoyan.apk、maoyan2.apk 等等),每个 apk 都相当于一个 plugin app,运行在各自独立的子进程内,不同于多个 plugin.zip 的是它们会共享 runtime 和 loader
注意虽然文件名是 apk 但是它们是不能独立运行的,缺少 Shadow 相关的一些类
- plugin.zip
- config.json
- runtime.apk
- loader.apk
- maoyan.apk
- maoyan2.apk
- ...
将 plugin.zip 解压后,可以得到如下有关 plugin 的信息
- UUID 标识唯一的 plugin
- version 可以用来动态下发 plugin 并热更新
- loader 和 runtime 都是可选的
- plugins 用 string key 标识 plugin 内的多个 apk
public class PluginConfig {
/**
* 配置json文件的格式版本号
*/
public int version;
/**
* 配置json文件的格式兼容版本号
*/
public int[] compact_version;
/**
* 标识一次插件发布的id
*/
public String UUID;
/**
* 标识一次插件发布的id,可以使用自定义格式描述版本信息
*/
public String UUID_NickName;
/**
* pluginLoaderAPk 文件信息
*/
public FileInfo pluginLoader;
/**
* runtime 文件信息
*/
public FileInfo runTime;
/**
* 业务插件 key: partKey value:文件信息
*/
public Map<String, PluginFileInfo> plugins = new HashMap<>();
/**
* 插件的存储目录
*/
public File storageDir;
}
安装过程如下:
- 利用 DexClassLoader 生成 loader、runtime 和 plugin apk 的 odex
- 将 so 解压出来
- 将 plugin 信息保存/更新到数据库
public abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {
public InstalledPlugin installPlugin(String zip, String hash , boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {
final PluginConfig pluginConfig = installPluginFromZip(new File(zip), hash);
final String uuid = pluginConfig.UUID;
List<Future> futures = new LinkedList<>();
if (pluginConfig.runTime != null && pluginConfig.pluginLoader != null) {
Future odexRuntime = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_RUNTIME,
pluginConfig.runTime.file);
return null;
}
});
futures.add(odexRuntime);
Future odexLoader = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPluginLoaderOrRunTime(uuid, InstalledType.TYPE_PLUGIN_LOADER,
pluginConfig.pluginLoader.file);
return null;
}
});
futures.add(odexLoader);
}
for (Map.Entry<String, PluginConfig.PluginFileInfo> plugin : pluginConfig.plugins.entrySet()) {
final String partKey = plugin.getKey();
final File apkFile = plugin.getValue().file;
Future extractSo = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
extractSo(uuid, partKey, apkFile);
return null;
}
});
futures.add(extractSo);
if (odex) {
Future odexPlugin = mFixedPool.submit(new Callable() {
@Override
public Object call() throws Exception {
oDexPlugin(uuid, partKey, apkFile);
return null;
}
});
futures.add(odexPlugin);
}
}
for (Future future : futures) {
future.get();
}
onInstallCompleted(pluginConfig);
return getInstalledPlugins(1).get(0);
}
}
Binder & ClassLoader
plugin app 是运行在子进程里的,这样即使插件崩溃了也不会影响主 APP 的运行
由于 plugin process 是一个完整的 APP 进程,所以不能 fork,只能通过四大组件的 android:process 属性创建;上面也说过了有几个 plugin app 就需要在主 APP 的 AndroidManifest.xml 里配置几个 PluginProcessService
plugin process 起来后,APP process 就需要通过 Binder 与 plugin 通讯,主 APP 端的叫 PpsController,plugin 端的叫 PpsBinder,从下面的 PpsBinder.TRANSACTION_ 可以看出 PPS 主要提供以下功能:
- 加载 runtime
- 加载/获取 loader
- 设置 UUIDManager(它用来操作数据库)
- 退出 plugin process
public abstract class FastPluginManager extends PluginManagerThatUseDynamicLoader {
private void loadPlugin(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {
loadPluginLoaderAndRuntime(uuid, partKey);
Map map = mPluginLoader.getLoadedPlugin();
if (!map.containsKey(partKey)) {
mPluginLoader.loadPlugin(partKey);
}
}
private void loadPluginLoaderAndRuntime(String uuid, String partKey) throws RemoteException, TimeoutException, FailedException {
if (mPpsController == null) {
bindPluginProcessService(getPluginProcessServiceName(partKey));
waitServiceConnected(10, TimeUnit.SECONDS);
}
loadRunTime(uuid);
loadPluginLoader(uuid);
}
}
abstract public class BaseDynamicPluginManager extends BasePluginManager implements UuidManagerImpl {
/**
* 启动PluginProcessService
*
* @param serviceName 注册在宿主中的插件进程管理service完整名字
*/
public final void bindPluginProcessService(final String serviceName) {
if (mServiceConnecting.get()) {
if (mLogger.isInfoEnabled()) {
mLogger.info("pps service connecting");
}
return;
}
if (mLogger.isInfoEnabled()) {
mLogger.info("bindPluginProcessService " + serviceName);
}
mConnectCountDownLatch.set(new CountDownLatch(1));
mServiceConnecting.set(true);
final CountDownLatch startBindingLatch = new CountDownLatch(1);
final boolean[] asyncResult = new boolean[1];
mUiHandler.post(new Runnable() {
@Override
public void run() {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mHostContext, serviceName));
boolean binding = mHostContext.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mLogger.isInfoEnabled()) {
mLogger.info("onServiceConnected connectCountDownLatch:" + mConnectCountDownLatch);
}
mServiceConnecting.set(false);
// service connect 后处理逻辑
onPluginServiceConnected(name, service);
mConnectCountDownLatch.get().countDown();
if (mLogger.isInfoEnabled()) {
mLogger.info("onServiceConnected countDown:" + mConnectCountDownLatch);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (mLogger.isInfoEnabled()) {
mLogger.info("onServiceDisconnected");
}
mServiceConnecting.set(false);
onPluginServiceDisconnected(name);
}
}, BIND_AUTO_CREATE);
asyncResult[0] = binding;
startBindingLatch.countDown();
}
});
try {
//等待bindService真正开始
startBindingLatch.await(10, TimeUnit.SECONDS);
if (!asyncResult[0]) {
throw new IllegalArgumentException("无法绑定PPS:" + serviceName);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class PpsBinder extends android.os.Binder {
static final int TRANSACTION_loadRuntime = (FIRST_CALL_TRANSACTION);
static final int TRANSACTION_loadPluginLoader = (FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_setUuidManager = (FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_exit = (FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_getPpsStatus = (FIRST_CALL_TRANSACTION + 4);
static final int TRANSACTION_getPluginLoader = (FIRST_CALL_TRANSACTION + 5);
}
plugin runtime
把 runtime.apk 插入 ClassLoader 树,注意这是 IPC,在主 APP 端是 PpsController.loadRuntime
public abstract class PluginManagerThatUseDynamicLoader extends BaseDynamicPluginManager implements PluginManagerImpl {
public final void loadRunTime(String uuid) throws RemoteException, FailedException {
if (mLogger.isInfoEnabled()) {
mLogger.info("loadRunTime mPpsController:" + mPpsController);
}
PpsStatus ppsStatus = mPpsController.getPpsStatus();
if (!ppsStatus.runtimeLoaded) {
mPpsController.loadRuntime(uuid);
}
}
}
public class PpsController {
public void loadRuntime(String uuid) throws RemoteException, FailedException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(PpsBinder.DESCRIPTOR);
_data.writeString(uuid);
mRemote.transact(PpsBinder.TRANSACTION_loadRuntime, _data, _reply, 0);
int i = _reply.readInt();
if (i == TRANSACTION_CODE_FAILED_EXCEPTION) {
throw new FailedException(_reply);
} else if (i != TRANSACTION_CODE_NO_EXCEPTION) {
throw new RuntimeException("不认识的Code==" + i);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
}
plugin 端是 PpsBinder,实际代码在 PluginProcessService.loadRuntime,基本逻辑如下:
- 根据 Plugin ID 从数据库里查出 plugin 信息,从而拿到 runtime 的 apk 和 odex,这一步是 IPC 因为 plugin 信息是保存在主 APP 的数据库里
- 往 plugin process 的 ClassLoader 树里插入 runtime 形成这么一个结构:
BootClassLoader
- RuntimeClassLoader(包含 runtime 代码)
- PathClassLoader(包含主 APP 代码)
class PpsBinder extends android.os.Binder {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
switch (code) {
case TRANSACTION_loadRuntime: {
data.enforceInterface(DESCRIPTOR);
String _arg0;
_arg0 = data.readString();
try {
mPps.loadRuntime(_arg0);
reply.writeInt(TRANSACTION_CODE_NO_EXCEPTION);
} catch (FailedException e) {
reply.writeInt(TRANSACTION_CODE_FAILED_EXCEPTION);
e.writeToParcel(reply, 0);
}
return true;
}
// ...
}
}
}
public class PluginProcessService extends BasePluginProcessService {
void loadRuntime(String uuid) throws FailedException {
checkUuidManagerNotNull();
setUuid(uuid);
if (mRuntimeLoaded) {
throw new FailedException(ERROR_CODE_RELOAD_RUNTIME_EXCEPTION
, "重复调用loadRuntime");
}
try {
if (mLogger.isInfoEnabled()) {
mLogger.info("loadRuntime uuid:" + uuid);
}
InstalledApk installedApk;
try {
installedApk = mUuidManager.getRuntime(uuid);
} catch (RemoteException e) {
throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());
} catch (NotFoundException e) {
throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, "uuid==" + uuid + "的Runtime没有找到。cause:" + e.getMessage());
}
InstalledApk installedRuntimeApk = new InstalledApk(installedApk.apkFilePath, installedApk.oDexPath, installedApk.libraryPath);
boolean loaded = DynamicRuntime.loadRuntime(installedRuntimeApk);
if (loaded) {
DynamicRuntime.saveLastRuntimeInfo(this, installedRuntimeApk);
}
mRuntimeLoaded = true;
} catch (RuntimeException e) {
if (mLogger.isErrorEnabled()) {
mLogger.error("loadRuntime发生RuntimeException", e);
}
throw new FailedException(e);
}
}
}
/**
* 将runtime apk加载到DexPathClassLoader,形成如下结构的classLoader树结构
* ---BootClassLoader
* ----RuntimeClassLoader
* ------PathClassLoader
*/
public class DynamicRuntime {
/**
* 加载runtime apk
*
* @return true 加载了新的runtime
*/
public static boolean loadRuntime(InstalledApk installedRuntimeApk) {
ClassLoader contextClassLoader = DynamicRuntime.class.getClassLoader();
RuntimeClassLoader runtimeClassLoader = getRuntimeClassLoader();
if (runtimeClassLoader != null) {
String apkPath = runtimeClassLoader.apkPath;
if (mLogger.isInfoEnabled()) {
mLogger.info("last apkPath:" + apkPath + " new apkPath:" + installedRuntimeApk.apkFilePath);
}
if (TextUtils.equals(apkPath, installedRuntimeApk.apkFilePath)) {
//已经加载相同版本的runtime了,不需要加载
if (mLogger.isInfoEnabled()) {
mLogger.info("已经加载相同apkPath的runtime了,不需要加载");
}
return false;
} else {
//版本不一样,说明要更新runtime,先恢复正常的classLoader结构
if (mLogger.isInfoEnabled()) {
mLogger.info("加载不相同apkPath的runtime了,先恢复classLoader树结构");
}
try {
recoveryClassLoader();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
//正常处理,将runtime 挂到pathclassLoader之上
try {
hackParentToRuntime(installedRuntimeApk, contextClassLoader);
} catch (Exception e) {
throw new RuntimeException(e);
}
return true;
}
private static void hackParentToRuntime(InstalledApk installedRuntimeApk, ClassLoader contextClassLoader) throws Exception {
RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(installedRuntimeApk.apkFilePath, installedRuntimeApk.oDexPath,
installedRuntimeApk.libraryPath, contextClassLoader.getParent());
hackParentClassLoader(contextClassLoader, runtimeClassLoader);
}
/**
* 修改ClassLoader的parent
*
* @param classLoader 需要修改的ClassLoader
* @param newParentClassLoader classLoader的新的parent
* @throws Exception 失败时抛出
*/
static void hackParentClassLoader(ClassLoader classLoader,
ClassLoader newParentClassLoader) throws Exception {
Field field = getParentField();
if (field == null) {
throw new RuntimeException("在ClassLoader.class中没找到类型为ClassLoader的parent域");
}
field.setAccessible(true);
field.set(classLoader, newParentClassLoader);
}
}
plugin loader
APP 端发起 IPC PpsController.loadPluginLoader
public abstract class PluginManagerThatUseDynamicLoader extends BaseDynamicPluginManager implements PluginManagerImpl {
public final void loadPluginLoader(String uuid) throws RemoteException, FailedException {
if (mLogger.isInfoEnabled()) {
mLogger.info("loadPluginLoader mPluginLoader:" + mPluginLoader);
}
if (mPluginLoader == null) {
PpsStatus ppsStatus = mPpsController.getPpsStatus();
if (!ppsStatus.loaderLoaded) {
mPpsController.loadPluginLoader(uuid);
}
IBinder iBinder = mPpsController.getPluginLoader();
mPluginLoader = new BinderPluginLoader(iBinder);
}
}
}
plugin 端最终执行 PluginProcessService.loadPluginLoader,基本逻辑如下:
- 从数据库里根据 Plugin ID 查询得到 loader 信息,这一步是 IPC
- 从 loader.apk 里加载 com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl,从工厂里获取 DynamicPluginLoader,这一步的代码是 Shadow 库 dynamic-loader-impl 里的,它作为依赖打包进 loader.apk
- 从 loader.apk 里加载 com.tencent.shadow.dynamic.loader.impl.CoreLoaderFactoryImpl,从工厂里获取 ShadowPluginLoader,这一步的代码才是 loader.apk 里自定义的实现
- 最后 APP 端持有的 PluginLoader 是 BinderPluginLoader,plugin 端持有的是 PluginLoaderBinder(实际逻辑由 DynamicPluginLoader 实现)
从 PluginLoader.TRANSACTION_ 可以看出 loader 主要提供以下功能:
- 加载 plugin apk
- 执行 plugin apk 的 Application.onCreate
- start/stop/bind/unbind service
- startActivity
public class PluginProcessService extends BasePluginProcessService {
void loadPluginLoader(String uuid) throws FailedException {
if (mLogger.isInfoEnabled()) {
mLogger.info("loadPluginLoader uuid:" + uuid + " mPluginLoader:" + mPluginLoader);
}
checkUuidManagerNotNull();
setUuid(uuid);
if (mPluginLoader != null) {
throw new FailedException(ERROR_CODE_RELOAD_LOADER_EXCEPTION
, "重复调用loadPluginLoader");
}
try {
InstalledApk installedApk;
try {
installedApk = mUuidManager.getPluginLoader(uuid);
if (mLogger.isInfoEnabled()) {
mLogger.info("取出uuid==" + uuid + "的Loader apk:" + installedApk.apkFilePath);
}
} catch (RemoteException e) {
if (mLogger.isErrorEnabled()) {
mLogger.error("获取Loader Apk失败", e);
}
throw new FailedException(ERROR_CODE_UUID_MANAGER_DEAD_EXCEPTION, e.getMessage());
} catch (NotFoundException e) {
throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, "uuid==" + uuid + "的PluginLoader没有找到。cause:" + e.getMessage());
}
File file = new File(installedApk.apkFilePath);
if (!file.exists()) {
throw new FailedException(ERROR_CODE_FILE_NOT_FOUND_EXCEPTION, file.getAbsolutePath() + "文件不存在");
}
PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());
pluginLoader.setUuidManager(mUuidManager);
mPluginLoader = pluginLoader;
} catch (RuntimeException e) {
if (mLogger.isErrorEnabled()) {
mLogger.error("loadPluginLoader发生RuntimeException", e);
}
throw new FailedException(e);
} catch (FailedException e) {
throw e;
} catch (Exception e) {
if (mLogger.isErrorEnabled()) {
mLogger.error("loadPluginLoader发生Exception", e);
}
String msg = e.getCause() != null ? e.getCause().getMessage() : e.getMessage();
throw new FailedException(ERROR_CODE_RUNTIME_EXCEPTION, "加载动态实现失败 cause:" + msg);
}
}
}
final class LoaderImplLoader extends ImplLoader {
/**
* 加载{@link #sLoaderFactoryImplClassName}时
* 需要从宿主PathClassLoader(含双亲委派)中加载的类
*/
private static final String[] sInterfaces = new String[]{
//当runtime是动态加载的时候,runtime的ClassLoader是PathClassLoader的parent,
// 所以不需要写在这个白名单里。但是写在这里不影响,也可以兼容runtime打包在宿主的情况。
"com.tencent.shadow.core.runtime.container",
"com.tencent.shadow.dynamic.host",
"com.tencent.shadow.core.common"
};
private final static String sLoaderFactoryImplClassName
= "com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl";
PluginLoaderImpl load(InstalledApk installedApk, String uuid, Context appContext) throws Exception {
ApkClassLoader pluginLoaderClassLoader = new ApkClassLoader(
installedApk,
LoaderImplLoader.class.getClassLoader(),
loadWhiteList(installedApk),
1
);
LoaderFactory loaderFactory = pluginLoaderClassLoader.getInterface(
LoaderFactory.class,
sLoaderFactoryImplClassName
);
return loaderFactory.buildLoader(uuid, appContext);
}
@Override
String[] getCustomWhiteList() {
return sInterfaces;
}
}
open class LoaderFactoryImpl : LoaderFactory {
override fun buildLoader(p0: String, p2: Context): PluginLoaderImpl {
return PluginLoaderBinder(DynamicPluginLoader(p2, p0))
}
}
internal class DynamicPluginLoader(hostContext: Context, uuid: String) {
companion object {
private const val CORE_LOADER_FACTORY_IMPL_NAME =
"com.tencent.shadow.dynamic.loader.impl.CoreLoaderFactoryImpl"
}
fun setUuidManager(p0: UuidManager?) {
if (p0 != null)
mUuidManager = p0
//todo #30 兼容mUuidManager为null时的逻辑
}
private val mPluginLoader: ShadowPluginLoader
private val mDynamicLoaderClassLoader: ClassLoader = DynamicPluginLoader::class.java.classLoader!!
private var mContext: Context;
private lateinit var mUuidManager: UuidManager;
private var mUuid: String;
private val mUiHandler = Handler(Looper.getMainLooper())
/**
* 同一个IServiceConnection只会对应一个ServiceConnection对象,此Map就是保存这种对应关系
*/
private val mConnectionMap = HashMap<IBinder, ServiceConnection>()
init {
try {
val coreLoaderFactory = mDynamicLoaderClassLoader.getInterface(
CoreLoaderFactory::class.java,
CORE_LOADER_FACTORY_IMPL_NAME
)
mPluginLoader = coreLoaderFactory.build(hostContext)
DelegateProviderHolder.setDelegateProvider(mPluginLoader.delegateProviderKey, mPluginLoader)
ContentProviderDelegateProviderHolder.setContentProviderDelegateProvider(mPluginLoader)
mPluginLoader.onCreate()
} catch (e: Exception) {
throw RuntimeException("当前的classLoader找不到PluginLoader的实现", e)
}
mContext = hostContext;
mUuid = uuid;
}
}
public interface PluginLoader {
int TRANSACTION_loadPlugin = (IBinder.FIRST_CALL_TRANSACTION);
int TRANSACTION_getLoadedPlugin = (IBinder.FIRST_CALL_TRANSACTION + 1);
int TRANSACTION_callApplicationOnCreate = (IBinder.FIRST_CALL_TRANSACTION + 2);
int TRANSACTION_convertActivityIntent = (IBinder.FIRST_CALL_TRANSACTION + 3);
int TRANSACTION_startPluginService = (IBinder.FIRST_CALL_TRANSACTION + 4);
int TRANSACTION_stopPluginService = (IBinder.FIRST_CALL_TRANSACTION + 5);
int TRANSACTION_bindPluginService = (IBinder.FIRST_CALL_TRANSACTION + 6);
int TRANSACTION_unbindService = (IBinder.FIRST_CALL_TRANSACTION + 7);
int TRANSACTION_startActivityInPluginProcess = (IBinder.FIRST_CALL_TRANSACTION + 8);
}
load plugin apk
plugin.zip 内的 apk 在使用前是需要加载的,这个加载操作由 loader 负责:PluginLoader.loadPlugin(partKey),在主 APP 侧是 BinderPluginLoader,在 plugin process 侧是 PluginLoaderBinder(实际逻辑由 DynamicPluginLoader 实现)
这个步骤非常重要,因为它初始化了核心功能所需的几个组件,其基本流程如下:
- 从数据库里根据 plugin id 查询得到 plugin apk 文件路径
- ComponentManager,包含 plugin apk 里配置的 Activity、Service 和 ContentProvider 等组件信息,我们知道这些组件实际上并没有在主 APP 的 AndroidManifest.xml 里配置,而且也没法动态增加组件,所以插件的这些组件是不能直接使用的,需要用桩技术
- PluginClassLoader,用来加载 plugin apk 里的类
- ShadowApplication
- Resources
- PluginPackageManagerImpl
- …
下面一个个分析它们的作用
internal class DynamicPluginLoader(hostContext: Context, uuid: String) {
fun loadPlugin(partKey: String) {
val installedApk = mUuidManager.getPlugin(mUuid, partKey)
val future = mPluginLoader.loadPlugin(installedApk)
future.get()
}
}
abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, DI, ContentProviderDelegateProvider {
@Throws(LoadPluginException::class)
open fun loadPlugin(
installedApk: InstalledApk
): Future<*> {
val loadParameters = installedApk.getLoadParameters()
if (mLogger.isInfoEnabled) {
mLogger.info("start loadPlugin")
}
// 在这里初始化PluginServiceManager
mPluginServiceManagerLock.withLock {
if (!::mPluginServiceManager.isInitialized) {
mPluginServiceManager = PluginServiceManager(this, mHostAppContext)
}
mComponentManager.setPluginServiceManager(mPluginServiceManager)
}
return LoadPluginBloc.loadPlugin(
mExecutorService,
mPluginPackageInfoSet,
::allPluginPackageInfo,
mComponentManager,
mLock,
mPluginPartsMap,
mHostAppContext,
installedApk,
loadParameters)
}
}
object LoadPluginBloc {
@Throws(LoadPluginException::class)
fun loadPlugin(
executorService: ExecutorService,
pluginPackageInfoSet: MutableSet<PackageInfo>,
allPluginPackageInfo: () -> (Array<PackageInfo>),
componentManager: ComponentManager,
lock: ReentrantLock,
pluginPartsMap: MutableMap<String, PluginParts>,
hostAppContext: Context,
installedApk: InstalledApk,
loadParameters: LoadParameters
): Future<*> {
if (installedApk.apkFilePath == null) {
throw LoadPluginException("apkFilePath==null")
} else {
val buildClassLoader = executorService.submit(Callable {
lock.withLock {
LoadApkBloc.loadPlugin(installedApk, loadParameters, pluginPartsMap)
}
})
val getPackageInfo = executorService.submit(Callable {
val archiveFilePath = installedApk.apkFilePath
val packageManager = hostAppContext.packageManager
val packageArchiveInfo = packageManager.getPackageArchiveInfo(
archiveFilePath,
PackageManager.GET_ACTIVITIES
or PackageManager.GET_META_DATA
or PackageManager.GET_SERVICES
or PackageManager.GET_PROVIDERS
or PackageManager.GET_SIGNATURES
)
?: throw NullPointerException("getPackageArchiveInfo return null.archiveFilePath==$archiveFilePath")
val tempContext = ShadowContext(hostAppContext, 0).apply {
setBusinessName(loadParameters.businessName)
}
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tempContext.dataDir
} else {
File(tempContext.filesDir, "dataDir")
}
dataDir.mkdirs()
packageArchiveInfo.applicationInfo.nativeLibraryDir = installedApk.libraryPath
packageArchiveInfo.applicationInfo.dataDir = dataDir.absolutePath
packageArchiveInfo.applicationInfo.processName = hostAppContext.applicationInfo.processName
packageArchiveInfo.applicationInfo.uid = hostAppContext.applicationInfo.uid
lock.withLock { pluginPackageInfoSet.add(packageArchiveInfo) }
packageArchiveInfo
})
val buildPluginInfo = executorService.submit(Callable {
val packageInfo = getPackageInfo.get()
ParsePluginApkBloc.parse(packageInfo, loadParameters, hostAppContext)
})
val buildPackageManager = executorService.submit(Callable {
val packageInfo = getPackageInfo.get()
val hostPackageManager = hostAppContext.packageManager
PluginPackageManagerImpl(hostPackageManager, packageInfo, allPluginPackageInfo)
})
val buildResources = executorService.submit(Callable {
val packageInfo = getPackageInfo.get()
CreateResourceBloc.create(packageInfo, installedApk.apkFilePath, hostAppContext)
})
val buildAppComponentFactory = executorService.submit(Callable<ShadowAppComponentFactory> {
val pluginClassLoader = buildClassLoader.get()
val pluginInfo = buildPluginInfo.get()
if (pluginInfo.appComponentFactory != null) {
val clazz = pluginClassLoader.loadClass(pluginInfo.appComponentFactory)
ShadowAppComponentFactory::class.java.cast(clazz.newInstance())
} else ShadowAppComponentFactory()
})
val buildApplication = executorService.submit(Callable {
val pluginClassLoader = buildClassLoader.get()
val resources = buildResources.get()
val pluginInfo = buildPluginInfo.get()
val packageInfo = getPackageInfo.get()
val appComponentFactory = buildAppComponentFactory.get()
CreateApplicationBloc.createShadowApplication(
pluginClassLoader,
pluginInfo,
resources,
hostAppContext,
componentManager,
packageInfo.applicationInfo,
appComponentFactory
)
})
val buildRunningPlugin = executorService.submit {
if (File(installedApk.apkFilePath).exists().not()) {
throw LoadPluginException("插件文件不存在.pluginFile==" + installedApk.apkFilePath)
}
val pluginPackageManager = buildPackageManager.get()
val pluginClassLoader = buildClassLoader.get()
val resources = buildResources.get()
val pluginInfo = buildPluginInfo.get()
val shadowApplication = buildApplication.get()
val appComponentFactory = buildAppComponentFactory.get()
lock.withLock {
componentManager.addPluginApkInfo(pluginInfo)
pluginPartsMap[pluginInfo.partKey] = PluginParts(
appComponentFactory,
shadowApplication,
pluginClassLoader,
resources,
pluginInfo.businessName,
pluginPackageManager
)
PluginPartInfoManager.addPluginInfo(pluginClassLoader, PluginPartInfo(shadowApplication, resources,
pluginClassLoader, pluginPackageManager))
}
}
return buildRunningPlugin
}
}
}
Plugin Application
我们知道 plugin process 是由 PluginProcessService 拉起的 APP 子进程,所以自然的 app application 的新实例会被创建并调用它的 onCreate,plugin application 并没有配置在 AndroidManifest.xml 里,我们只能模拟系统初始化 Application 的流程手动地初始化 plugin application
plugin apk 是从 plugin app 项目打包而来,plugin application 当然是插件业务自定义的 Application 子类,在打包 plugin.zip 的阶段(maoyan-plugin),shadow gradle plugin 会将 plugin application 改造为 ShadowApplication 的子类
通过解析 plugin apk 可以得到配置在 AndroidManifest.xml 里的 plugin appliaction(PackageManager.getPackageArchiveInfo),它是 ShadowApplication 的子类,用包含了 plugin apk 的 PluginClassLoader 实例化出来
一个 plugin.zip 里可以有多个 plugin apk,根据 partKey 区分,所以在初始化 Application 时需要传入 partKey,从而找到对应 apk 里的 plugin application,然后手动调用它的生命周期方法:attachBaseContext 和 onCreate
object LoadPluginBloc {
// 构造 ShadowApplication
@Throws(LoadPluginException::class)
fun loadPlugin(
executorService: ExecutorService,
pluginPackageInfoSet: MutableSet<PackageInfo>,
allPluginPackageInfo: () -> (Array<PackageInfo>),
componentManager: ComponentManager,
lock: ReentrantLock,
pluginPartsMap: MutableMap<String, PluginParts>,
hostAppContext: Context,
installedApk: InstalledApk,
loadParameters: LoadParameters
): Future<*> {
// ...
val buildApplication = executorService.submit(Callable {
val pluginClassLoader = buildClassLoader.get()
val resources = buildResources.get()
val pluginInfo = buildPluginInfo.get()
val packageInfo = getPackageInfo.get()
val appComponentFactory = buildAppComponentFactory.get()
CreateApplicationBloc.createShadowApplication(
pluginClassLoader,
pluginInfo,
resources,
hostAppContext,
componentManager,
packageInfo.applicationInfo,
appComponentFactory
)
})
// ...
}
}
}
/**
* 初始化插件Application类
*
* @author cubershi
*/
object CreateApplicationBloc {
@Throws(CreateApplicationException::class)
fun createShadowApplication(
pluginClassLoader: PluginClassLoader,
pluginInfo: PluginInfo,
resources: Resources,
hostAppContext: Context,
componentManager: ComponentManager,
applicationInfo: ApplicationInfo,
appComponentFactory: ShadowAppComponentFactory
): ShadowApplication {
try {
val appClassName = pluginInfo.applicationClassName
?: ShadowApplication::class.java.name
val shadowApplication = appComponentFactory.instantiateApplication(pluginClassLoader, appClassName)
val partKey = pluginInfo.partKey
shadowApplication.setPluginResources(resources)
shadowApplication.setPluginClassLoader(pluginClassLoader)
shadowApplication.setPluginComponentLauncher(componentManager)
shadowApplication.setBroadcasts(componentManager.getBroadcastsByPartKey(partKey))
shadowApplication.setAppComponentFactory(appComponentFactory)
shadowApplication.applicationInfo = applicationInfo
shadowApplication.setBusinessName(pluginInfo.businessName)
shadowApplication.setPluginPartKey(partKey)
//和ShadowActivityDelegate.initPluginActivity一样,attachBaseContext放到最后
shadowApplication.setHostApplicationContextAsBase(hostAppContext)
shadowApplication.setTheme(applicationInfo.theme)
return shadowApplication
} catch (e: Exception) {
throw CreateApplicationException(e)
}
}
}
abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, DI, ContentProviderDelegateProvider {
// 手动调用 Application 的生命周期方法:attachBaseContext 和 onCreate
fun callApplicationOnCreate(partKey: String) {
fun realAction() {
val pluginParts = getPluginParts(partKey)
pluginParts?.let {
val application = pluginParts.application
application.attachBaseContext(mHostAppContext)
mPluginContentProviderManager.createContentProviderAndCallOnCreate(
application, partKey, pluginParts)
application.onCreate()
}
}
if (isUiThread()) {
realAction()
} else {
val waitUiLock = CountDownLatch(1)
mUiHandler.post {
realAction()
waitUiLock.countDown()
}
waitUiLock.await();
}
}
}
Plugin Activity
Plugin Activity 实际上并没有在 Host AndroidManifest.xml 里注册,所以不能使用常规的方式 Context.startActivity 来打开,为了让这些没有注册的 Plugin Activity 正常运行,需要用一个桩为它们提供运行环境,这个桩 Activity 就是 PluginDefaultProxyActivity
Host 通过 PluginManager.enter 替代 Context.startActivity 来打开 Plugin Activity,需要指明是哪个 plugin.zip 里的哪个 plugin.apk 里的哪个 Activity
Bundle{
pluginZipPath = /data/user/0/com.tencent.shadow.sample.host/files/plugin-debug.zip,
KEY_PLUGIN_PART_KEY = sample-plugin-app,
KEY_ACTIVITY_CLASSNAME = com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity
}
SamplePluginManager.enter
SamplePluginManager.onStartActivity
将目标 Activity 替换为桩 Activity,并在 Plugin Process 打开桩页面
public abstract class FastPluginManager {
public void startPluginActivity(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mPluginLoader.startActivityInPluginProcess(intent);
}
public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
loadPlugin(installedPlugin.UUID, partKey);
Map map = mPluginLoader.getLoadedPlugin();
Boolean isCall = (Boolean) map.get(partKey);
if (isCall == null || !isCall) {
mPluginLoader.callApplicationOnCreate(partKey);
}
return mPluginLoader.convertActivityIntent(pluginIntent);
}
}
// Plugin Process,IPC
DynamicPluginLoader.convertActivityIntent
SampleComponentManager.convertPluginActivityIntent
// 得到最终的 Intent,它指向桩 Activity
Intent{
mComponent = ComponentInfo{
mPackage = com.tencent.shadow.sample.host,
mClass = com.tencent.shadow.sample.plugin.runtime.PluginDefaultProxyActivity
},
mExtra = {
CM_BUSINESS_NAME=sample-plugin-app,
PROCESS_ID_KEY=14731661,
CM_LOADER_BUNDLE=Bundle{
CM_CLASS_NAME=com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity,
CM_PACKAGE_NAME=com.tencent.shadow.sample.host,
CM_ACTIVITY_INFO=PluginActivityInfo{
className = com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity,
themeResource = 16973830,
activityInfo = ActivityInfo{...}
}
},
LOADER_VERSION=local,
CM_EXTRAS_BUNDLE=null,
CM_PART=sample-plugin-app
}
}
// Plugin Process 打开桩 Activity,IPC
BinderPluginLoader.startActivityInPluginProcess
DynamicPluginLoader.startActivityInPluginProcess
Context.startActivity
桩 PluginDefaultProxyActivity 在内部实例化目标 Activity 并将 AM 的调用转发给它
目标 Activity 被 Shadow Gradle Plugin 改造为继承自 ShadowActivity,一些内部调用如 setContentView 将被转发给桩 Activity
abstract class GeneratedPluginContainerActivity extends Activity implements GeneratedHostActivityDelegator {
GeneratedHostActivityDelegate hostActivityDelegate;
@Override
protected void onCreate(Bundle arg0) {
if (hostActivityDelegate != null) {
hostActivityDelegate.onCreate(arg0);
} else {
super.onCreate(arg0);
}
}
@Override
protected void onDestroy() {
if (hostActivityDelegate != null) {
hostActivityDelegate.onDestroy();
} else {
super.onDestroy();
}
}
@Override
public void finish() {
if (hostActivityDelegate != null) {
hostActivityDelegate.finish();
} else {
super.finish();
}
}
}
public class ShadowActivity extends PluginActivity {
@Override
public void setContentView(int layoutResID) {
if ("merge".equals(XmlPullParserUtil.getLayoutStartTagName(getResources(), layoutResID))) {
//如果传进来的xml文件的根tag是merge时,需要特殊处理
View decorView = hostActivityDelegator.getWindow().getDecorView();
ViewGroup viewGroup = decorView.findViewById(android.R.id.content);
LayoutInflater.from(this).inflate(layoutResID, viewGroup);
} else {
View inflate = LayoutInflater.from(this).inflate(layoutResID, null);
hostActivityDelegator.setContentView(inflate);
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
final Intent pluginIntent = new Intent(intent);
pluginIntent.setExtrasClassLoader(mPluginClassLoader);
ComponentName callingActivity = new ComponentName(getPackageName(), getClass().getName());
final boolean success = mPluginComponentLauncher.startActivityForResult(hostActivityDelegator, pluginIntent, requestCode, options, callingActivity);
if (!success) {
hostActivityDelegator.startActivityForResult(intent, requestCode, options);
}
}
}
一些细节问题
public class ShadowActivity extends PluginActivity {
// 要返回 Plugin Application 而不是 Host Application
@Override
public final ShadowApplication getApplication() {
return mPluginApplication;
}
// 要用 Plugin Activity 名称而不是桩的名称
@Override
public SharedPreferences getPreferences(int mode) {
return super.getSharedPreferences(getLocalClassName(), mode);
}
// Plugin Activity 都是没有注册进 AndroidManifest.xml 的,需要将 Intent 转发给 ComponentManager
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
final Intent pluginIntent = new Intent(intent);
pluginIntent.setExtrasClassLoader(mPluginClassLoader);
ComponentName callingActivity = new ComponentName(getPackageName(), getClass().getName());
final boolean success = mPluginComponentLauncher.startActivityForResult(hostActivityDelegator, pluginIntent, requestCode, options, callingActivity);
if (!success) {
hostActivityDelegator.startActivityForResult(intent, requestCode, options);
}
}
}
Plugin Service
Service 是需要静态配置在 AndroidManifest.xml 里的,那么 Plugin Service 自然也不能通过常规的 Context.startService 启动,而是由 PluginServiceManager 负责管理 Service 的生命周期
所有的 Plugin Activity 都继承自 ShadowActivity,它重写了 Context.startService 把 Intent 转发给 ComponentManager 处理
public class ShadowContext {
@Override
public ComponentName startService(Intent service) {
if (service.getComponent() == null) {
return super.startService(service);
}
Pair<Boolean, ComponentName> ret = mPluginComponentLauncher.startService(this, service);
if (!ret.first)
return super.startService(service);
return ret.second;
}
}
abstract class ComponentManager : PluginComponentLauncher {
override fun startService(context: ShadowContext, service: Intent): Pair<Boolean, ComponentName?> {
if (service.isPluginComponent()) {
// 插件service intent不需要转换成container service intent,直接使用intent
val component = mPluginServiceManager!!.startPluginService(service)
if (component != null) {
return Pair(true, component)
}
}
return Pair(false, service.component)
}
}
ComponentManager 又交由 PluginServiceManager 处理 Service:
0. 同 Plugin Activity,Plugin Service 也被改造为继承自 ShadowService
- 实例化 Plugin Service
- attachBaseContext
- onCreate
- onStartCommand
class PluginServiceManager {
fun startPluginService(service: Intent) =
execInMainThread {
delegate.startPluginService(service)
}
}
class UnsafePluginServiceManager {
fun startPluginService(intent: Intent): ComponentName? {
val componentName = intent.component!!
// 检查所请求的service是否已经存在
if (!mAliveServicesMap.containsKey(componentName)) {
// 不存在则创建
val service = createServiceAndCallOnCreate(intent)
mAliveServicesMap[componentName] = service
// 通过startService启动集合
mServiceStartByStartServiceSet.add(componentName)
}
mAliveServicesMap[componentName]?.onStartCommand(intent, 0, getNewStartId())
return componentName
}
private fun createServiceAndCallOnCreate(intent: Intent): ShadowService {
val service = newServiceInstance(intent)
service.onCreate()
return service
}
private fun newServiceInstance(intent: Intent): ShadowService {
val componentName = intent.component!!
val businessName = mPluginLoader.mComponentManager.getComponentBusinessName(componentName)
val partKey = mPluginLoader.mComponentManager.getComponentPartKey(componentName)
val className = componentName.className
val tmpShadowDelegate = TmpShadowDelegate()
mPluginLoader.inject(tmpShadowDelegate, partKey!!)
val service = tmpShadowDelegate.getAppComponentFactory()
.instantiateService(tmpShadowDelegate.getPluginClassLoader(), className, intent)
service.setPluginResources(tmpShadowDelegate.getPluginResources())
service.setPluginClassLoader(tmpShadowDelegate.getPluginClassLoader())
service.setShadowApplication(tmpShadowDelegate.getPluginApplication())
service.setPluginComponentLauncher(tmpShadowDelegate.getComponentManager())
service.applicationInfo = tmpShadowDelegate.getPluginApplication().applicationInfo
service.setBusinessName(businessName)
service.setPluginPartKey(partKey)
//和ShadowActivityDelegate.initPluginActivity一样,attachBaseContext放到最后
service.setHostContextAsBase(mHostContext)
return service
}
}
如果是 bindService:
- 实例化 Plugin Service
- attachBaseContext
- onCreate
- onBind
- onServiceConnected
private open class UnsafePluginServiceManager(
private val mPluginLoader: ShadowPluginLoader,
private val mHostContext: Context
) {
fun bindPluginService(intent: Intent, conn: ServiceConnection, flags: Int): Boolean {
// todo #25 目前实现未处理flags,后续实现补上
val componentName = intent.component!!
// 1. 看要bind的service是否创建并在运行了
if (!mAliveServicesMap.containsKey(componentName)) {
// 如果还没创建,则创建,并保持
val service = createServiceAndCallOnCreate(intent)
mAliveServicesMap[componentName] = service
}
val service = mAliveServicesMap[componentName]!!
// 2. 检查是否该Service之前是否被绑定过了
if (!mServiceBinderMap.containsKey(componentName)) {
// 还没调用过onBinder,在这里调用
mServiceBinderMap[componentName] = service.onBind(intent)
}
// 3. 如果binder不为空,则要回调onServiceConnected
mServiceBinderMap[componentName]?.let {
// 检查该connections是否存在了
if (mServiceConnectionMap.containsKey(componentName)) {
if (!mServiceConnectionMap[componentName]!!.contains(conn)) {
// 如果service的bind connection集合中不包含该connection,则加入
mServiceConnectionMap[componentName]!!.add(conn)
mConnectionIntentMap[conn] = intent
// 回调onServiceConnected
conn.onServiceConnected(componentName, it)
} else {
// 已经包含该connection了,说明onServiceConnected已经回调过了,所以这里什么也不用干
}
} else {
// 该connection是第一个bind connection
val connectionSet = HashSet<ServiceConnection>()
connectionSet.add(conn)
mServiceConnectionMap[componentName] = connectionSet
mConnectionIntentMap[conn] = intent
// 回调onServiceConnected
conn.onServiceConnected(componentName, it)
}
}
return true
}
}
总之 Host APP 是没有 Plugin Service 记录的,所有的 Plugin Service 实例都是作为一个普通的 Java 对象被 PluginServiceManager 管理,而且不支持子进程,所有的 Plugin Service 都运行在 Plugin Process(实际上所有的 Component 都运行在 Plugin Process,不支持子进程)
stopService/unbindService 则对应地执行 onDestroy/onUnbind 生命周期函数,然后移除相关实例即可
Plugin ContentProvider
原先在 plugin.apk 里静态注册在 AndroidManifest.xml 里的 ContentProvider 自然也要我们手动处理:
- PackageManager.getPackageArchiveInfo + PackageManager.GET_PROVIDERS 可以获得静态注册在清单文件里的 ContentProvider
- 在 Application.attachBaseContext 之后,并在 Application.onCreate 之前实例化它们,用 ContentProvider.attachInfo 初始化它们
object LoadPluginBloc {
fun loadPlugin(...): Future<*> {
// ... 从 apk 里解析出所有 ContentProvider
val getPackageInfo = executorService.submit(Callable {
val archiveFilePath = installedApk.apkFilePath
val packageManager = hostAppContext.packageManager
val packageArchiveInfo = packageManager.getPackageArchiveInfo(
archiveFilePath,
PackageManager.GET_ACTIVITIES or PackageManager.GET_META_DATA or PackageManager.GET_SERVICES
or PackageManager.GET_PROVIDERS or PackageManager.GET_SIGNATURES
) ?: throw NullPointerException("getPackageArchiveInfo return null.archiveFilePath==$archiveFilePath")
val tempContext = ShadowContext(hostAppContext, 0).apply {
setBusinessName(loadParameters.businessName)
}
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
tempContext.dataDir
} else {
File(tempContext.filesDir, "dataDir")
}
dataDir.mkdirs()
packageArchiveInfo.applicationInfo.nativeLibraryDir = installedApk.libraryPath
packageArchiveInfo.applicationInfo.dataDir = dataDir.absolutePath
packageArchiveInfo.applicationInfo.processName = hostAppContext.applicationInfo.processName
packageArchiveInfo.applicationInfo.uid = hostAppContext.applicationInfo.uid
lock.withLock { pluginPackageInfoSet.add(packageArchiveInfo) }
packageArchiveInfo
})
// 放入 PluginInfo._mProviders
val buildPluginInfo = executorService.submit(Callable {
val packageInfo = getPackageInfo.get()
ParsePluginApkBloc.parse(packageInfo, loadParameters, hostAppContext)
})
// ... 再放入 ComponentManager.mProviders
val buildRunningPlugin = executorService.submit {
if (File(installedApk.apkFilePath).exists().not()) {
throw LoadPluginException("插件文件不存在.pluginFile==" + installedApk.apkFilePath)
}
val pluginPackageManager = buildPackageManager.get()
val pluginClassLoader = buildClassLoader.get()
val resources = buildResources.get()
val pluginInfo = buildPluginInfo.get()
val shadowApplication = buildApplication.get()
val appComponentFactory = buildAppComponentFactory.get()
lock.withLock {
componentManager.addPluginApkInfo(pluginInfo)
pluginPartsMap[pluginInfo.partKey] = PluginParts(
appComponentFactory,
shadowApplication,
pluginClassLoader,
resources,
pluginInfo.businessName,
pluginPackageManager
)
PluginPartInfoManager.addPluginInfo(pluginClassLoader, PluginPartInfo(shadowApplication, resources,
pluginClassLoader, pluginPackageManager))
}
}
return buildRunningPlugin
}
}
// 由 ComponentManager 统一管理
abstract class ComponentManager : PluginComponentLauncher {
fun addPluginApkInfo(pluginInfo: PluginInfo) {
fun common(pluginComponentInfo: PluginComponentInfo,componentName:ComponentName) {
packageNameMap[pluginComponentInfo.className!!] = pluginInfo.packageName
val previousValue = pluginInfoMap.put(componentName, pluginInfo)
if (previousValue != null) {
throw IllegalStateException("重复添加Component:$componentName")
}
pluginComponentInfoMap[componentName] = pluginComponentInfo
}
pluginInfo.mActivities.forEach {
val componentName = ComponentName(pluginInfo.packageName, it.className!!)
common(it,componentName)
componentMap[componentName] = onBindContainerActivity(componentName)
}
pluginInfo.mServices.forEach {
val componentName = ComponentName(pluginInfo.packageName, it.className!!)
common(it,componentName)
}
pluginInfo.mProviders.forEach {
val componentName = ComponentName(pluginInfo.packageName, it.className!!)
mPluginContentProviderManager!!.addContentProviderInfo(pluginInfo.partKey,it,onBindContainerContentProvider(componentName))
}
}
}
abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, DI, ContentProviderDelegateProvider {
// 手动调用 Application 的生命周期方法:attachBaseContext 和 onCreate
fun callApplicationOnCreate(partKey: String) {
fun realAction() {
val pluginParts = getPluginParts(partKey)
pluginParts?.let {
val application = pluginParts.application
application.attachBaseContext(mHostAppContext)
mPluginContentProviderManager.createContentProviderAndCallOnCreate(
application, partKey, pluginParts)
application.onCreate()
}
}
if (isUiThread()) {
realAction()
} else {
val waitUiLock = CountDownLatch(1)
mUiHandler.post {
realAction()
waitUiLock.countDown()
}
waitUiLock.await();
}
}
}
class PluginContentProviderManager() : UriConverter.UriParseDelegate {
fun addContentProviderInfo(partKey: String, pluginProviderInfo: PluginProviderInfo, containerProviderInfo: ContainerProviderInfo) {
if (providerMap.containsKey(pluginProviderInfo.authority)) {
throw RuntimeException("重复添加 ContentProvider")
}
providerAuthorityMap[pluginProviderInfo.authority!!] = containerProviderInfo.authority
var pluginProviderInfos: HashSet<PluginProviderInfo>? = null
if (pluginProviderInfoMap.containsKey(partKey)) {
pluginProviderInfos = pluginProviderInfoMap[partKey]
} else {
pluginProviderInfos = HashSet()
}
pluginProviderInfos?.add(pluginProviderInfo)
pluginProviderInfoMap.put(partKey, pluginProviderInfos)
}
fun createContentProviderAndCallOnCreate(mContext: Context, partKey: String, pluginParts: PluginParts?) {
pluginProviderInfoMap[partKey]?.forEach {
try {
val contentProvider = pluginParts!!.appComponentFactory
.instantiateProvider(pluginParts.classLoader, it.className)
contentProvider?.attachInfo(mContext, it.providerInfo)
providerMap[it.authority!!] = contentProvider
} catch (e: Exception) {
throw RuntimeException("partKey==$partKey className==${it.className} providerInfo==${it.providerInfo}", e)
}
}
}
}