我们内部使用的Ui效果展示应用,为了在使用上能够更加便捷快速的集成一个个单独的Ui效果包,我们采用了动态化实现的思路,将Ui效果包放在asserts目录下,我们的主框架动态扫描并加载了整个apk来运行,理论上整个过程不需要写一行代码,整个框架的实现是基于一个名叫android-pluginmgr的开源项目,这里就对实现原理做一个简要的分析。
Ps:这套动态化的实现方案有一个很大的优点就是不仅限于代码的动态,资源也可以动态化的加载使用。目前这套方案还有些问题待于解决,但如果可以被攻克并驾驭,那么就为许多产品的功能实现方式打开了想象的空间。
项目地址:https://github.com/houkx/android-pluginmgr
该框架的核心是探索解决了几个大问题:
为什么会有这个问题,其实很好理解,apk被宿主程序动态调起以后,apk中的activity其实就是一个普通的对象,不具有activity的性质,因为系统启动activity是要做很多初始化工作的,同时也要在系统中有注册,而我们在应用层通过反射去启动activity是很难完成系统所做的各项工作的,所以activity的大部分特性都无法使用,包括activity的生命周期管理,这就需要我们自己去管理。谈到activity生命周期,其实就是那几个常见的方法:onCreate、onStart、onResume、onPause等,由于动态load进来的apk中的activity不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),所以这几个生命周期的方法系统就不会去自动调用了。
针对此类问题,大部分的解决方案都是依赖一个已声明的Activity来管理,这个已声明的Activity就是所谓的代理Activity,我们要在代理activity中去反射动态apk中activity的所有生命周期的方法,然后将activity的生命周期和代理activity的生命周期进行同步。动态Apk中的activity要依附于代理Activity,与代理Activity捆绑在一起,这叫正向依赖,采用正向依赖的框架有DL等。
接下来我们来具体看看pluginmgr是怎么做的,pluginmgr采用了一种依赖倒转的方式来做,不让插件依赖框架API,而是反过来,自动生成一个Activity类依赖(继承)插件中的Activity,这个自动生成的类就叫PluginActivity,并且我们要提前将该Activity声明在框架的清单文件中,如下:
本质上是偷梁换柱了,如果你想启动插件里的Activity,如com.test.MyPlugActivity, 我就把启动目标修改为androidx.pluginmgr.PluginActivity类,然后从com.test.MyPlugActivity.dex文件中找到 public class androidx.pluginmgr.PluginActivity extends com.test.MyPlugActivity{....},最后实际启动的是你注册文件中的androidx.pluginmgr.PluginActivity。
以下是示意图:
为了实现以上的方案,涉及到了替换Application类的ClassLoader,动态生成可被虚拟机执行的Dex代码两个比较重要的步骤。
相关的代码片段:
PluginManager中
Init() 通过反射替换了App默认的mClassLoader
startMainActivity() 返回代理Activity
FrameworkClassLoader 中进行判断区分
ActivityOverider 中利用了DexMaker 动态生成PluginActivity的代码
ActivityClassGenerator中的具体实现:
里面具体是使用DexMaker特定的语法动态对Activity的运行Dex代码进行生成。
生成的代码样例如下:(ActivityOverider 类负责与自动生成的Activity类交互)
package androidx.pluginmgr;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import androidplugdemo.SthActivity;
public final class PluginActivity extends SthActivity {
private static final String _pluginId = "activityTest_v1";
private AssetManager mAssertManager;
private Resources mResources;
public boolean bindService(Intent paramIntent, ServiceConnection paramServiceConnection, int paramInt) {
return ActivityOverider.overrideBindService(this, _pluginId, paramIntent, paramServiceConnection, paramInt);
}
public AssetManager getAssets() {
AssetManager localAssetManager = this.mAssertManager;
if (localAssetManager == null) {
localAssetManager = super.getAssets();
}
return localAssetManager;
}
public Resources getResources() {
Resources localResources = this.mResources;
if (localResources == null) {
localResources = super.getResources();
}
return localResources;
}
public void onBackPressed() {
if (ActivityOverider.overrideOnbackPressed(this, _pluginId)) {
super.onBackPressed();
}
}
protected void onCreate(Bundle paramBundle) {
String str = _pluginId;
AssetManager localAssetManager = ActivityOverider.getAssetManager(str, this);
this.mAssertManager = localAssetManager;
Resources localResources = super.getResources();
this.mResources = new Resources(localAssetManager, localResources.getDisplayMetrics(), localResources.getConfiguration());
ActivityOverider.callback_onCreate(str, this);
super.onCreate(paramBundle);
}
protected void onDestroy()
{
String str = _pluginId;
super.onDestroy();
ActivityOverider.callback_onDestroy(str, this);
}
protected void onPause()
{
String str = _pluginId;
super.onPause();
ActivityOverider.callback_onPause(str, this);
}
protected void onRestart()
{
String str = _pluginId;
super.onRestart();
ActivityOverider.callback_onRestart(str, this);
}
protected void onResume()
{
String str = _pluginId;
super.onResume();
ActivityOverider.callback_onResume(str, this);
}
protected void onStart()
{
String str = _pluginId;
super.onStart();
ActivityOverider.callback_onStart(str, this);
}
protected void onStop()
{
String str = _pluginId;
super.onStop();
ActivityOverider.callback_onStop(str, this);
}
public void startActivityForResult(Intent paramIntent, int paramInt, Bundle paramBundle)
{
super.startActivityForResult(ActivityOverider.overrideStartActivityForResult(this, _pluginId, paramIntent, paramInt, paramBundle), paramInt, paramBundle);
}
public ComponentName startService(Intent paramIntent)
{
return ActivityOverider.overrideStartService(this, _pluginId, paramIntent);
}
public boolean stopService(Intent paramIntent)
{
return ActivityOverider.overrideStopService(this, _pluginId, paramIntent);
}
public void unbindService(ServiceConnection paramServiceConnection)
{
ActivityOverider.overrideUnbindService(this, _pluginId, paramServiceConnection);
}
}
同时,上面的问题也有采用Fragment来解决的,Fragment从3.0引入,通过support-v4包,可以兼容3.0以下的android版本。Fragment既有类似于Activity的生命周期,又有类似于View的界面,将Fragment加入到Activity中,activity会自动管理Fragment的生命周期, 这种方式就要尽量少的在动态apk包中使用Activity了,要使用大量的Fragment进行代替,动态apk中的activity是通过宿主程序中的代理activity启动的,将Fragment加入到代理activity内部,其生命周期将完全由代理activity来管理,采用这种方法,就要求apk尽量采用Fragment来实现,当然这也是一种思路。
解决的方法是通过反射,通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中,由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。
PluginManager中的buildPlugInfo()中的相关片段:
动态加载的Apk也需要一个Applicaton对象,在正常情况下,每个程序在第一次启动时,系统都会去创建默认为包名的Application对象,而在动态Load的环境下,系统是创建不了的,需要我们人为的模拟创建,并替换需要的相关属性。关于相关的上下文属性,在PluginMgr项目中,有PluginContextWrapper与PluginActivityWrapper(代码中没有被调用),分别是对ContextWrapper的复写实现,在PluginManager的buildPlugInfo函数里,是对动态Load的Apk的相关信息的创建,在该函数中通过对动态Apk的主配置文件的解析,抽取其中的相关信息,构建出了PuginInfo对象,该对象包含了需动态Apk的所需相关信息,信息获取完成后接下来在initPluginApplication中完成对动态Apk的Application的创建与相关的属性的模拟与绑定。
相关代码片段:
buildPlugInfo()中:
解析Manifest:
创建PluginApplication:
initPluginApplication()中:
load Application类,并执行OnCreate
setApplicationBase() 中:
通过反射将PluginContextWrapper与手动创建初始化的Application绑定,完成了相关属性的模拟