Shadow-插件化框架分析
框架簡單介紹
Shadow 是最近騰訊開源的一款插件化框架。原理是使用宿主代理的方式實現組件的生命周期。
目前的插件化框架,大部分都是使用 hook 系統的方式來做的。使用代理的基本上沒有成體系的框架,只是一些小 demo,Shadow 框架的開源,在系統 api 控制越來越嚴格的趨勢下,算是一個新的方向。
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 的實現,我們主要看兩個地方: 0. 替換插件 Activity 的父類
替換插件 Activity 的父類
Shadow 中有一個比較巧妙的地方,就是插件開發的時候,插件的 Activity 還是正常繼承 Activity,在打包的時候,會通過 Transform 替換其父類為 ShadowActivity。如果對 Transform 不太了解,可以看看之前的 Gradle 學習系列文章。 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 的主入口。
啟動插件的方式是:
可以看到,這里是通過 PluginLoadActivity 來啟動的,傳入了要啟動的插件 Activity:SplashActivity,接著就到 PluginLoadActivity 里看一下具體的啟動。
class PluginLoadActivity extends Activity {public void startPlugin() {PluginHelper.getInstance().singlePool.execute(new Runnable() {@Overridepublic 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() {@Overridepublic void onShowLoadingView(final View view) {// 設置加載的樣式mHandler.post(new Runnable() {@Overridepublic void run() {mViewGroup.addView(view);}});}// ...});}});} }這里可以看到,是通過 HostApplication 獲取到 PluginManager,然后調用其 enter 方法,進入插件。這里先看看返回的 PluginManager 是什么。
class HostApplication extends Application {public void loadPluginManager(File apk) {if (mPluginManager == null) {// 創建 PluginManagermPluginManager = 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) {// 創建 DynamicPluginManagerreturn new DynamicPluginManager(fixedPathPmUpdater);}return null;} }可以看到,HostApplication 里返回的其實是一個 DynamicPluginManager 實例,那么接下來就要看 DynamicPluginManager 的 enter 方法。
class DynamicPluginManager implements PluginManager {@Overridepublic 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) {// ...// 啟動 ActivityonStartActivity(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 viewfinal View view = LayoutInflater.from(mCurrentContext).inflate(R.layout.activity_load_plugin, null);callback.onShowLoadingView(view);}executorService.execute(new Runnable() {@Overridepublic void run() {// ...// 加載插件InstalledPlugin installedPlugin = installPlugin(pluginZipPath, null, true);// 創建插件 IntentIntent pluginIntent = new Intent();pluginIntent.setClassName(context.getPackageName(),className);if (extras != null) {pluginIntent.replaceExtras(extras);}// 啟動插件 ActivitystartPluginActivity(context, installedPlugin, partKey, pluginIntent);// ...}});} }在 SamplePluginManager.enter 中,調用 onStartActivity 啟動插件 Activity,其中開線程去加載插件,然后調用 startPluginActivity。
startPluginActivity 實現在其父類 FastPluginManager 里。
其中的重點是 convertActivityIntent,將插件 intent 轉化成宿主的 intent,然后調用 系統的 context.startActivity 啟動插件。這里的 context 是 PluginLoadActivity.this,從其 enter 方法中一直傳進來的。
下面重點看看 convertActivityIntent 的實現。
到了這里其實有一些復雜了,因為 mPluginLoader 是通過 Binder 去調用相關方法的。由于這里涉及到了 Binder 的使用,需要讀者了解 Binder 相關的知識,代碼比較繁瑣,這里就不具體分析代碼實現了,用一張圖理順一下對應的關系:
通過上面的 Binder 對應圖,我們可以簡單的理解為,調用 mPluginLoader 中的方法,就是調用 DynamicPluginLoader 中的方法,調用 mPpsController 的方法,就是調用 PluginProcessService 中的方法。
所以這里的 mPluginLoader.convertActivityIntent 相當于調用了 DynamicPluginLoader.convertActivityIntent。
調用到了 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]!!.businessNameval partKey = pluginInfoMap[component]!!.partKeyval pluginExtras: Bundle? = extrasreplaceExtras(null as Bundle?)val containerIntent = Intent(this)containerIntent.component = containerComponentbundleForPluginLoader.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 分發生命周期。
上面就是在宿主中啟動插件 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) {// 調用系統 startActivitysuper.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,直接使用intentval 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";// 動態創建 PluginLoaderImplPluginLoaderImpl load(InstalledApk installedApk, String uuid, Context appContext) throws Exception {// 創建插件 ClassLoaderApkClassLoader 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,更加穩定。在系統控制越來越嚴格的趨勢下,也算是一個比較好的選擇。 原理簡單,其中的設計思想可以學習~
作者:ZYLAB
轉載于:https://juejin.cn/post/6844903894271705095
如有侵權,請聯系刪除!
總結
以上是生活随笔為你收集整理的Shadow-插件化框架分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 下wifi 驱动开发(二)—
- 下一篇: Android版哆啦A梦连连看游戏源码完