Shadow 是最近騰訊開源的一款插件化框架。原理是使用宿主代理的方式實現組件的生命週期。
目前的插件化框架,大部分都是使用 hook 系統的方式來作的。使用代理的基本上沒有成體系的框架,只是一些小 demo,Shadow 框架的開源,在系統 api 控制愈來愈嚴格的趨勢下,算是一個新的方向。
Shadow 最大的兩個亮點是:java
下面就具體分析一下框架的實現。 android
├── 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 生命週期等等設計模式
負責啓動插件api
註冊了三個代理 Activity,分別是 PluginDefaultProxyActivity,PluginSingleInstance1ProxyActivity,PluginSingleTask1ProxyActivity。
能夠看到,這三個 Activity 都是繼承自 PluginContainerActivity,只是設置了不一樣的 launchMode,這裏就明顯的看出來,PluginContainerActivity 就是代理 Activity。bash
PluginContainerContentProvider 也是代理 Provider。app
關於插件 Activity 的實現,咱們主要看兩個地方: 0. 替換插件 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
爲什麼插件 Activity 能夠不用繼承 Activity 呢?由於在代理 Activity 的方式中,插件 Activity 是被看成一個普通類來使用的,只要負責執行對應的生命週期便可。
宿主中啓動插件 Activity 原理以下圖:
咱們就從 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 對應圖,咱們能夠簡單的理解爲,調用 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 會在打包過程當中替換其父類爲 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 的實現,咱們直接看 插件中如何啓動的便可。看一下 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 的處理很簡單,直接調用其生命週期方法,不過如此的實現方式,可能會帶來一些時序問題。
廣播的實現也比較常規,在插件中動態註冊和發送廣播,直接調用系統的方法便可,由於廣播不涉及生命週期等複雜的內容。須要處理的就是在 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 而後分發給插件 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,更加穩定。在系統控制愈來愈嚴格的趨勢下,也算是一個比較好的選擇。 原理簡單,其中的設計思想能夠學習~