腾讯新开源的插件化框架 Shadow,原来是这么玩的
/ 今日科技快讯 /
8月27日,哔哩哔哩发布了2019年第二季度财报。月活用户连续攀升和营收结构趋于平衡,成为本季度B站成绩单的关键词。根据财报显示,第二季度,B站月活用户达到1.1亿,实现了900万的环比净增,创造了B站自2017年以来单季增长的记录。在此基础上,移动端月活达9620万,同比增长35%;日活用户达3320万,同比增长41%。
/ 作者简介 /
本篇文章来自ZY5A59的投稿,分享了他对Shadow插件化框架的相关理解,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。
ZY5A59的博客地址:
https://juejin.im/user/58d9d015ac502e0058df1f96
/ 框架简单介绍 /
Shadow是最近腾讯开源的一款插件化框架。原理是使用宿主代理的方式实现组件的生命周期。目前的插件化框架,大部分都是使用hook系统的方式来做的。使用代理的基本上没有成体系的框架,只是一些小demo,Shadow框架的开源,在系统api 控制越来越严格的趋势下,算是一个新的方向。Shadow最大的两个亮点是:
零反射
框架自身动态化
下面就具体分析一下框架的实现。
Shadow框架的开源地址:
https://github.com/Tencent/Shadow
/ 框架结构分析 /
框架结构图
项目目录结构
├── projects
│ ├── sample // 示例代码
│ │ ├── README.md
│ │ ├── maven
│ │ ├── sample-constant // 定义一些常量
│ │ ├── sample-host // 宿主实现
│ │ ├── sample-manager // PluginManager 实现
│ │ └── sample-plugin // 插件的实现
│ ├── sdk // 框架实现代码
│ │ ├── coding // lint
│ │ ├── core
│ │ │ ├── common
│ │ │ ├── gradle-plugin // gradle 插件
│ │ │ ├── load-parameters
│ │ │ ├── loader // 负责加载插件
│ │ │ ├── manager // 装载插件,管理插件
│ │ │ ├── runtime // 插件运行时需要,包括占位 Activity,占位 Provider 等等
│ │ │ ├── transform // Transform 实现,用于替换插件 Activity 父类等等
│ │ │ └── transform-kit
│ │ └── dynamic // 插件自身动态化实现,包括一些接口的抽象
框架主要类说明
PluginContainerActivity
代理Activity。
ShadowActivity
插件Activity统一父类,在打包时通过Transform统一替换。
ComponentManager
管理插件和宿主代理的对应关系。
PluginManager
装载插件。
PluginLoader
管理插件Activity生命周期等等。
sample示例代码 AndroidManifest.xml分析
注册sample MainActivity
负责启动插件。
注册代理Activity
注册了三个代理Activity,分别是 PluginDefaultProxyActivity,PluginSingleInstance1ProxyActivity,PluginSingleTask1ProxyActivity。可以看到,这三个Activity都是继承自PluginContainerActivity,只是设置了不同的launchMode,这里就明显的看出来,PluginContainerActivity就是代理Activity。
注册代理Provider
PluginContainerContentProvider也是代理Provider。
Activity实现
关于插件Activity的实现,我们主要看两个地方:
替换插件Activity的父类
宿主中如何启动插件Activity
插件中如何启动插件Activity
替换插件Activity的父类
Shadow中有一个比较巧妙的地方,就是插件开发的时候,插件的Activity还是正常继承Activity,在打包的时候,会通过Transform替换其父类为ShadowActivity。
projects/sdk/core/transform 和 projects/sdk/core/transform-kit 两个项目就是Transform,入口是ShadowTransform。这里对Transform做了一些封装,提供了友好的开发方式,这里就不多做分析了,我们主要看下TransformManager。
class TransformManager(ctClassInputMap: Map<CtClass, InputClass>,
classPool: ClassPool,
useHostContext: () -> Array<String>
) : AbstractTransformManager(ctClassInputMap, classPool) {
override val mTransformList: List<SpecificTransform> = listOf(
ApplicationTransform(),
ActivityTransform(),
ServiceTransform(),
InstrumentationTransform(),
RemoteViewTransform(),
FragmentTransform(ctClassInputMap),
DialogTransform(),
WebViewTransform(),
ContentProviderTransform(),
PackageManagerTransform(),
KeepHostContextTransform(useHostContext())
)
}
这里的mTransformList就是要依次执行的Transform内容,也就是需要替换的类映射。我们以ApplicationTransform和ActivityTransform为例。
class ApplicationTransform : SimpleRenameTransform(
mapOf(
"android.app.Application"
to "com.tencent.shadow.core.runtime.ShadowApplication"
,
"android.app.Application\$ActivityLifecycleCallbacks"
to "com.tencent.shadow.core.runtime.ShadowActivityLifecycleCallbacks"
)
)
class ActivityTransform : SimpleRenameTransform(
mapOf(
"android.app.Activity"
to "com.tencent.shadow.core.runtime.ShadowActivity"
)
)
可以看到,打包过程中,插件的Application会被替换成ShadowApplication,Activity会被替换成ShadowActivity,这里主要看一下ShadowActivity的继承关系。
为何插件Activity可以不用继承Activity呢?因为在代理Activity的方式中,插件Activity是被当作一个普通类来使用的,只要负责执行对应的生命周期即可。
宿主中如何启动插件Activity
宿主中启动插件Activity原理如下图:
我们就从sample里的MainActivity开始看起。
sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity是demo的主入口。启动插件的方式是:
startPluginButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// ...
Intent intent = new Intent(MainActivity.this, PluginLoadActivity.class);
intent.putExtra(Constant.KEY_PLUGIN_PART_KEY, partKey);
intent.putExtra(Constant.KEY_ACTIVITY_CLASSNAME, "com.tencent.shadow.sample.plugin.app.lib.gallery.splash.SplashActivity");
// ...
startActivity(intent);
}
});
可以看到,这里是通过PluginLoadActivity来启动的,传入了要启动的插件Activity:SplashActivity,接着就到PluginLoadActivity里看一下具体的启动。
class PluginLoadActivity extends Activity {
public void startPlugin() {
PluginHelper.getInstance().singlePool.execute(new Runnable() {
public void run() {
HostApplication.getApp().loadPluginManager(PluginHelper.getInstance().pluginManagerFile);
// ...
bundle.putString(Constant.KEY_ACTIVITY_CLASSNAME, getIntent().getStringExtra(Constant.KEY_ACTIVITY_CLASSNAME));
HostApplication.getApp().getPluginManager()
.enter(PluginLoadActivity.this, Constant.FROM_ID_START_ACTIVITY, bundle, new EnterCallback() {
public void onShowLoadingView(final View view) {
// 设置加载的样式
mHandler.post(new Runnable() {
public void run() {
mViewGroup.addView(view);
}
});
}
// ...
});
}
});
}
}
这里可以看到,是通过HostApplication获取到PluginManager,然后调用其enter方法,进入插件。这里先看看返回的PluginManager是什么。
class HostApplication extends Application {
public void loadPluginManager(File apk) {
if (mPluginManager == null) {
// 创建 PluginManager
mPluginManager = Shadow.getPluginManager(apk);
}
}
public PluginManager getPluginManager() {
return mPluginManager;
}
}
public class Shadow {
public static PluginManager getPluginManager(File apk){
final FixedPathPmUpdater fixedPathPmUpdater = new FixedPathPmUpdater(apk);
File tempPm = fixedPathPmUpdater.getLatest();
if (tempPm != null) {
// 创建 DynamicPluginManager
return new DynamicPluginManager(fixedPathPmUpdater);
}
return null;
}
}
可以看到,HostApplication里返回的其实是一个DynamicPluginManager实例,那么接下来就要看DynamicPluginManager的enter方法。
class DynamicPluginManager implements PluginManager {
public void enter(Context context, long fromId, Bundle bundle, EnterCallback callback) {
// 加载 mManagerImpl 实现,这里涉及到了框架的自身动态化,在后面会讲到,这里只要知道,mManagerImpl 最终是 SamplePluginManager 实例即可
updateManagerImpl(context);
// mManagerImpl 是 SamplePluginManager 实例,调用其实现
mManagerImpl.enter(context, fromId, bundle, callback);
mUpdater.update();
}
}
通过上面的代码我们知道了,调用DynamicPluginManager.enter会转发到SamplePluginManager.enter中去,接着就看看这个实现。
class SamplePluginManager extends FastPluginManager {
public void enter(final Context context, long fromId, Bundle bundle, final EnterCallback callback) {
// ...
// 启动 Activity
onStartActivity(context, bundle, callback);
// ...
}
private void onStartActivity(final Context context, Bundle bundle, final EnterCallback callback) {
// ...
final String className = bundle.getString(Constant.KEY_ACTIVITY_CLASSNAME);
// ...
final Bundle extras = bundle.getBundle(Constant.KEY_EXTRAS);
if (callback != null) {
// 创建 loading view
final View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);
callback.onShowLoadingView(view);
}
executorService.execute(new Runnable() {
public void run() {
// ...
// 加载插件
InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);
// 创建插件 Intent
Intent pluginIntent = new Intent();
pluginIntent.setClassName(
context.getPackageName(),
className
);
if (extras != null) {
pluginIntent.replaceExtras(extras);
}
// 启动插件 Activity
startPluginActivity(context, installedPlugin, partKey, pluginIntent);
// ...
}
});
}
}
在SamplePluginManager.enter中,调用onStartActivity启动插件Activity,其中开线程去加载插件,然后调用 startPluginActivity。startPluginActivity 实现在其父类FastPluginManager里。
class FastPluginManager {
public void startPluginActivity(Context context, InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
Intent intent = convertActivityIntent(installedPlugin, partKey, pluginIntent);
if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
}
其中的重点是convertActivityIntent,将插件intent转化成宿主的intent,然后调用 系统的context.startActivity启动插件。这里的context是PluginLoadActivity.this,从其enter方法中一直传进来的。下面重点看看convertActivityIntent的实现。
class FastPluginManager {
public Intent convertActivityIntent(InstalledPlugin installedPlugin, String partKey, Intent pluginIntent) throws RemoteException, TimeoutException, FailedException {
// 创建 mPluginLoader
loadPlugin(installedPlugin.UUID, partKey);
// 先调用 Application onCreate 方法
mPluginLoader.callApplicationOnCreate(partKey);
// 转化插件 intent 为 代理 Activity intent
return mPluginLoader.convertActivityIntent(pluginIntent);
}
}
到了这里其实有一些复杂了,因为mPluginLoader是通过Binder去调用相关方法的。由于这里涉及到了Binder的使用,需要读者了解Binder相关的知识,代码比较繁琐,这里就不具体分析代码实现了,用一张图理顺一下对应的关系:
通过上面的Binder对应图,我们可以简单的理解为,调用mPluginLoader中的方法,就是调用DynamicPluginLoader中的方法,调用mPpsController的方法,就是调用PluginProcessService中的方法。所以这里的mPluginLoader.convertActivityIntent相当于调用了DynamicPluginLoader.convertActivityIntent。
internal class DynamicPluginLoader(hostContext: Context, uuid: String) {
fun convertActivityIntent(pluginActivityIntent: Intent): Intent? {
return mPluginLoader.mComponentManager.convertPluginActivityIntent(pluginActivityIntent)
}
}
调用到了ComponentManager.convertPluginActivityIntent方法。
abstract class ComponentManager : PluginComponentLauncher {
override fun convertPluginActivityIntent(pluginIntent: Intent): Intent {
return if (pluginIntent.isPluginComponent()) {
pluginIntent.toActivityContainerIntent()
} else {
pluginIntent
}
}
private fun Intent.toActivityContainerIntent(): Intent {
// ...
return toContainerIntent(bundleForPluginLoader)
}
private fun Intent.toContainerIntent(bundleForPluginLoader: Bundle): Intent {
val className = component.className!!
val packageName = packageNameMap[className]!!
component = ComponentName(packageName, className)
val containerComponent = componentMap[component]!!
val businessName = pluginInfoMap[component]!!.businessName
val partKey = pluginInfoMap[component]!!.partKey
val pluginExtras: Bundle? = extras
replaceExtras(null as Bundle?)
val containerIntent = Intent(this)
containerIntent.component = containerComponent
bundleForPluginLoader.putString(CM_CLASS_NAME_KEY, className)
bundleForPluginLoader.putString(CM_PACKAGE_NAME_KEY, packageName)
containerIntent.putExtra(CM_EXTRAS_BUNDLE_KEY, pluginExtras)
containerIntent.putExtra(CM_BUSINESS_NAME_KEY, businessName)
containerIntent.putExtra(CM_PART_KEY, partKey)
containerIntent.putExtra(CM_LOADER_BUNDLE_KEY, bundleForPluginLoader)
containerIntent.putExtra(LOADER_VERSION_KEY, BuildConfig.VERSION_NAME)
containerIntent.putExtra(PROCESS_ID_KEY, DelegateProviderHolder.sCustomPid)
return containerIntent
}
}
这里最终调用到toContainerIntent方法,终于水落石出了。在toContainerIntent中,创建了新的宿主代理Activity的 intent,这里的containerComponent对应的就是前面在Manifest里注册的PluginDefaultProxyActivity,返回代理activity intent 以后,调用context.startActivity(intent)就启动了代理Activity。
PluginDefaultProxyActivity继承自PluginContainerActivity,这个也就是整个框架的代理Activity,在PluginContainerActivity里,就是常规的分发生命周期了。和之前在插件化原理里介绍的差不多了。中间通过HostActivityDelegate分发生命周期。
class ShadowActivityDelegate(private val mDI: DI) : HostActivityDelegate, ShadowDelegate() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
// 设置 application,resources 等等
mDI.inject(this, partKey)
// 创建插件资源
mMixResources = MixResources(mHostActivityDelegator.superGetResources(), mPluginResources)
// 设置插件主题
mHostActivityDelegator.setTheme(pluginActivityInfo.themeResource)
try {
val aClass = mPluginClassLoader.loadClass(pluginActivityClassName)
// 创建插件 activity
val pluginActivity = PluginActivity::class.java.cast(aClass.newInstance())
// 初始化插件 activity
initPluginActivity(pluginActivity)
mPluginActivity = pluginActivity
//设置插件AndroidManifest.xml 中注册的WindowSoftInputMode
mHostActivityDelegator.window.setSoftInputMode(pluginActivityInfo.activityInfo.softInputMode)
// 获取 savedInstanceState
val pluginSavedInstanceState: Bundle? = savedInstanceState?.getBundle(PLUGIN_OUT_STATE_KEY)
pluginSavedInstanceState?.classLoader = mPluginClassLoader
// 调用插件 activity onCreate
pluginActivity.onCreate(pluginSavedInstanceState)
mPluginActivityCreated = true
} catch (e: Exception) {
throw RuntimeException(e)
}
}
// 获取插件资源
override fun getResources(): Resources {
if (mDependenciesInjected) {
return mMixResources;
} else {
return Resources.getSystem()
}
}
}
上面就是在宿主中启动插件Activity的整个流程,下面看看在插件中如何启动Activity的。
插件中如何启动插件Activity
插件中启动Activity原理如下图:
我们上面说到过,插件Activity会在打包过程中替换其父类为ShadowActivity,很明显了,在插件中启动Activity即调用startActivity,自然就是调用ShadowActivity的startActivity了。startActivity在其父类ShadowContext里实现,我们来具体看下。
class ShadowContext extends SubDirContextThemeWrapper {
public void startActivity(Intent intent) {
final Intent pluginIntent = new Intent(intent);
// ...
final boolean success = mPluginComponentLauncher.startActivity(this, pluginIntent);
// ...
}
}
可以看到,是通过mPluginComponentLauncher.startActivity继续调用的,mPluginComponentLauncher就是ComponentManager的一个实例,是在前面说到的初始化插件Activity的时候设置的。内部实现就比较简单了。
abstract class ComponentManager : PluginComponentLauncher {
override fun startActivity(shadowContext: ShadowContext, pluginIntent: Intent): Boolean {
return if (pluginIntent.isPluginComponent()) {
shadowContext.superStartActivity(pluginIntent.toActivityContainerIntent())
true
} else {
false
}
}
}
public class ShadowContext extends SubDirContextThemeWrapper {
public void superStartActivity(Intent intent) {
// 调用系统 startActivity
super.startActivity(intent);
}
}
通过调用toActivityContainerIntent转化intent为代理Activity的intent,然后调用系统startActivity启动代理Activity,剩下的步骤就和上面宿主启动插件Activity中讲到的一样了。到现在,我们就对框架中Activity的启动基本了解了。
/ Service实现 /
Service的实现,我们直接看 插件中如何启动的即可。看一下ShadowContext中的startService实现:
public class ShadowContext extends SubDirContextThemeWrapper {
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;
}
}
也是调用mPluginComponentLauncher.startService,这里我们就比较熟悉了,就是ComponentManager.startService。
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)
// ...
}
return Pair(false, service.component)
}
}
这里直接调用PluginServiceManager.startPluginService。
class PluginServiceManager(private val mPluginLoader: ShadowPluginLoader, private val mHostContext: Context) {
fun startPluginService(intent: Intent): ComponentName? {
val componentName = intent.component
// 检查所请求的service是否已经存在
if (!mAliveServicesMap.containsKey(componentName)) {
// 创建 Service 实例并调用 onCreate 方法
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.component)
service.onCreate()
return service
}
}
可以看到,在Shadow中对Service的处理很简单,直接调用其生命周期方法,不过如此的实现方式,可能会带来一些时序问题。
/ BroadcastReceiver实现 /
广播的实现也比较常规,在插件中动态注册和发送广播,直接调用系统的方法即可,因为广播不涉及生命周期等复杂的内容。需要处理的就是在Manifest中静态注册的广播。这个理论上也和我们之前讲解插件化原理时候实现基本一致,解析Manifest,然后进行动态注册。不过在Shadow的demo里,并没有做解析,就是直接写在了代码里。
// AndroidManifest.xml
<receiver android:name="com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver">
<intent-filter>
<action android:name="com.tencent.test.action" />
</intent-filter>
</receiver>
// SampleComponentManager
public class SampleComponentManager extends ComponentManager {
public List<BroadcastInfo> getBroadcastInfoList(String partKey) {
List<ComponentManager.BroadcastInfo> broadcastInfos = new ArrayList<>();
if (partKey.equals(Constant.PART_KEY_PLUGIN_MAIN_APP)) {
broadcastInfos.add(
new ComponentManager.BroadcastInfo(
"com.tencent.shadow.sample.plugin.app.lib.usecases.receiver.MyReceiver",
new String[]{"com.tencent.test.action"}
)
);
}
return broadcastInfos;
}
}
/ ContentProvider 实现 /
关于ContentProvider的实现,其实和之前插件化原理文章中思路是一致的,也是通过注册代理ContentProvider然后分发给插件Provider,这里就不多做介绍了。
/ 框架自身动态化 /
Shadow框架还有一个特点,就是框架本身也实现了动态化,这里的实现主要是三步:
抽象接口类
在插件中实现工厂类
通过工厂类动态创建接口的实现
我们以PluginLoaderImpl为例来看看,在前面介绍Activity启动流程的时候,有说到mPluginLoader.convertActivityIntent用来转换插件intent为代理Activity的 intent,这里的mPluginLoader就是动态创建的。我们来看一下创建过程。创建入口在PluginProcessService.loadPluginLoader里。
public class PluginProcessService extends Service {
void loadPluginLoader(String uuid) throws FailedException {
// ...
PluginLoaderImpl pluginLoader = new LoaderImplLoader().load(installedApk, uuid, getApplicationContext());
// ...
}
}
接下来需要看一下LoaderImplLoader的具体实现。
final class LoaderImplLoader extends ImplLoader {
// 创建 PluginLoaderImpl 的工厂类
private final static String sLoaderFactoryImplClassName
= "com.tencent.shadow.dynamic.loader.impl.LoaderFactoryImpl";
// 动态创建 PluginLoaderImpl
PluginLoaderImpl load(InstalledApk installedApk, String uuid, Context appContext) throws Exception {
// 创建插件 ClassLoader
ApkClassLoader pluginLoaderClassLoader = new ApkClassLoader(
installedApk,
LoaderImplLoader.class.getClassLoader(),
loadWhiteList(installedApk),
1
);
// 获取插件中的 工厂类
LoaderFactory loaderFactory = pluginLoaderClassLoader.getInterface(
LoaderFactory.class,
sLoaderFactoryImplClassName
);
// 调用工厂类方法创建 PluginLoaderImpl 实例
return loaderFactory.buildLoader(uuid, appContext);
}
}
从上面的代码和注释来看,其实很简单,创建插件的ClassLoader,通过ClassLoader创建一个工厂类的实例,然后调用工厂类方法生成 PluginLoaderImpl。而工厂类和PluginLoaderImpl的实现都在插件中,就达到了框架自身的动态化。PluginManagerImpl也是一样的道理,在DynamicPluginManager.updateManagerImpl中通过ManagerImplLoader.load加载。
/ 总结 /
其实整个框架看下来,没有什么黑科技,就是代理Activity的原理加上设计模式的运用。其实目前几大插件化框架,基本上都是hook系统为主,像使用代理Activity原理的,Shadow应该算是第一个各方面实现都比较完整的框架,带来的好处就是不用去调用系统限制的api,更加稳定。在系统控制越来越严格的趋势下,也算是一个比较好的选择。原理简单,其中的设计思想可以学习~
推荐阅读:
总是听到有人说AndroidX,到底什么是AndroidX?
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注
本文地址:https://blog.csdn.net/c10WTiybQ1Ye3/article/details/100135318
下一篇: 简单jsp实现输出当前时间