初识插件化开发

最近要加入一个新的项目组,使用的技术都是之前没有接触过的,主要是利用Java的反射,动态加载技术,实现插件化开发。

目前要解决的问题是:实现框架与app之间的通讯。
所以特别的来了解一下插件化开发。

1.插件开发的介绍

1. 定义:

  所谓插件化,就是让我们的应用不必再像原来一样把所有的内容都放在一个apk中,可以把一些功能和逻辑单独抽出来放在插件apk中,然后主apk做到[按需调用],这样的好处是一来可以减少主apk的体积,让应用更轻便,二来可以做到热插拔,更加动态化。

2. 背景
初识插件化开发_第1张图片
image.png
3. 优点
初识插件化开发_第2张图片
image.png

2.插件开发需要解决的问题

1.类的动态加载

类的加载可以使用Java的ClassLoader机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给它们注入活力,也就是所谓的组件生命周期管理;

2. 资源的加载

资源加载方案大家使用的原理都差不多,都是用AssetManager的隐藏方法addAssetPath。

3.插件开发需要掌握的基础知识

  • ClassLoader类加载器

要想实现加载外部dex文件(即插件)来实现热部署,那么必然要把其中的class文件加载到内存中。

其中涉及到两种ClassLoader:DexClassLoader和PathClassLoader。而DexClassLoader可以加载外部的jar,dex等文件,正是我们需要的。

关于ClassLoader详解,见ClassLoader完全解析。

  • Java 反射

因为插件apk与宿主apk不在一个apk内,那么一些类的访问必然要通过反射进行获取。所以了解反射对插件化的学习是必须的。

关于Java反射,见Java反射详解。

  • 插件资源访问

res里的每一个资源都会在R.java里生成一个对应的Integer类型的id,APP启动时会先把R.java注册到当前的上下文环境,我们在代码里以R文件的方式使用资源时正是通过使用这些id访问res资源,然而插件的R.java并没有注册到当前的上下文环境,所以插件的res资源也就无法通过id使用了。

查看源码,通过“addAssetPath”方法重新生成一个新的Resource对象来保存插件中的资源,避免冲突。

关于插件资源访问,见使用插件中的R资源。

  • 代理模式

插件化实现的过程主要靠欺上瞒下,坑蒙拐骗来实现。想想虽然加载进来了Activity等组件,但也仅仅是最为一个对象而存在,并没有在AndroidManifest中注册,没有生命周期的回调,并不能实现我们想要的效果。因此无论是dynamic_load_apk通过代理activity来操控插件activity的方式,还是DroidPlugin通过hook activity启动过程来启动插件activity的方式,都是对代理模式的应用。

关于代理模式,见静态代理与动态代理。

至此,通过ClassLoader加载,然后通过代理模式让Activity等组件具有生命周期实现真正的功能,并且解决了资源访问问题。可能插件化已经可以简单的实现一些初步的功能,然而插件化绝不止于此。更多的内容仍需要进一步探索,不过以上知识是基础中的基础,必备之必备。

比较重要的两个插件开发框架介绍,它们的实现原理不一样
1. Dynamic_Load_apk (任玉刚)

Dynamic-Load-Apk简称DL这个开源框架,他的实现方式是,在宿主中埋一个代理Activity,更改ClassLoader后找到加载插件中的Activity,使用宿主中的Activity作为代理,回调给插件中Activity所以对应的生命周期。

这个思路与AndroidDynamicLoader有点像,都是做一个代理,只不过Dynamic-load-apk加载的插件中的Activity。

项目地址:https://github.com/singwhatiwanna/dynamic-load-apk

Dynamic-load-apk详解

  • Android插件化学习之路(一)之动态加载综述
  • Android插件化学习之路(二)之ClassLoader完全解析
  • Android插件化学习之路(三)之调用外部.dex文件中的代码
  • Android插件化学习之路(四)之使用插件中的R资源
  • Android插件化学习之路(五)之代理Activity
  • Android插件化学习之路(六)之动态创建Activity
  • Android插件化学习之路(七)之DL插件开发该注意的坑
  • Android插件化学习之路(八)之DynamicLoadApk 源码解析(上)
  • Android插件化学习之路(九)之DynamicLoadApk 源码解析(下)
2. DroidPlugin (张勇)

DroidPlugin它的原理是Hook客户端一侧的系统Api。
项目地址:https://github.com/DroidPluginTeam/DroidPlugin

DroidPlugin详解

  • Hook机制之动态代理
  • Hook机制之Binder Hook
  • Hook机制之AMS&PMS
  • Activity生命周期管理
  • 插件加载机制
  • 广播的管理
  • Service的插件化
  • ContentProvider的插件化

4.插件开发的分类

插件式编程,由易到难,分以下个类型来说吧:

  • 简单的单向接口插件构架。
  • 双向式接口插件构架。
  • 界面式软件的插件构架。
  • 进程式插件构架

1.单向接口插件构架

框架不向插件开放任何接口,框架决定了怎么加载插件,怎么调用插件,怎么卸载插件,插件完全是被动的,只能干自己的活,不能反过来要求框架。

也就是说,框架老大,我说怎么干就怎么干。这种一般用于算法式的框架,比如提供加密算法框架,主程序不做具体的加密,由插件来完成加密,每完成一种合适的加密算法,就加入到插件目录,主程序中就可以多出一种加密算法选择,具体选择哪个,由用户来选择。

2. 双向式接口插件构架

单向式插件构架是最简单的一种,插件处于模块化的地位,没有任何的话语权,一般的介绍插件机制的文章,讲的都是这种,比较容易说清楚,但这种情况在真实项目中,一般是很少存在的,充其量是个模块化编程,根本算不上插件式构架。

框架调用插件的功能,天经地义,插件反过来调用框架,也是天经地义的。双方只有交互的情况下,才能更智能,更符合用户习惯,这才能算是一个标准的插件式构架。

交互就是一个互相调用的过程而已,因此,实现过程也不难,主框架也提供几个头文件,定义好一些接口,插件在运行过程中,可以调用主框架的功能,实现一些环境变量的获取与交互等功能。在实际项目中,插件和框架的头文件通常都放在一个目录即可,双方各负责实现自己定义好的头文件。这点在前面的例子中,已经演示过了,这里说说运行过程中的加载过程。

3. 界面式软件的插件构架

这个分类和前面说的构架,是一样的,唯独不一样的地方在于,界面。我们现在的客户端软件,如果不提供一个好看的,易用的界面,基本上很难扩大使用人群,而这点也制约了个人共享软件的发展,现在很难再出现以前那样有影响力的个人版本共享软件了,精力有限。

一个桌面软件框架,肯定有一部分的界面,需要插件来提供,比如提供工具栏,菜单,主客户端区域等等,而主框架也需要开放很多接口,供插件操作各种界面元素。这涉及到了大量的界面交互内容,这么一来,整个框架的复杂度将会提高很多,而框架的接口定义,也将增加很多,导致整个接口阅读和理解的难度增加。

在界面框架这块,事先的预定义很重要,要根据项目的内容进行规划,比如Word这样的,一个或者多个文档,各个插件都可以操作这个文档,还不需要提供客户区窗口。还比如winamp那样的,各个插件互相不干扰,也不使用主界面,都提供自己的界面,还比如QQ界面那样的,多个tab页,每个tab页面算一个插件,点击不同的tab按钮,出来自己的界面。这个界面需求定下来,接口设计也就不一样了。因此,构架需要按照你的界面要求而开发。

4. 进程式界面插件构架

最典型的例子,就是早期的某些浏览器,具备多tab页面功能,但如果某个页面卡死,整个浏览器都将无法使用,导致其他已经打开的网页也无法查看了。这促使后来的浏览器全部采用了多进程方式,也就是说,每个tab页面,实际上都是一个新的进程,某一个进程卡死,将不会对其他进程产生影响。

进程式插件构架实际上只是把多个窗口叠加在一起,看起来像一个程序,底层则是各走各的路,互不干扰。最基本的原理,就是基于WINDOWS的窗口HWND可以互相嵌套的原理。

Windows上面的各个进程,进程空间都是互不干扰,本进程要访问其他进程的东西,并在它的进程中加入自己的代码,都需要非常麻烦或者说高深的技术,但有一样除外,窗口。只需要使用FindWindow,就可以找到其他进程的窗口,窗口时windows的资源,所有的进程公用这些资源,当窗口所有资源耗尽的时候,就再也创建不了新的窗口了。这个限制,我得观察是6万多个,不知道对不对。自己的窗口在创建的时候,需要传入一个父窗口句柄,而这个父窗口句柄,是没有进程限制的。

进程式插件在解决了窗口问题之后,另外面临的问题,就是通讯问题,原来的接口指针,直接调用即可,现在这些指针,全部不能使用了。这里,我们可以采用如下图所示方案。

  • 主框架接口
  • 通讯模块
  • 加封/解封装模块
  • 插件模块接口
  • 加封/解封模块
  • 通讯模块

如果把上面的通讯模块,换成http通讯,把加封/解封换成xml封装方式,是不是和“soap”协议一模一样了?没错,标准的SOAP协议就是这么做的。我们可以不用HTTP,简单的TCP或者管道通讯即可,把接口用xml方式封装,然后传递到对端,即可形成插件调用机制。

作为构架,主要是把接口开放给插件,但要屏蔽掉进程通讯相关的细节,因此把以上的通讯机制,封装成一个lib或者dll,在插件中,调用这些函数的时候,通过通讯,会调用到主框架程序中。只要这个封装库做的够好,插件几乎完全不知道自己是进程式插件,还是单进程式插件。

你可能感兴趣的:(初识插件化开发)