浅谈 Android 开发中模块化、组件化、插件化、热修复的简单理解

目录

前言 

一、模块化

二、组件化

1、概念

2、使用

3、版本管理

4、模块间跳转

5、资源命名问题

三、插件化

四、热修复

1、概述

2、流派

3、原理

五、总结


前言 

谈到热修复相信大家应该比较熟悉,因为它是目前比较重要的技术,平常面试中也是被问的比较多。插件化和热修复同出一门,俩者都属于动态更新,而模块化和组件化是基础。相信看完本篇的内容,对于这些模糊的概念应该会有一个比较清晰的了解。

 

一、模块化

1、概念

模块(Module),Android Studio提出的概念,根据不同关注点将原项目中共享的部分或业务抽取出来形成独立module,这就类似我们最集成的第三方库的SDK。

2、思想

实际开发中,我们通常会抽取第三方库、整个项目的初始化的代码、自定义的Utils工具类、自定义View 、图片、xml这些(value目录下的各种xml文件)等到一个共有的Common模块中,其他模块在配置Gradle依赖后,就能够调用这些API。

特别注意的是style.xml文件,对于全局共用的style,我们应该把它也放在common模块中。例如我们的项目theme主题,本来是放在main组件的style里面,我们可以把它移到common中,这样其他组件调试时,作为一个单独的项目,也能和主项目有一样的主题。

 总之,你认为需要共享的资源,都应该放在common组件中。

3、使用

每一个Module都可以在自身的 build.gradle 中进行设置两种格式:application和library。 

apply plugin: 'com.android.application'
//或
apply plugin: 'com.android.library'

引用时,就像添加依赖GitHub库一样。

二、组件化

1、概念

组件化是基于模块化的可以在打包时是设置为library,开始调试运行是设置成application。目的是解耦与加快开发。组件化适用于多人合作开发的场景,隔离不需要关注的模块,大家各自分工、各守其职。简而言之,就是把一个项目分开成多个项目

(1)好处

  • 业务模块分开,解耦的同时也降低了项目的复杂度。
  • 开发调试时不需要对整个项目进行编译。
  • 多人合作时可以只关注自己的业务模块,把某一业务当成单一项目来开发。
  • 可以灵活的对业务模块进行组装和拆分。

(2)规则

  • 只有上层的组件才能依赖下层组件,不能反向依赖,否则可能会出现循环依赖的情况;
  • 同一层之间的组件不能相互依赖,这也是为了组件之间的彻底解耦;

2、使用

1、在整个项目 gradle.properties 文件中,添加代码

#是否处于debug状态
isDebug = flase

2、在其他Module的 build.gradle 文件中,添加代码

if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

3、在宿主Module的 build.gradle 文件中,添加代码

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    //...
    if(!isDebug.toBoolean()){//不是debug,就添加依赖其他模块
        compile project(':home')
        compile project(':personal')
        compile project(':video')
    }
    if(isDebug.toBoolean()){
        compile project(':common')
    }
}

3、版本管理

每个Module的build.gradle文件中很多地方需要些写版本号,例如 targetSdkVersion、appcompat-v7、第三方库等。修改时都要同时修改多份build.gradle文件。如果把版本号可以统一管理起来,就会省时省力,又避免不同的组件使用的版本不一样,导致合并在一起时引起冲突。

整个项目根目录下的 build.gradle 文件中,添加代码

ext {
    compileSdkVersion = 25
    buildToolsVersion = "25.0.2"
    minSdkVersion = 14
    targetSdkVersion = 25
    versionCode = 1
    versionName = "1.0"
}

每个Mudule的 build.Gradle 文件中,改写代码

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
    }
//...
}

4、模块间跳转

我们知道,通常在Gradle中依赖的库是可以直接引用的,即通过startActivity跳转。根据组件化的规则,宿主可以依赖下层组件,而组件之间不可以依赖。因此,当常规业务模块之间遇到业务需求,进行互相跳转时该怎么处理?

这里简单介绍两种方式,即路由和反射。路由的方式以用阿里的ARouter/美团的WMRouter,但是我觉得人少、项目小的公司必要用到这么强大的工具,直接反射就好。

放在common组件中的EventUtile工具类

public class EventUtil{
    /**
     * 页面跳转
     * className  全路径类名
     */
    public static void open(Context context,String className){
        try {
            Class clazz = Class.forName(className);
            Intent intent = new Intent(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,无法跳转");
        }
    }
    /**
     * 页面跳转,可以传参,参数放在intent中,所以需要传入一个intent
     */
    public static void open(Context context,String className,Intentintent){
        try {
            Class clazz = Class.forName(className);
           intent.setClass(context,clazz);
           context.startActivity(intent);
        } catch (ClassNotFoundException e) {
            Log.e("zhuang","未集成,无法跳转");
        }
    }
}

5、资源命名问题

首先,多组件集成时,特别容易出现资源命名重复的问题。可以让各个组件中使用统一前缀,比如home组件中的资源,以home_开通、video组件中以video_开头。当然,如果是嫌麻烦,我们可以在build.gradle文件中,加入如下代码:

resourcePrefix"home_"

但是这个功能其实很弱。比较xml文件报错,依然可以运行,图片文件不已home_为前缀,也不会报错。

三、插件化

也是属于模块化的一种体现。将完整的项目按业务划分不同的插件,分治法,越小的模块越容易维护。单位是apk,一个完整的项目。插件化比热修复简单,插件化只是增加新的功能或资源文件。灵活性在于加载apk,按需下载,动态更新。

实现原理

  1. 通过dexclassloader加载。
  2. 代理模式添加生命周期。
  3. hook思想跳过清单验证。

Android 使用Java的反射机制总结

Android 动态代理与Hook机制详解

总结

  • 宿主和插件分开编译
  • 动态更新插件
  • 按需下载插件
  • 缓解65535方法数限制

四、热修复

1、概述

热修复与插件化都利用classloader实现加载新功能。热修复比插件化复杂,插件化只是增加新的功能或资源文件,所以不涉及抢先加载旧类的使命。热修复为了修复bug,要将新的同名类替旧的同名bug类,要抢在加载bug类之前加载新的类。

2、流派

   热修复作为当下热门的技术,在业界内比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补丁和微信的Tinker,以及大众点评nuwa和美团Robust。阿里百川推出的HotFix热修复服务就基于AndFix技术,定位于线上紧急BUG的即时修复。虽然Tinker支持修复的功能强大兼容性很好,但是不能即时生效、集成负责、补丁包大。

3、原理

(1)native修复方案

AndFix 

提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。实现过程三步骤:

  • setup():对于Dalvik的即时编译机制(JIT),在运行时装载libdvm.so动态链接库,从而获取native层内部函数:dvmThreadSelf( ):查询当前的线程;dvmDecodeIndirectRef( ):根据当前线程获得ClassObject对象。
  • setFieldFlag():把 private、protected的方法和字段都改为public,这样才可被动态库看见并识别,因为动态库会忽略非public属性的字段和方法。
  • replaceMethod():该步骤是方法替换的核心。拿到新旧方法的指针,将指针指向新的替换方法来实现方法替换。

(2)Dex 分包方案

概述

DEX分包是为了解决65536方法限制,系统在应用打包APK阶段,会将有调用关系的类打包在同一个Dex文件中,并且同一个dex中的类会被打上`CLASS_ISPREVERIFIED`的标志。因为加载后的类不能卸载,必须通过重启后虚拟机进行加载才能实现修复,所以此方案不支持即时生效。

 QQ空间超级补丁

是把BUG方法修复以后放到一个patch.dex,拿到当前应用BaseDexClassloader后,通过反射获取到DexPathList属性对象pathList、再反射调用pathList的dexElements方法把patch.dex转化为Element[],两个Element[]进行合并,最后把patch.dex插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法,就可以达到修复目的。

问题 

而然,问题就是两个有调用关系的类不再同一个Dex文件中,那么就会抛“unexpected DEX problem”异常报错。解决办法,就是单独放一个AnitLazyLoad类在另外DEX中,在每一个类的构造方法中引用其他DEX中的唯一AnitLazyLoad类,避免类被打上CLASS_ISPREVERIFIED标志。

不足 

此方案通过增加dex来修复,但是修复的类到了一定数量,就需要花不少的时间加载。对手淘这种航母级应用来说,启动耗时增加2s以上是不能够接受的事。在ART模式下,如果类修改了结构,就会出现内存错乱的问题。为了解决这个问题,就必须把所有相关的调用类、父类子类等等全部加载到patch.dex中,导致补丁包异常的大,进一步增加应用启动加载的时候,耗时更加严重。

微信Tinker

微信Tinker采用的是DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,但不将patch.dex增加到elements数组中。差量的方式拿到patch.dex,开启新进程的服务TinkerPatchService,将patch.dex与应用中的classes.dex合并,得到一个新的fix_classess.dex。通过反射操作得到PathClassLoader的DexPatchList,再反射调用patchlist的makeDexElements()方法,把fix_classess.dex直接替换到Element[]数组中去,达到修复的目的。从而提高了兼容性和稳定性。

(3)Instand Run 方案

Instant Run,是android studio2.0新增的一个运行机制,用来减少对当前应用的构建和部署的时间。

构建项目的流程:

构建修改的部分 → 部署修改的dex或资源 → 热部署,温部署,冷部署。

热拔插:方法实现的修改,或者变量值修改,不需要重启应用,不需要重建当前activity。

温拔插:代码修改涉及到了资源文件,activity需要被重启。

冷拔插:修改了继承规则、修改了方法签名,app需要被重启,但是仍然不需要重新安装 。

五、总结

模块化、组件化、插件化通讯方式不同之处

  1. 模块化相互引入,抽取了公共的common模块,其他模块自然要引入这个module。
  2. 组件化主流是隐式和路由。隐式使解耦和灵活大大降低,因此路由是主流。
  3. 插件化本身是不同进程,因此是binder机制进程间通讯。

推荐阅读我的相关系列文章:

Android 热修复原理,DVM或ART与JVM的介绍ClassLoad及双亲委派模型理解

Android 热修复开源方案阿里、微信、美团等

 

发布了153 篇原创文章 · 获赞 755 · 访问量 100万+

猜你喜欢

转载自blog.csdn.net/csdn_aiyang/article/details/103735995