Android插件化学习

一、什么是Android插件化
  1. android插件化就是不用安装就能被宿主app调动运行。 插件化的目的就是要减小宿主程序apk包的大小同时降低宿主程序的更新频率并做到自由装载模块。
  2. 个人认为就是把好几个不同功能apk免安装的集成在一个apk中使用,apk之间相互解藕,相互独立,大体上可以分为两类:
          一类,插件可以独立运行,不依赖于宿主。
         另一类,插件不可以独立运行依赖宿主的class或者资源文件。
  3. 几个概念
       插件项目:指没有被安装且希望借助已经安装到手机上的项目运行的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

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

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

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

  2. AssetManager资源管理
     

     

    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中的资源了,这样我们就获取到了插件中资源。

    http://blog.csdn.net/luoshengyang/article/details/8738877/
  3. app下载插件存放指定的文件,通过检索文件的方式发现插件,使用classloader根据路径+apk名加载插件。

     

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

(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)使用其他开源框架

  1. 360手机助手DroidPlugin
    • 插件APK完全不需做任何修改,可以独立安装运行、也可以做插件运行。要以插件模式运行某个APK,你无需重新编译、无需知道其源码。
    • 插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件
    • 超强隔离:插件之间、插件与Host之间完全的代码级别的隔离:不能互相调用对方的代码。通讯只能使用Android系统级别的通讯方法。
    • 资源完全隔离:插件之间、与Host之间实现了资源完全隔离,不会出现资源窜用的情况。
    • 实现了进程管理,插件的空进程会被及时回收,占用内存低。
    总结:360是使用instrumentation欺骗(API欺骗)的方式启动插件apk的activity的,提供一种机制,一个线程运行多个Apk(共享进程),插件挂了不会影响到宿主;预先在mainfest中注册所需要的组件,占坑;hook系统API,为了让插件中的方法能够正确的调用,DroidPlugin把所有的,方法都在内部Hook并替换掉了。
    缺陷:  

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

      2.无法在插件中注册一些具有特殊Intent Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。
      3.对Activity的LaunchMode支持不够好,Activity Stack管理存在一定缺陷。Activity的onNewIntent函数可能不会被触发。
      4.缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏(cocos2d、unity3d开发的游戏估计都用不了)无法当作插件运行。
    项目地址:https://github.com/Qihoo360/DroidPlugin
  2. 淘宝Atlas
    • 支持Activity Service  Receiver Provider ,任何部分都可以插件化,代码耦合度低,支持native,支持自定义application。
    • 使用简单,没有任何限制,组件可以相互调用,使用延迟加载,即使整个项目都插件化了,启动可以只加载核心组件,其余的可以用的时候加载。
    • 需要注册插件的组件,不然不能使用。
    • 可以互相调用对方的代码,使用接口,协同处理业务。
    • 插件与插件之间,插件与宿主之间可以互相通信。
    • 可以登录共享,数据共享,服务共享,推送共享。
    • 运行沙箱机制,插件在独立的进程中运行,插件崩溃不会影响宿主。
    总结:淘宝的框架目前来说是最好的插件框架,app可做插件也可当正式版发布,对编程者来不用去维护两套代码,提高了开发效率。开发插件app就像开发普通的app一样,只需要遵循一定的协议,插件之间就可以交互。Atlas是一个很大的框架,适合于业务逻辑比较多工程,模块拆分,并行开发。
    项目代码:https://github.com/bunnyblue/OpenAtlas
  3. Android-Plugin-Framework
    • 宿主不需要注册插件的组件,它使用的是,把插件的mainfest以文件流的形式导入,即可识别插件中所有注册的组件。
    • 通过构造插件apk的Dexclassloader来加载插件apk中的类。DexClassLoader的parent设置为宿主程序的classloader,即可将主程序和插件程序的class贯通。若是独立插件,将parent设置为宿主程序的classloader的parent,可隔离宿主class和插件class。
    • 直接构造插件apk的AssetManager和Resouce对象即可,需要注意的是,通过addAssetsPath方法添加资源的时候,需要同时添加插件程序的资源文件和宿主程序的资源,以及其依赖的资源。这样可以讲Resource合并到一个Context里面去,很多资源问题都迎刃而解。
    • 我们知道,资源id是在编译时生成的,其生成的规则是0xPPTTNNNN,PP段,是用来标记apk的,默认情况下系统资源PP是01,应用程序的PP是07,TT段,是用来标记资源类型的,比如图标、布局等,相同的类型TT值相同,但是同一个TT值,不代表同一种资源,那么我们要解决资源id问题,就可从TT的值开始入手,只要将每次编译时的TT值固定,即可是资源id达到分组的效果,从而避免重复。
    • 插件中Activity、service、receiver不在宿主manifest中注册即拥有完整生命周期的方法。

    总结:该框架方式独特,读取插件apk的mainfest文件进行来获取插件activity注册的组从,从而根据类名用dexclassloader获取类实例,插件与宿主使用一个虚拟机,所以可能造成id冲突,作者固定了TT值,所以达到分组效果,不会产生冲突。
    缺陷:

    1、项目插件开发后,特别需要注意的是宿主程序混淆问题。宿主程序混淆后,可能会导致插件程序运行时出现classnotfound

    2、android gradle插件不支持public.xml中使用padding,在android Studio可能无法编译。可以使用eclipse。

    3、android sdk中的build tools版本较低时也午饭编译public.xml 文件。


    项目代码:https://github.com/limpoxe/Android-Plugin-Framework

五、插件化替代方式

  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插件化优点很多,但是其局限性也很多,使用起来可能会遇到很多问题,有待完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值