插件化开发

 

一、什么是Android插件化

  1. android插件化就是不用安装就能被宿主app调动运行。 插件化的目的就是要减小宿主程序apk包的大小同时降低宿主程序的更新频率并做到自由装载模块。
  2.  

个人认为就是把好几个不同功能apk免安装的集成在一个apk中使用,apk之间相互解藕,相互独立,大体上可以分为两类:

  1.  

      一类,插件可以独立运行,不依赖于宿主。

  1.      另一类,插件不可以独立运行依赖宿主的class或者资源文件。
  2. 几个概念
       插件项目:指没有被安装且希望借助已经安装到手机上的项目运行的apk。
       插件化:可以免安装运行且APP项目jar包冲突已经解决的插件项目称为已经被插件化。
       代理:指插件中的一个委派/代理Activity,通过这个Activity去处理插件中Activity的全部事务,从而表现为就像插件中的Activity在运行一样。

二、实现插件化的好处

  1. APP因为业务的频繁变更而频繁升级客户端,会造成较差的用户体验,插件化可以做到动态升级,不需要更新整个客户端
  2. APP往往需要集成许多的功能,插件化可以使模块解藕
  3. 并行开发,提高开发效率
  4. 插件化突破最大方法数的限制
  5. 插件化做到了按需加载,提高了内存的使用效率
  6. 节省了升级流量

三、实现插件化的难点

  1. 没有被安装的apk是不能运行的
  2. 插件的资源不能被引用
  3. 插件与宿主之间如何通信
  4. 插件Activity如何获得生命周期

四、实现插件化的方式

  ***********相关知识************

  1. android有两个类加载器DexClassLoader和PathClassLoader  

DexClassLoader可以加载任何路径的apk/dex/jar

  1.  

PathClassLoader只能加载/data/app中的apk,也就是已经安装到手机中的apk。这个也是PathClassLoader作为默认的类加载器的原因,因为一般程序都是安装了,在打开,这时候

  1.  

PathClassLoader就去加载指定的apk(解压成dex,然后在优化成odex)就可以了

  1.  

http://blog.csdn.net/jiangwei0910410003/article/details/41384667

 

AssetManager资源管理

  1.  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

protected void loadResources() {  

    try {  

        AssetManager assetManager = AssetManager.class.newInstance();  

        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);  

        addAssetPath.invoke(assetManager, mDexPath);  

        mAssetManager = assetManager;  

    catch (Exception e) {  

        e.printStackTrace();  

    }  

    Resources superRes = super.getResources();  

    mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),  

            superRes.getConfiguration());  

    mTheme = mResources.newTheme();  

    mTheme.setTo(super.getTheme());  

 

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

  1. http://blog.csdn.net/luoshengyang/article/details/8738877/
  2.  

app下载插件存放指定的文件,通过检索文件的方式发现插件,使用classloader根据路径+apk名加载插件。

  1.  

 

  1.  

DexClassLoader loader = new DexClassLoader(libPath, filesDir,filesDir, getClassLoader());

  1.  

(1)使用反射的方式

  1. 用DexClassLoader去加载apk的Activity,加载到的只是一个普通的类,我们需要让他拥有真正Activity的生命周期,这里怎么做呢?我们可以用宿主的Activity类加载器去启动插架Activity,所以需要把DexClassLoader加载器绑定到系统加载Activity的类加载器上就可以了,这样插件的Activity就拥有了完全的生命周期。
  2. PathClassLoader是系统本身默认的类加载器(也就是mClassLoader变量的值,我们如果单独的将DexClassLoader设置为mClassLoader的值的话,就会出错的),所以一定要DexClassLoader的父加载器设置成PathClassLoader,因为类加载器是符合双亲委派机制的。
  3. Activity的类加载器,我们到app的入口ActivityThread.java(其中存放许多app的信息)中通过反射去获得它。
  4. 接下来就可以利用这个DexClassLoader去把Activity和对应的xml文件load进来,这里还需要注意一点,Avtivity需要预留一个setContentView()方法来关联xml文件。
  5. 编译好的插件apk放到宿主apk的cache中。
  • 优点: 不用关心插件中的Activity的生命周期方法,因为他加载进来之后就是一个真正意义上的Activity了
  • 缺点:此方法加载的虽然是真正意义上的Activity,但是插件的Activity需要在宿主的mainfest中注册,如果插件有多个activity需要多次重复4步骤,用起来就不灵活了。这中国方法只适用于activity。

(2)使用代理的方式

  1. 最关键的是宿主Activity为插件的Activity提供的一个空壳Activity,这个Activity主要就是为了模仿插件Activity的生命周期,因为用DexClassLoader获取插件只是一个类,没有生命周期的概念。相当于宿主获取插件Activity的每个方法(onCreate,onStart)的方法内容,好比,宿主的Activity在它的 onCreate 的方法调取插件Activity的onCreate方法,这是代理的主体思想。通过这一个代理Activity就可以调取其他的插件的Activity了。
  2. 插件的Activity内的方法我们该怎么获得呢?这里使用java的反射机制,通过getMethod()方法来得到。即使获得了方法,但是方法里面的内容,没有宿主的引用是没办法执行的,所以我们要把代理的代理Activity对象传到插件所继承的BaseActivity中,那么所有继承插件BaseActivity 的Activity就可以拿到宿主的代理对象了。
  3. 获得DexClassLoader加载器。
  4. 用反射拿到AssetManager对象获取插件的资源包,并做好做适配。
  5. 在代理activity中初始化加载的插件activity,使用反射将代理设置给插件,以及用反射获取插件的所有方法,并与代理一一对应。
  6. 使用其他组件的方法与使用activity的类似。
  • 优点:宿主只声明一个代理activity就加载所有的插架activity,mainfest需要注册一个代理activity,相对较灵活。
  • 缺点:需要手动去管理插件activity的生命周期,当某个activity的方法过多时,需要独自为其设置一个代理activity,使用过程会出现其他问题。

(3)使用其他开源框架

DL 动态加载框架 ( 2014 年底)

https://github.com/singwhatiwanna/dynamic-load-apk

 

DL支持的功能

1、plugin无需安装即可由宿主调起。

 

2、支持用R访问plugin资源

 

3、plugin支持Activity和

FragmentActivity(未来还将支持其他组件)

4、基本无反射调用

 

5、插件安装后仍可独立运行从而便于调试

 

6、支持3种plugin对host的调用模式:

(1)无调用(但仍然可以用反射调用)。
(2)部分调用,host可公开部分接口供plugin调用。 这前两种模式适用于plugin开发者无法获得host代码的情况。
(3)完全调用,plugin可以完全调用host内容。这种模式适用于plugin开发者能获得host代码的情况。

 

7、只需引入DL的一个jar包即可高效开发插

件,DL的工作过程对开发者完全透明

 

8、支持android2.x版本

 

DL框架原理

动态加载主要有两个需要解决的复杂问题:资源的访问和activity生命周期的管理,除此之外,还有很多坑爹的小问题,而DL框架很好地解决了这些问题。需要说明的一点是,我们不可能调起任何一个未安装的apk,这在技术上是很难实现的,我们调起的apk必须受某种规范的约束,只有在这种约束下开发的apk,我们才能将其调起。

 

DroidPlugin ( 2015 年 8 月)

https://github.com/DroidPluginTeam/DroidPlugin

 

DroidPlugin 是 360 手机助手实现的一种插件化框架,它可以直接运行第三方的独立 APK 文件,完全不需要对 APK 进行修改或安装。一种新的插件机制,一种免安装的运行机制,是一个沙箱(但是不完全的沙箱。就是对于使用者来说,并不知道他会把 apk 怎么样), 是模块化的基础。

 

DroidPlugin 插件机制 :它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。

 

定义:

HOST程序:插件的宿主。

插件:免安装运行的APK

 

限制和缺陷:

无法在插件中发送具有自定义资源的Notification,例如:
a.  带自定义RemoteLayout的Notification
b.  图标通过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap)

 

无法在插件中注册一些具有特殊Intent Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。

 

缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏无法当作插件运行。

 

特点:

 

支持Androd 2.3以上系统

 

插件APK完全不需做任何修改,可以独立安装运行、也可以做插件运行。要以插件模式运行某个APK,你无需重新编译、无需知道其源码。

 

插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件

 

插件之间、Host程序与插件之间会互相认为对方已经"安装"在系统上了。

 

API低侵入性:极少的API。HOST程序只是需要一行代码即可集成Droid Plugin

 

超强隔离:插件之间、插件与Host之间完全的代码级别的隔离:不能互相调用对方的代码。通讯只能使用Android系统级别的通讯方法。

 

支持所有系统API

 

 

资源完全隔离:插件之间、与Host之间实现了资源完全隔离,不会出现资源窜用的情况。

 

实现了进程管理,插件的空进程会被及时回收,占用内存低。

 

插件的静态广播会被当作动态处理,如果插件没有运行(即没有插件进程运行),其静态广播也永远不会被触发。

 

Small ( 2015 年底)

https://github.com/wequick/Small


Small 是一种实现轻巧的跨平台插件化框架,基于“轻量、透明、极小化、跨平台”的理念

优点如下:

所有插件支持内置宿主包中。
2.插件的编码和资源文件的使用与普通开发应用没有差别。
3.通过设定 URI ,宿主以及 Native 应用插件,Web 插件,在线网页等能够方便进行通信。
4.支持 Android 、 iOS 、和 Html5 ,三者可以通过同一套 Javascript 接口实现通信。

 

缺点如下:

暂不支持 Service 的动态注册,不过这个可以通过将 Service 预先注册在宿主的 AndroidManifest.xml 文件中进行规避,因为 Service 的更新频率通常非常低。

 

 

 

VirtualAPK (2017年 6 月 )

https://github.com/didi/VirtualAPK

 

VirtualAPK 是滴滴开源的一套插件化框架,支持几乎所有的 Android 特性,四大组件方面。

 

原理:

VirtualAPK 对插件没有额外的约束,原生的 apk 即可作为插件。插件工程编译生成 apk后,即可通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象。如下图所示,通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的 App 一样运行。

 

 

合并宿主和插件的ClassLoader 需要注意的是,插件中的类不可以和宿主重复

 

合并插件和宿主的资源 重设插件资源的 packageId,将插件资源和宿主资源合并

 

去除插件包对宿主的引用 构建时通过 Gradle 插件去除插件对宿主的代码以及资源的引用

 

特性如下:

四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期。

1.Activity:支持显示和隐式调用,支持Activity的theme和LaunchMode,支持透明主题;
2.Service:支持显示和隐式调用,支持Service的start、stop、bind和unbind,并支持跨进程bind插件中的Service;
3.Receiver:支持静态注册和动态注册的Receiver;
4.ContentProvider:支持provider的所有操作,包括CRUD和call方法等,支持跨进程访问插件中的Provider。
5.自定义View:支持自定义 View,支持自定义属性和style,支持动画;
6.PendingIntent:支持PendingIntent以及和其相关的Alarm、Notification和AppWidget;
7.支持插件Application以及插件manifest中的meta-data;
8.支持插件中的so。

 

优秀的兼容性

 

兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证。

 

资源方面适配小米、Vivo、Nubia 等,对未知机型采用自适应适配方案。

 

极少的 Binder Hook,目前仅仅 hook了两个Binder:AMS和IContentProvider,hook 过程做了充分的兼容性适配。

 

插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行。

 

 

 

RePlugin (2017 年 7 月)

https://github.com/Qihoo360/RePlugin


RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。

 

其主要优势有:

  •  

极其灵活:主程序无需升级(无需在Manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件

 

非常稳定:Hook点仅有一处(ClassLoader),无任何Binder Hook!如此可做到其崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的Android ROM

 

特性丰富:支持近乎所有在“单品”开发时的特性。包括静态Receiver、Task-Affinity坑位、自定义Theme、进程坑位、AppCompat、DataBinding等

 

易于集成:无论插件还是主程序,只需“数行”就能完成接入

 

管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等

 

数亿支撑:有360手机卫士庞大的数亿用户做支撑,三年多的残酷验证,确保App用到的方案是最稳定、最适合使用的

 

截止2017年6月底,RePlugin的:

目前360公司几乎所有的亿级用户量的APP,以及多款主流第三方APP,都采用了RePlugin方案。

 

特性

五、插件化替代方式

  1. Android:谷歌提供的一个方法
    首先可以用--multi-dex配置(build.gradle)进行解决,生成的Apk中将包含多个dex文件,比如classes.dex, classes2.dex. 
    该方法只是为了突破最大方法数的限制。
    我司美团app目前使用的是该方式:美团Android DEX自动拆包及动态加载简介
    有兴趣的童鞋可以去看看:http://tech.meituan.com/mt-android-auto-split-dex.html
  2. Android 动态加载 
    动态加载,也有叫法是热加载或 Android 动态部署,指容器(App)在运⾏状态下动态加载某个模块,从而新增功能或改变某⼀部分行为,实际上就是从本地或者后台获得一个class然后用类加载器去加载让它完成一定的功能。
  3. h5技术
    目前使用最多的方式,优点是代码在后台,可以随时部署修改,有bug个也可以fix之后立即发布,不必等发布的时间点,开发成本低,减少apk的体积,几乎插件化的优点它都有。但是这会增大后台的压力,访问高峰期时app端会出现加载不出来的情况,影响用户体验。h5效率低于原生app,但是我觉得随着设备配置的提高以及网速的提高,应用h5前景更广阔。当然了游戏类的app还是原生的好。

 

 

 

 

 

 

 

 

         怎么让一个没有安装的apk运行起来呢?资源又该如何引用?

         android插件化目前没有一个完美的开源的解决方案,开源的框架有很多,但都存在着一些问题,对于小型的插件app应用开发足矣,但是对于大型项目而言不那么适用。

         我们知道, 一个apk中包含三种文件,dex、resouce、mainfest,实现插件化无非就是对apk中这些文件的引用关联,以及使插件的组件用有与正常组件相同生命周期等;实现方式分为两种,一种是,通过反射机制,代理模式为组件构造伪生命周期,或者是注入context对象,实现接口的方式,此种方式必然要用到DexClassloader与AssetManager,一个加载dex,一个加载resouce以此达到调用插件方法的目的,这种方式或多或少对apk有一定的侵入性,插件为配合宿主的调用要特定规则下编写,某些编写的插件还不可以独立运行,此方法简单易用,对于小型插件来说足矣。第二种方式是,hook系统API,为插件新建一个进程运行,或者是将一个进程有效分割使用,通过拦截系统执行代码,替换成自己的代码,以欺骗的系统方式达到插件的调用,此方法启动的apk有有效的进程管理,组件有完整的生命周期。

        插件化可以让一个app的功能模块化,比如说一个app需要一个扫描二维码的功能,只要加载一个二维码插件就可以了,对用户来说可以随性选择需要的插件,动态加载,有效的减少了app的体积,对开发人员来说,不必关注太多关于宿主app细节,只需互相为对方提供一个接口,就可以达到交互的目的。一定程度上做到开发隔离,从而达到并行开发的目的。而且对于部分模块的bug不会影响到整个app,修复bug之后也可以迅速部署,不必更新整个客户端,同时也减少了app运行时占用的内存空间,做到了按需加载。

         从应用层面来讲,目前应用最多的是广告类插件,以插件形式运行的广告不会对宿主app造成太多侵入性,而且一份插件适用于所有app。而对于业务繁多的项目,使用插件化可以有效提高开发效率等诸多优点。从技术层面讲,如果说要保证插件与宿主达到和一个完整的完整app一样的使用效果,这将会考虑到很多问题,目前这方面做的较好的是淘宝的Atlas。

         android插件化优点很多,但是其局限性也很多,使用起来可能会遇到很多问题

 

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