Shadow 插件化框架分析

框架簡單介紹

Shadow 是最近騰訊開源的一款插件化框架。原理是使用宿主代理的方式實現組件的生命週期。
目前的插件化框架,大部分都是使用 hook 系統的方式來作的。使用代理的基本上沒有成體系的框架,只是一些小 demo,Shadow 框架的開源,在系統 api 控制愈來愈嚴格的趨勢下,算是一個新的方向。
Shadow 最大的兩個亮點是:java

  1. 零反射
  2. 框架自身動態化

下面就具體分析一下框架的實現。 android

summary

框架結構分析

框架結構圖圖

framework

項目目錄結構

├── 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

負責啓動插件api

註冊代理 Activity

註冊了三個代理 Activity,分別是 PluginDefaultProxyActivity,PluginSingleInstance1ProxyActivity,PluginSingleTask1ProxyActivity。
能夠看到,這三個 Activity 都是繼承自 PluginContainerActivity,只是設置了不一樣的 launchMode,這裏就明顯的看出來,PluginContainerActivity 就是代理 Activity。bash

註冊代理 Provider

PluginContainerContentProvider 也是代理 Provider。app

Activity 實現

關於插件 Activity 的實現,咱們主要看兩個地方: 0. 替換插件 Activity 的父類框架

  1. 宿主中如何啓動插件 Activity
  2. 插件中如何啓動插件 Activity

替換插件 Activity 的父類

Shadow 中有一個比較巧妙的地方,就是插件開發的時候,插件的 Activity 仍是正常繼承 Activity,在打包的時候,會經過 Transform 替換其父類爲 ShadowActivity。若是對 Transform 不太瞭解,能夠看看以前的 Gradle 學習系列文章。 projects/sdk/core/transform 和 projects/sdk/core/transform-kit 兩個項目就是 Transform,入口是 ShadowTransform。這裏對 Transform 作了一些封裝,提供了友好的開發方式,這裏就很少作分析了,咱們主要看下 TransformManager。maven

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 爲例。ide

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 的繼承關係。
post

shadowActivity

爲什麼插件 Activity 能夠不用繼承 Activity 呢?由於在代理 Activity 的方式中,插件 Activity 是被看成一個普通類來使用的,只要負責執行對應的生命週期便可。

宿主中如何啓動插件 Activity

宿主中啓動插件 Activity 原理以下圖:

startActivity

咱們就從 sample 裏的 MainActivity 開始看起。 sample-host/src/main/java/com/tencent/shadow/sample/host/MainActivity 是 demo 的主入口。
啓動插件的方式是:

startPluginButton.setOnClickListener(new View.OnClickListener() {
    @Override
    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() {
            @Override
            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() {
                            @Override
                            public void onShowLoadingView(final View view) {
                                // 設置加載的樣式
                                mHandler.post(new Runnable() {
                                    @Override
                                    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 {
    @Override
    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() {
            @Override
            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

經過上面的 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 原理以下圖:

startActivity2

咱們上面說到過,插件 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 框架還有一個特色,就是 框架自己也實現了動態化,這裏的實現主要是三步:

  1. 抽象接口類
  2. 在插件中實現工廠類
  3. 經過工廠類動態建立接口的實現

咱們以 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 加載。

總結

summary2

其實整個框架看下來,沒有什麼黑科技,就是代理 Activity 的原理加上設計模式的運用。其實目前幾大插件化框架,基本上都是 hook 系統爲主,像使用代理 Activity 原理的,Shadow 應該算是第一個各方面實現都比較完整的框架,帶來的好處就是不用去調用系統限制的 api,更加穩定。在系統控制愈來愈嚴格的趨勢下,也算是一個比較好的選擇。 原理簡單,其中的設計思想能夠學習~

關於我

about
相關文章
相關標籤/搜索