Android插件化与热修复(二)---Dynamic-load-apk

dynamic-load-apk 核心原理分析(代理模式)

dynamic-load-apk简介

项目地址:https://github.com/singwhatiwanna/dynamic-load-apk
dynamic-load-apk是2014年底,任玉刚发布的一个Android插件化项目,这跟后续出现的很多插件化项目都不太一样。它没有Hook太多的系统底层方法,而是在应用层上,通过代理的方式实现的一种插件化框架。主要特点:

  • plugin支持Activity、Service以及动态的BroadcastReceiver。
  • 基本无反射调用
  • 插件安装后仍可独立运行从而便于调试
  • 支持plugin对host的调用
  • 插件需要引入DL的一个jar包,遵循DL接口规范

为什么是核心原理分析不是源码分析?

dynamic-load-apk插件在项目实际应用中会有很多问题,并且支持组件少、对插件侵入严重,插件开发成本高等问题,所以在2015年该项目就已经停止更新。但作为国内第一个可参考的较完整的android插件化项目,他的核心原理我们还是要了解,有利于我们更全面的了解插件化方案。

总体设计

代码结构

一下为dynamic-load-apk框架的所有代码,对于一个插件化框架来说算是很少了

《Android插件化与热修复(二)---Dynamic-load-apk》 $RUG5PRJ.png

dynamic-load-apk主要分为四大模块:

  • DLPluginManager插件管理模块,负责插件的加载、管理以及启动插件组件。
  • Proxy代理组件模块,目前包括 DLProxyActivity(代理 Activity)、DLProxyFragmentActivity(代理 FragmentActivity)、DLProxyService(代理 Service)。
  • Proxy Impl代理组件公用逻辑模块,负责构建、加载插件组件的管理器。这些 Proxy Impl 通过反射得到插件组件,然后将插件与 Proxy 组件建立关联,最后调用插件组件的 onCreate 函数进行启动。
  • Base Plugin插件组件的基类模块,目前包括 DLBasePluginActivity(插件 Activity 的基类)、DLBasePluginFragmentActivity(插件 FragmentActivity 的基类)、DLBasePluginService(插件 Service 的基类)。

调用插件 Activity 的流程图

《Android插件化与热修复(二)---Dynamic-load-apk》 2_看图王.png

其他组件调用流程类似:
(1) 首先通过 DLPluginManager 的 loadApk 函数加载插件,这步每个插件只需调用一次。
(2) 通过 DLPluginManager 的 startPluginActivity 函数启动代理 Activity.。
(3) 代理 Activity 启动过程中构建、启动插件 Activity。

loadApk 加载插件

加载插件源码:

public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib) {
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath,
            PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
    if (packageInfo == null) {
        return null;
    }
    // 得到插件信息封装类
    DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
    // 如果有 .so 文件,则复制到 mNativeLibDir 目录
    if (hasSoLib) {
        copySoLib(dexPath);
    }

    return pluginPackage;
}

loadApk方法 主要做了两件事:

  • 在 preparePluginEnv方法中把插件 packageInfo 封装成 pluginPackage ;
  • eventInheritance 默认为false,支持事件继承:直接发送eventClass 事件。
  • 复制 .so 文件到 mNativeLibDir 目录,主要流程就是在 SoLibManager 中利用 I/O 流复制文件。

进一步到preparePluginEnv(PackageInfo packageInfo, String dexPath) 方法:

private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath) {
    // 先查看缓存中有没有该 pluginPackage
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
    if (pluginPackage != null) {
        return pluginPackage;
    }
    // 创建 加载插件的ClassLoader
    DexClassLoader dexClassLoader = createDexClassLoader(dexPath); 
    AssetManager assetManager = createAssetManager(dexPath);
    // 得到插件 res 资源
    Resources resources = createResources(assetManager);
    // create pluginPackage
    pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
    mPackagesHolder.put(packageInfo.packageName, pluginPackage);
    return pluginPackage;
}

preparePluginEnv方法主要做的事情:

  • 创建了插件的 ClassLoader ,用于之后加载插件类。
  • 创建插件的 resources 资源。插件的 res 资源访问主要通过 AssetManager 的 addAssetPath 方法来获取。需要注意的是,addAssetPath 方法是 @hide 的,需要反射来执行。
  • 最后封装成一个 pluginPackage 对象返回。

startPluginActivityForResult启动插件

startPluginActivityForResult源码

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
    // 判断是否宿主内部调用
    if (mFrom == DLConstants.FROM_INTERNAL) {
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        performStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.START_RESULT_SUCCESS;
    }

    String packageName = dlIntent.getPluginPackage();
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    // 得到插件信息
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    // 得到插件 Activity 的全类名
    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    // 得到对应的 class
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }

    // 根据插件 class 继承的是哪个基类,分别得到对应的代理类
    // 若继承的是 DLBasePluginActivity ,得到的就是 DLProxyActivity 代理类
    // 若继承的是 DLBasePluginFragmentActivity ,得到的就是 DLProxyFragmentActivity 代理类
    Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
    if (activityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }

    // 把插件信息传入 Intent 中
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    // 这里启动的是上面得到的代理类 Activity
    dlIntent.setClass(mContext, activityClass);
    // 启动 Activity
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}

startPluginActivityForResult方法里主要是获得插件主页 Activity 的clazz , 根据插件 class 继承的是哪个基类,分别得到对应的代理类
,并通过intent 启动的是代理的 Activity ,并不是我们插件的 Activity 。

DLProxyActivity绑定插件Acitivity并启动

DLProxyActivity 源码

public class DLProxyActivity extends Activity implements DLAttachable {

    protected DLPlugin mRemoteActivity;
    private DLProxyImpl impl = new DLProxyImpl(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        impl.onCreate(getIntent());
    }

    ...

}

在ProxyActivity 的onCreate(Bundle savedInstanceState)方法 中调用了 impl.onCreate(getIntent()) , impl.onCreate(getIntent()) 的方法里

public void onCreate(Intent intent) {

    // set the extra's class loader
    intent.setExtrasClassLoader(DLConfigs.sPluginClassloader);
    // 得到传过来的插件 Activity 包名和全类名
    mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
    mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
    Log.d(TAG, "mClass=" + mClass + " mPackageName=" + mPackageName);
    // 得到插件相关的信息
    mPluginManager = DLPluginManager.getInstance(mProxyActivity);
    mPluginPackage = mPluginManager.getPackage(mPackageName);
    mAssetManager = mPluginPackage.assetManager;
    mResources = mPluginPackage.resources;
    // 得到要启动插件的 activityInfo,设置插件 Activity 的主题
    initializeActivityInfo();
    // 把 DLProxyActivity 的主题设置为插件 Activity 的主题
    handleActivityInfo();
    launchTargetActivity();
}

在 onCreate(Intent intent) 中得到了之前插件 Activity 相关的信息。然后把 DLProxyActivity 的主题设置为 PluginActivity 的主题。最后调用了 launchTargetActivity() ,把 PluginActivity 和 ProxyActivity 绑定在一起。
继续看捆绑方法launchTargetActivity() :

TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
protected void launchTargetActivity() {
    try {
        // 通过反射创建插件 Activity 的对象      
         Class<?> localClass = getClassLoader().loadClass(mClass);
        Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
        Object instance = localConstructor.newInstance(new Object[] {});
        mPluginActivity = (DLPlugin) instance;
         // 手动调用插件的 attach 方法,将ProxyActivity和PluginActivity绑定在一起
        ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
        mPluginActivity.attach(mProxyActivity, mPluginPackage);

        Bundle bundle = new Bundle();
        bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
        // 手动调用插件的 onCreate 方法
        mPluginActivity.onCreate(bundle);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

launchTargetActivity() 在方法中使用反射创建了插件 PluginActivity的对象,又因为插件 Activity 必须继承指定的基类DLBasePluginActivity,这些基类是实现了 DLPlugin 接口的。所以插件 Activity 可以强转为 DLPlugin 。DLPlugin 接口定义了一系列的 Activity 生命周期方法,之后手动回调了 attach 和 onCreate 方法将ProxyActivity和PluginActivity绑定在一起。代理 ProxyActivity 回调mRemoteActivity生命周期方法时,都调用了 DLPlugin 接口一致的生命周期方法,这样就实现了插件 PluginActivity具备了完整的生命周期。

至此,dynamic-load-apk的插件化实现的主要流程介绍完了,主要解决了资源加载问题和代理activity的生命周期管理问题。最后附上主要流程图:

《Android插件化与热修复(二)---Dynamic-load-apk》 4.png

    原文作者:野生的安卓兽
    原文地址: https://www.jianshu.com/p/6a6ed243ed81
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞