美文网首页android基础知识插件化&热更新
Android插件化与热修复(二)---Dynamic-load

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

作者: 嘎啦果安卓兽 | 来源:发表于2017-05-21 12:08 被阅读168次

    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框架的所有代码,对于一个插件化框架来说算是很少了

    $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 的流程图

    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的生命周期管理问题。最后附上主要流程图:

    4.png

    相关文章

      网友评论

        本文标题:Android插件化与热修复(二)---Dynamic-load

        本文链接:https://www.haomeiwen.com/subject/zvimxxtx.html