简介 Shadow是最近腾讯开源的一款插件化框架。原理是使用宿主代理的方式实现组件的生命周期。目前的插件化框架,大部分都是使用hook系统的方式来做的。使用代理的基本上没有成体系的框架,只是一些小demo,Shadow框架的开源,在系统api控制越来越严格的趋势下,算是一个新的方向。框架对比 Shadow主要具有以下特点:复用独立安装App的源码:插件App的源码原本就是可以正常安装运行的。零反射无Hack实现插件技术:从理论上就已经确定无需对任何系统做兼容开发,更无任何隐藏API调用,和Google限制非公开SDK接口访问的策略完全不冲突。全动态插件框架:一次性实现完美的插件框架很难,但Shadow将这些实现全部动态化起来,使插件框架的代码成为了插件的一部分。插件的迭代不再受宿主打包了旧版本插件框架所限制。宿主增量极小:得益于全动态实现,真正合入宿主程序的代码量极小(15KB,160方法数左右)。Kotlin实现:core。loader,core。transform核心代码完全用Kotlin实现,代码简洁易维护。编译与开发环境环境准备 第一次cloneShadow的代码到本地后,建议先在命令行编译一次。在编译前,必须设置ANDROIDHOME环境变量。在编译时,必须使用gradlew脚本,以保证采用了项目配置的Gradle版本。 在命令行测试编译时可以执行这个任务:。gradlewbuild 如果没有出错,再尝试用AndroidStudio打开工程。必须使用3。4或更高版本的AndroidStudio打开工程。(业务插件开发时没有限制)必须关闭AndroidStudio的InstantRun功能。 然后就可以在IDE中选择samplehost模块直接运行了。 Shadow的所有代码都位于projects目录下的3个目录,分别是:sdk包含SDK的所有代码test包含SDK的自动化测试代码sample包含演示代码 其中sample应该是大家体验Shadow的最佳环境。详见sample目录中的README介绍。注意事项:1、Shadow是跨进程的,插件运行在插件进程,通过Binder机制通信,所以不了解Binder的,建议提前熟悉一下,否则看着会比较绕。2、Shadow的宿主和业务插件之间还有一层中间层,中间层也是以插件的形式加载,同时可以升级,有较强的灵活性。3、插件里写一个页面,比如继承自Activity,我们可以正常写,但是在编译期会修改继承关系,将其父类改为ShadowActivity,ShadowActivity实际上不是一个Activity,他持有HostActivity的代理对象,依赖此完成生命周期的回调。 这个操作是靠修改字节码实现的,自定义gradle脚本,通过javassist或者asm都可以实现,不再赘述。 以上是Shadow插件化的简单介绍;想要更多深入学习Android知识可以参考这份电子册《Android核心进阶技术手册》点击查看获取方式,免费且好用系列。 【私信:手册领取】Android核心进阶技术手册 源码分析: Shadow源码较多,我们只分析一下插件Activity是如何启动及运行的。 可以先看一下打包出来的apk的结构 我的理解pluginmanager。apkloader。apkruntime。apk是中间层 config。json是发版信息,主要用于检查更新,其中的uuid即为当前版本的唯一标示 HostApplication的onCreate方法会有一些初始化的工作,主要是把asset目录下的插件复制到指定目录,还有runtime插件的状态恢复,非核心流程,不再详述。 我们直接看启动插件的逻辑,很容易就找到加载插件的缺省页PluginLoadActivity,只有一个startPlugin方法:publicvoidstartPlugin(){PluginHelper。getInstance()。singlePool。execute(newRunnable(){Overridepublicvoidrun(){方法名虽然叫loadPluginManager,实际上并没有真正安装manager插件,只是将插件路径包装成FixedPathPmUpdater,作为构造函数的参数,创建一个DynamicPluginManager保存在Application中HostApplication。getApp()。loadPluginManager(PluginHelper。getInstance()。pluginManagerFile);BundlebundlenewBundle();插件的安装路径bundle。putString(Constant。KEYPLUGINZIPPATH,PluginHelper。getInstance()。pluginZipFile。getAbsolutePath());当前值是:samplepluginappbundle。putString(Constant。KEYPLUGINPARTKEY,getIntent()。getStringExtra(Constant。KEYPLUGINPARTKEY));要启动的插件中的Activity路径com。tencent。shadow。sample。plugin。app。lib。gallery。splash。SplashActivitybundle。putString(Constant。KEYACTIVITYCLASSNAME,getIntent()。getStringExtra(Constant。KEYACTIVITYCLASSNAME));EnterCallback主要是用于处理插件加载过程中的过度状态HostApplication。getApp()。getPluginManager()。enter(PluginLoadActivity。this,Constant。FROMIDSTARTACTIVITY,bundle,newEnterCallback(){OverridepublicvoidonShowLoadingView(finalViewview){mHandler。post(newRunnable(){Overridepublicvoidrun(){mViewGroup。addView(view);}});}OverridepublicvoidonCloseLoadingView(){finish();}OverridepublicvoidonEnterComplete(){}});}});} 懒的长篇大论,相关逻辑已经写在注释里,会执行到DynamicPluginManager的enter方法:publicvoidenter(Contextcontext,longfromId,Bundlebundle,EnterCallbackcallback){if(mLogger。isInfoEnabled()){mLogger。info(enterfromId:fromIdcallback:callback);}动态管理插件的更新逻辑updateManagerImpl(context);mManagerImpl的类型是SamplePluginManagermManagerImpl。enter(context,fromId,bundle,callback);mUpdater。update();} mManagerImpl是一个接口,上面的代码其真实实例是SamplePluginManager,updateManagerImpl方法会安装pluginmanager。apk插件,同时通过反射创建一个SamplePluginManager实例,也就是上面的mManagerImpl,同时支持pluginmanager。apk插件的更新逻辑。 所以进入SamplePluginManager的enteronStartActivity,代码逻辑比较简单,没什么可说的,需要注意一点是会启动一个线程,去加载zip包下的几个插件(runtime、loader、业务插件),而后会调用到其父类FastPluginManager的startPluginActivity方法:publicvoidstartPluginActivity(Contextcontext,InstalledPlugininstalledPlugin,StringpartKey,IntentpluginIntent)throwsRemoteException,TimeoutException,FailedException{IntentintentconvertActivityIntent(installedPlugin,partKey,pluginIntent);if(!(contextinstanceofActivity)){intent。setFlags(Intent。FLAGACTIVITYNEWTASK);}最终启动的是com。tencent。shadow。sample。plugin。runtime。PluginDefaultProxyActivityPluginDefaultProxyActivity在宿主manifest中有注册context。startActivity(intent);} 核心流程就在convertActivityIntent里,从命名就可以看出来,最终会把我们要启动的插件Activity,映射成一个在Manifest里注册的真实Activity,也就是注释中标注的PluginDefaultProxyActivity。 可以回看一下上文思考中的内容,即为Shadow第一次使用插件的主要流程,convertActivityIntent的代码如下:publicIntentconvertActivityIntent(InstalledPlugininstalledPlugin,StringpartKey,IntentpluginIntent)throwsRemoteException,TimeoutException,FailedException{这个partKey的真实值是samplepluginapploadPlugin(installedPlugin。UUID,partKey);MapmapmPluginLoader。getLoadedPlugin();BooleanisCall(Boolean)map。get(partKey);if(isCallnull!isCall){其持有的是PluginLoaderBinder的引用这里又是一次跨进程通信mPluginLoader。callApplicationOnCreate(partKey);}returnmPluginLoader。convertActivityIntent(pluginIntent);} loadPlugin:先安装中间层插件再安装业务插件,当然如果已安装,直接跳过 mPluginLoader:是一个比较关键的变量,具体他是什么初始化的,下面会具体分析 后续的代码执行逻辑可自行看源码,首先会执行loadPluginLoaderAndRuntime方法,这个方法里会初始化插件进程的服务,同时将插件进程的binder对象赋值给mPpsController:privatevoidloadPluginLoaderAndRuntime(Stringuuid,StringpartKey)throwsRemoteException,TimeoutException,FailedException{if(mPpsControllernull){partKey是启动插件的时候在PluginLoadActivity中赋值getPluginProcessServiceName获取插件进程服务的名字bindPluginProcessService启动插件进程服务由此可见,shadow宿主和插件的信息传递是进程间通信的过程bindPluginProcessService(getPluginProcessServiceName(partKey));等待链接超时时间waitServiceConnected(10,TimeUnit。SECONDS);}loadRunTime(uuid);loadPluginLoader(uuid);}。。。。。。启动PluginProcessServiceparamserviceName注册在宿主中的插件进程管理service完整名字publicfinalvoidbindPluginProcessService(finalStringserviceName){if(mServiceConnecting。get()){if(mLogger。isInfoEnabled()){mLogger。info(ppsserviceconnecting);}}if(mLogger。isInfoEnabled()){mLogger。info(bindPluginProcessServiceserviceName);}mConnectCountDownLatch。set(newCountDownLatch(1));mServiceConnecting。set(true);CountDownLatch是一个同步工具,协调多个线程之间的同步可以看下这篇文章https:www。cnblogs。comLeexyzp10470181。htmlfinalCountDownLatchstartBindingLatchnewCountDownLatch(1);finalboolean〔〕asyncResultnewboolean〔1〕;从onStartActivity方法可知,当前线程并不是UI线程mUiHandler。post(newRunnable(){Overridepublicvoidrun(){IntentintentnewIntent();serviceName的值是com。tencent。shadow。sample。host。PluginProcessPPSintent。setComponent(newComponentName(mHostContext,serviceName));booleanbindingmHostContext。bindService(intent,newServiceConnection(){OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice){service对应的是PluginProcessService中的mPpsControllerBinderif(mLogger。isInfoEnabled()){mLogger。info(onServiceConnectedconnectCountDownLatch:mConnectCountDownLatch);}mServiceConnecting。set(false);mPpsControllerPluginProcessService。wrapBinder(service);try{跨进程执行PluginProcessService的setUuidManager方法UuidManagerBinder内部封装了三个方法,可以让插件进程拿到loader、runtime及指定其他业务插件的相关信息mPpsController。setUuidManager(newUuidManagerBinder(PluginManagerThatUseDynamicLoader。this));}catch(DeadObjectExceptione){if(mLogger。isErrorEnabled()){mLogger。error(onServiceConnectedRemoteException:e);}}catch(RemoteExceptione){if(e。getClass()。getSimpleName()。equals(TransactionTooLargeException)){if(mLogger。isErrorEnabled()){mLogger。error(onServiceConnectedTransactionTooLargeException:e);}}else{thrownewRuntimeException(e);}}try{第一次拿到的是一个nullIBinderiBindermPpsController。getPluginLoader();if(iBinder!null){mPluginLoadernewBinderPluginLoader(iBinder);}}catch(RemoteExceptionignored){if(mLogger。isErrorEnabled()){mLogger。error(onServiceConnectedmPpsControllergetPluginLoader:,ignored);}}mConnectCountDownLatch。get()。countDown();if(mLogger。isInfoEnabled()){mLogger。info(onServiceConnectedcountDown:mConnectCountDownLatch);}}OverridepublicvoidonServiceDisconnected(ComponentNamename){if(mLogger。isInfoEnabled()){mLogger。info(onServiceDisconnected);}mServiceConnecting。set(false);mPpsCmPluginL}},BINDAUTOCREATE);asyncResult〔0〕startBindingLatch。countDown();}});try{当前线程会最多等待10s,startBindingLatch的线程计数为0之前,当前线程会处在中断状态startBindingLatch。await(10,TimeUnit。SECONDS);if(!asyncResult〔0〕){thrownewIllegalArgumentException(无法绑定PPS:serviceName);}}catch(InterruptedExceptione){thrownewRuntimeException(e);}} 上文说过,整个流程是运行在子线程,所以启动服务要post到UI线程 后续执行的loadRunTime(uuid);loadPluginLoader(uuid);方法即为启动中间层插件的逻辑,大同小异,只分析loadPluginLoader的执行逻辑,因为要解释关键变量mPluginLoader是怎么来的。publicfinalvoidloadPluginLoader(Stringuuid)throwsRemoteException,FailedException{if(mLogger。isInfoEnabled()){mLogger。info(loadPluginLoadermPluginLoader:mPluginLoader);}if(mPluginLoadernull){PpsStatusppsStatusmPpsController。getPpsStatus();if(!ppsStatus。loaderLoaded){动态加载sampleloaderdebug。apk此插件在插件进程创建了PluginLoaderBinder的实体mPpsController。loadPluginLoader(uuid);}拿到PluginLoaderBinder的引用IBinderiBindermPpsController。getPluginLoader();mPluginLoadernewBinderPluginLoader(iBinder);}} PpsStatus:只是一个状态bean,唯一作用就是保存插件的安装状态 mPpsController:怎么来的上文已经说过,所以他所调用的方法的具体实现,都是插件进程Service里,即PluginProcessService mPpsController。loadPluginLoader方法,即为安装loader插件,具体不再分析,可以自行查看Shadow源码 PluginProcessService的loadPluginLoader方法调用,有个关键点要注意:voidloadPluginLoader(Stringuuid)throwsFailedException{。。。try{。。。pluginLoader类型:PluginLoaderBinderpluginLoader持有DynamicPluginLoader的对象封装了一系列插件运行的方法PluginLoaderImplpluginLoadernewLoaderImplLoader()。load(installedApk,uuid,getApplicationContext());pluginLoader。setUuidManager(mUuidManager);mPluginLoaderpluginL}catch(RuntimeExceptione){。。。}catch(Exceptione){。。。}} 上文中提到,第一次启动插件服务的时候mPluginLoader是null,他的初始化就是在这里,反射创建了一个PluginLoaderBinder对象,也就是mPluginLoader。但是真正干活的是其持有的DynamicPluginLoader对象。具体可以看一下com。tencent。shadow。dynamic。loader。impl。LoaderFactoryImpl类 不要忘了这是跨进程的,所以要这样封装,mPluginLoader也是一个binder对象。 再回到FastPluginManager的loadPlugin方法 中间层插件已处理完,那就到了业务插件,会调用mPluginLoader。getLoadedPlugin(),会返回已安装的插件信息,这个方法的具体实现,从上文分析可知,是在DynamicPluginLoader里。如果要加载的插件没有安装,会调用mPluginLoader。loadPlugin(partKey);安装指定插件。 后续的插件安装逻辑直接看源码吧,相信大家都能看懂,会调到ShadowPluginLoader的loadPlugin方法。 再回到convertActivityIntent方法 如果插件是第一次启动,那么会调用mPluginLoader。callApplicationOnCreate(partKey); mPluginLoader是谁已经说了很多次,不再强调。这个方法会初始化插件的contentprovider以及broadcastreceiver 我们直接看mPluginLoader。convertActivityIntent(pluginIntent),一连串的方法调用连,最终会调用到ComponentManager类的方法:调用前必须先调用isPluginComponent判断Intent确实一个插件内的组件privatefunIntent。toActivityContainerIntent():Intent{valbundleForPluginLoaderBundle()valpluginComponentInfopluginComponentInfoMap〔component〕!!bundleForPluginLoader。putParcelable(CMACTIVITYINFOKEY,pluginComponentInfo)returntoContainerIntent(bundleForPluginLoader)} 其实很好理解,这里就是将插件Activity映射到我们注册在宿主的Activity,同时将映射关系以及一些必要的数据传递。 在demo里最终映射的Activity是com。tencent。shadow。sample。plugin。runtime。PluginDefaultProxyActivity 这是一个真实的Activity,可以正常启动。其主要逻辑都在父类PluginContainerActivity中。 先看PluginContainerActivity的初始化方法:HostActivityDelegatehostActivityDpublicPluginContainerActivity(){HostActivityDDelegateProviderdelegateProviderDelegateProviderHolder。getDelegateProvider();if(delegateProvider!null){delegatedelegateProvider。getHostActivityDelegate(this。getClass());delegate。setDelegator(this);}else{Log。e(TAG,PluginContainerActivity:DelegateProviderHolder没有初始化);}hostActivityD} hostActivityDelegate:看命名就知道,这是宿主Activity的代理类,我猜应该是给插件Activity使用的,你们觉得呢? 我们来看一下hostActivityDelegate到底是什么:overridefungetHostActivityDelegate(aClass:ClassoutHostActivityDelegator):HostActivityDelegate{returnShadowActivityDelegate(this)} 回到PluginContainerActivity,以onCreate方法为例:OverridefinalprotectedvoidonCreate(BundlesavedInstanceState){。。。if(hostActivityDelegate!null){hostActivityDelegate。onCreate(savedInstanceState);}else{。。。}} 这里会调用hostActivityDelegate的onCreate,也就是ShadowActivityDelegate类的onCreate方法:com。tencent。shadow。core。loader。delegates。ShadowActivityDelegateoverridefunonCreate(savedInstanceState:Bundle?){。。。try{valaClassmPluginClassLoader。loadClass(pluginActivityClassName)valpluginActivityPluginActivity::class。java。cast(aClass。newInstance())initPluginActivity(pluginActivity)mPluginActivitypluginActivity。。。pluginActivity。onCreate(pluginSavedInstanceState)mPluginActivityCreatedtrue}catch(e:Exception){throwRuntimeException(e)}}privatefuninitPluginActivity(pluginActivity:PluginActivity){pluginActivity。setHostActivityDelegator(mHostActivityDelegator)pluginActivity。setPluginResources(mPluginResources)pluginActivity。setHostContextAsBase(mHostActivityDelegator。hostActivityasContext)pluginActivity。setPluginClassLoader(mPluginClassLoader)pluginActivity。setPluginComponentLauncher(mComponentManager)pluginActivity。setPluginApplication(mPluginApplication)pluginActivity。setShadowApplication(mPluginApplication)pluginActivity。applicationInfomPluginApplication。applicationInfopluginActivity。setBusinessName(mBusinessName)pluginActivity。setPluginPartKey(mPartKey)pluginActivity。remoteViewCreatorProvidermRemoteViewCreatorProvider} 省略掉一些常规代码 valaClassmPluginClassLoader。loadClass(pluginActivityClassName) pluginActivityClassName:我们要启动的插件Activity的类路径即为SplashActivity 反射实例化保存在mPluginActivity,用于调用插件Activity的生命周期等系统方法 那么插件Activity要调用super方法,比如onCreate的super方法怎么办呢? 在initPluginActivity方法中会将mHostActivityDelegator传递给插件activity使用: pluginActivity。setHostActivityDelegator(mHostActivityDelegator) 本文最开始说过,插件Activity会在编译期修改其继承关系为ShadowActivity,ShadowActivity继承自PluginActivity:publicabstractclassPluginActivityextendsShadowContextimplementsWindow。Callback{HostActivityDelegatormHostActivityDpublicvoidonCreate(BundlesavedInstanceState){mHostActivityDelegator。superOnCreate(savedInstanceState);}} 宿主调用插件onCrate方法,插件会通过mHostActivityDelegator回调到宿主的super,即mHostActivityDelegator。superOnCreate(savedInstanceState);publicvoidsuperOnCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState);} 到这整个流程就跑通了。结尾 以我们多年的插件环境下业务开发经验,插件框架是不可能一步到位实现完美的。因此,我们相信大部分业务在接入时都是需要一定的二次开发工作。Shadow现有的代码满足的是我们自己的业务现在的需求。得益于全动态的设计,插件框架和插件本身都是动态发布的,插件包里既有插件代码也有插件框架代码,所以可以根据新版本插件的需要同时开发插件框架。 例如,ShadowActivity没有实现全所有Activity方法,你写的测试代码可能用到了,就会出现MethodNotFound错误,只需要在ShadowActivity中实现对应方法就可以了。大部分方法的实现都只是需要简单的转调就能工作正常。 如果遇到不会实现的功能,可以提Issue。最好附上测试代码。如有更多问题可以私信!