美文网首页Android开发Android技术知识Android开发
Android开发——模块化、组件化、插件化、热修复【简单理解】

Android开发——模块化、组件化、插件化、热修复【简单理解】

作者: 谁动了我的代码 | 来源:发表于2022-11-22 21:03 被阅读0次

    前言

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

    一、模块化

    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)好处

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

    (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需要被重启,但是仍然不需要重新安装 。

    以上就是Android开发中的;模块化、组件化、插件化、热修复的一些简单解析,让你快速理解这些原理。如果想更加深入进阶Android技术可以参考《Android核心进阶技术》这个文档;文档包含几十个技术点。

    五、总结

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

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

    相关文章

      网友评论

        本文标题:Android开发——模块化、组件化、插件化、热修复【简单理解】

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