Android开发经验谈Android技术知识Android开发

【Android面试】热修复、插件化、模块化、组件化、Gradl

2020-08-10  本文已影响0人  唐唐_1388

前言

现如今的Android面试中,热修复、插件化、组件化等技术几乎成为了各大小厂面试必问的知识点。特此为大家做了一个“全家桶”。让大伙不用再去网上收集各种零散的知识点。希望的朋友请点个赞支持一下~

1、热修复和插件化

Android中ClassLoader的种类&特点

热修补技术是怎样实现的,和插件化有什么区别?

插件化:动态加载主要解决3个技术问题:

插件化是体现在功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。以此来减少主应用的规模。

热修复:

原因:因为一个dvm中存储方法id用的是short类型,导致dex中方法不能超过65536个。

代码热修复原理:

相同点:

都使用ClassLoader来实现加载新的功能类,都可以使用PathClassLoader与DexClassLoader。

不同点:

热修复因为是为了修复Bug的,所以要将新的类替代同名的Bug类,要抢先加载新的类而不是Bug类,所以多做两件事:在原先的app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志,还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements,这样才能抢先代替Bug类,完成系统不加载旧的Bug类.。 而插件化只是增加新的功能类或者是资源文件,所以不涉及抢先加载新的类这样的使命,就避过了阻止相关类去打上CLASS_ISPREVERIFIED标志和还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements.

所以插件化比热修复简单,热修复是在插件化的基础上在进行替换旧的Bug类。

热修复原理:

资源修复:

很多热修复框架的资源修复参考了Instant Run的资源修复的原理。

传统编译部署流程如下:

Instant Run编译部署流程如下:

Instant Run中的资源热修复流程:

代码修复:

1、类加载方案:

65536限制:

65536的主要原因是DVM Bytecode的限制,DVM指令集的方法调用指令invoke-kind索引为16bits,最多能引用65535个方法。

LinearAlloc限制:

Dex分包方案主要做的是在打包时将应用代码分成多个Dex,将应用启动时必须用到的类和这些类的直接引用类放到Dex中,其他代码放到次Dex中。当应用启动时先加载主Dex,等到应用启动后再动态地加载次Dex,从而缓解了主Dex的65536限制和LinearAlloc限制。

加载流程:

类加载方案需要重启App后让ClassLoader重新加载新的类,为什么需要重启呢?

各个热修复框架的实现细节差异:

2、底层替换方案:

当我们要反射Key的show方法,会调用Key.class.getDeclaredMethod("show").invoke(Key.class.newInstance());,最终会在native层将传入的javaMethod在ART虚拟机中对应一个ArtMethod指针,ArtMethod结构体中包含了Java方法的所有信息,包括执行入口、访问权限、所属类和代码执行地址等。

替换ArtMethod结构体中的字段或者替换整个ArtMethod结构体,这就是底层替换方案。

AndFix采用的是替换ArtMethod结构体中的字段,这样会有兼容性问题,因为厂商可能会修改ArtMethod结构体,导致方法替换失败。

Sophix采用的是替换整个ArtMethod结构体,这样不会存在兼容问题。

底层替换方案直接替换了方法,可以立即生效不需要重启。采用底层替换方案主要是阿里系为主,包括AndFix、Dexposed、阿里百川、Sophix。

3、Instant Run方案:

什么是ASM?

ASM是一个java字节码操控框架,它能够动态生成类或者增强现有类的功能。ASM可以直接产生class文件,也可以在类被加载到虚拟机之前动态改变类的行为。

Instant Run在第一次构建APK时,使用ASM在每一个方法中注入了类似的代码逻辑:当Change不为Null时,则调用他的access dispatch方法,参数为具体的方法名和方法参数。当MainActivity的onCreate方法做了修改,就会生成替换类MainActivity

change设置为MainActivity Override

最后change就不会为null,则会执行MainActivity Override

dispatch方法,最终会执行onCreate方法,从而实现了onCreate方法的修改。

借鉴Instant Run原理的热修复框架有Robust和Aceso。

动态链接库修复:

重新加载so。

加载so主要用到了System类的load和loadLibrary方法,最终都会调用到nativeLoad方法。其会调用JavaVMExt的LoadNativeLibrary函数来加载so。

so修复主要有两个方案:

为什么选用插件化?

在Android传统开发中,一旦应用的代码被打包成APK并被上传到各个应用市场,我们就不能修改应用的源码了,只能通过服务器来控制应用中预留的分支代码。但是很多时候我们无法预知需求和突然发生的情况,也就不能提前在应用代码中预留分支代码,这时就需要采用动态加载技术,即在程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑。其中可执行文件包括动态链接库so和dex相关文件(dex以及包含dex的jar/apk文件)。随着应用开发技术和业务的逐步发展,动态加载技术派生出两个技术:热修复和插件化。其中热修复技术主要用来修复Bug,而插件化技术则主要用于解决应用越来越庞大以及功能模块的解耦。详细点说,就是为了解决以下几种情况:

插件化的思想:

安装的应用可以理解为插件,这些插件可以自由地进行插拔。

插件化的定义:

插件一般是指经过处理的APK,so和dex等文件,插件可以被宿主进行加载,有的插件也可以作为APK独立运行。

将一个应用按照插件的方式进行改造的过程就叫作插件化。

插件化的优势:

插件化框架对比:

插件化原理:

Activity插件化:

主要实现方式有三种:

Hook实现方式有两种:Hook IActivityManager和Hook Instrumentation。主要方案就是先用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。

Hook IActivityManager:

1、占坑、通过校验:

在Android 7.0和8.0的源码中IActivityManager借助了Singleton类实现单例,而且该单例是静态的,因此IActivityManager是一个比较好的Hook点。

接着,定义替换IActivityManager的代理类IActivityManagerProxy,由于Hook点IActivityManager是一个接口,建议这里采用动态代理。

然后,用代理类IActivityManagerProxy来替换IActivityManager。

2、还原插件Activity:

自定义的Callback实现了Handler.Callback,并重写了handleMessage方法,当收到消息的类型为LAUNCH_ACTIVITY时,将启动SubActivity的Intent替换为启动TargetActivity的Intent。然后使用反射将Handler的mCallback替换为自定义的CallBack即可。使用时则在application的attachBaseContext方法中进行hook即可。

3、插件Activity的生命周期:

Hook Instrumentation:

Hook Instrumentation实现同样也需要用到占坑Activity,与Hook IActivity实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不同。

分析:在Activity通过AMS校验前,会调用Activity的startActivityForResult方法,其中调用了Instrumentation的execStartActivity方法来激活Activity的生命周期。并且在ActivityThread的performLaunchActivity中使用了mInstrumentation的newActivity方法,其内部会用类加载器来创建Activity的实例。

方案:在Instrumentation的execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation的newActivity方法中还原TargetActivity,这两部操作都和Instrumentation有关,因此我们可以用自定义的Instumentation来替换掉mInstrumentation。具体为:

资源插件化:

资源的插件化和热修复的资源修复都借助了AssetManager。

资源的插件化方案主要有两种:

so的插件化:

so的插件化方案和so热修复的第一种方案类似,就是将so插件插入到NativelibraryElement数组中,并且将存储so插件的文件添加到nativeLibraryDirectories集合中就可以了。

插件的加载机制方案:

2、模块化和组件化

模块化的好处

www.jianshu.com/p/376ea8a19…

分析现有的组件化方案:

很多大厂的组件化方案是以 多工程 + 多 Module 的结构(微信, 美团等超级 App 更是以 多工程 + 多 Module + 多 P 工程(以页面为单元的代码隔离方式) 的三级工程结构), 使用 Git Submodule 创建多个子仓库管理各个模块的代码, 并将各个模块的代码打包成 AAR 上传至私有 Maven 仓库使用远程版本号依赖的方式进行模块间代码的隔离。

组件化开发的好处:

跨组件通信:

跨组件通信场景:

跨组件通信方案分析:

提供服务的业务模块:

在公共服务(CommonService) 中声明 Service 接口 (含有需要被调用的自定义方法), 然后在自己的模块中实现这个 Service 接口, 再通过 ARouter API 暴露实现类。

使用服务的业务模块:

通过 ARouter 的 API 拿到这个 Service 接口(多态持有, 实际持有实现类), 即可调用 Service 接口中声明的自定义方法, 这样就可以达到模块之间的交互。 此外,可以使用 AndroidEventBus 其独有的 Tag, 可以在开发时更容易定位发送事件和接受事件的代码, 如果以组件名来作为 Tag 的前缀进行分组, 也可以更好的统一管理和查看每个组件的事件, 当然也不建议大家过多使用 EventBus。

如何管理过多的路由表?

RouterHub 存在于基础库, 可以被看作是所有组件都需要遵守的通讯协议, 里面不仅可以放路由地址常量, 还可以放跨组件传递数据时命名的各种 Key 值, 再配以适当注释, 任何组件开发人员不需要事先沟通只要依赖了这个协议, 就知道了各自该怎样协同工作, 既提高了效率又降低了出错风险, 约定的东西自然要比口头上说的强。

Tips: 如果您觉得把每个路由地址都写在基础库的 RouterHub 中, 太麻烦了, 也可以在每个组件内部建立一个私有 RouterHub, 将不需要跨组件的路由地址放入私有 RouterHub 中管理, 只将需要跨组件的路由地址放入基础库的公有 RouterHub 中管理, 如果您不需要集中管理所有路由地址的话, 这也是比较推荐的一种方式。

ARouter路由原理:

ARouter维护了一个路由表Warehouse,其中保存着全部的模块跳转关系,ARouter路由跳转实际上还是调用了startActivity的跳转,使用了原生的Framework机制,只是通过apt注解的形式制造出跳转规则,并人为地拦截跳转和设置跳转条件。

多模块开发的时候不同的负责人可能会引入重复资源,相同的字符串,相同的icon等但是文件名并不一样,怎样去重?

3、gradle

gradle熟悉么,自动打包知道么?

如何加快 Gradle 的编译速度?

Gradle的Flavor能否配置sourceset?

Gradle生命周期

4、编译插桩

谈谈你对AOP技术的理解?

说说你了解的编译插桩技术?


推荐阅读:
字节跳动8年老Android面试官谈;Context都没弄明白凭什么拿高薪?
做了六年Android,终于熬出头了,15K到31K全靠这份高级面试题+解析
字节、腾讯,阿里Android高级面试真题汇总,会一半随便进大厂

上一篇下一篇

猜你喜欢

热点阅读