动态加载框架分析之:PluginMgr

我们内部使用的Ui效果展示应用,为了在使用上能够更加便捷快速的集成一个个单独的Ui效果包,我们采用了动态化实现的思路,将Ui效果包放在asserts目录下,我们的主框架动态扫描并加载了整个apk来运行,理论上整个过程不需要写一行代码,整个框架的实现是基于一个名叫android-pluginmgr的开源项目,这里就对实现原理做一个简要的分析。


Ps:这套动态化的实现方案有一个很大的优点就是不仅限于代码的动态,资源也可以动态化的加载使用。目前这套方案还有些问题待于解决,但如果可以被攻克并驾驭,那么就为许多产品的功能实现方式打开了想象的空间。

 

项目地址:https://github.com/houkx/android-pluginmgr

 

该框架的核心是探索解决了几个大问题:

1,动态加载的Activity的生命周期的处理:

       为什么会有这个问题,其实很好理解,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 name="androidx.pluginmgr.PluginActivity" />

本质上是偷梁换柱了,如果你想启动插件里的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来实现,当然这也是一种思路。

2,动态加载的apk包如何使用包中的资源:

        解决的方法是通过反射,通过调用AssetManager中的addAssetPath方法,我们可以将一个apk中的资源加载到Resources中,由于addAssetPath是隐藏api我们无法直接调用,所以只能通过反射,下面是它的声明,通过注释我们可以看出,传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,所以直接将apk的路径传给它,资源就加载到AssetManager中了,然后再通过AssetManager来创建一个新的Resources对象,这个对象就是我们可以使用的apk中的资源了,这样我们的问题就解决了。

 

PluginManager中的buildPlugInfo()中的相关片段:

3,动态加载的apk的Application的创建以及相关属性的模拟:

动态加载的Apk也需要一个Applicaton对象,在正常情况下,每个程序在第一次启动时,系统都会去创建默认为包名的Application对象,而在动态Load的环境下,系统是创建不了的,需要我们人为的模拟创建,并替换需要的相关属性。关于相关的上下文属性,在PluginMgr项目中,有PluginContextWrapper与PluginActivityWrapper(代码中没有被调用),分别是对ContextWrapper的复写实现,在PluginManagerbuildPlugInfo函数里,是对动态Load的Apk的相关信息的创建,在该函数中通过对动态Apk的主配置文件的解析,抽取其中的相关信息,构建出了PuginInfo对象,该对象包含了需动态Apk的所需相关信息,信息获取完成后接下来在initPluginApplication中完成对动态Apk的Application的创建与相关的属性的模拟与绑定。

相关代码片段:

buildPlugInfo()中:

解析Manifest:

创建PluginApplication:

initPluginApplication()中:

load Application类,并执行OnCreate

setApplicationBase() 中:

通过反射将PluginContextWrapper与手动创建初始化的Application绑定,完成了相关属性的模拟

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值