【Android面试】热修复、插件化、模块化、组件化、Gradle、编译插桩技术,面试全家桶

动态链接库修复:

重新加载so。

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

so修复主要有两个方案:

  • 1、将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载。
  • 2、调用System的load方法来接管so的加载入口。
为什么选用插件化?

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

  • 1、业务复杂、模块耦合:随着业务越来越复杂,应用程序的工程和功能模块数量会越来越多,一个应用可能由几十甚至几百人来协同开发,其中的一个工程可能就由一个小组来进行开发维护,如果功能模块间的耦合度较高,修改一个模块会影响其它功能模块,势必会极大地增加沟通成本。
  • 2、应用间的接入:当一个应用需要接入其它应用时,如淘宝,为了将流量引流到其它的淘宝应用如:飞猪旅游、口碑外卖、聚划算等等应用,如使用常规技术有两个问题:可能要维护多个版本的问题或单个应用体积将会非常庞大的问题。
  • 3、65536限制,内存占用大。
插件化的思想:

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

插件化的定义:

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

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

插件化的优势:
  • 低耦合
  • 应用间的接入和维护更便捷,每个应用团队只需要负责自己的那一部分。
  • 应用及主dex的体积也会相应变小,间接地避免了65536限制。
  • 第一次加载到内存的只有淘宝客户端,当使用到其它插件时才会加载相应插件到内存,以减少内存占用。
插件化框架对比:
  • 最早的插件化框架:2012年大众点评的屠毅敏就推出了AndroidDynamicLoader框架。
  • 目前主流的插件化方案有滴滴任玉刚的VirtualApk、360的DroidPlugin、RePlugin、Wequick的Small框架。
  • 如果加载的插件不需要和宿主有任何耦合,也无须和宿主进行通信,比如加载第三方App,那么推荐使用RePlugin,其他情况推荐使用VirtualApk。由于VirtualApk在加载耦合插件方面是插件化框架的首选,具有普遍的适用性,因此有必要对它的源码进行了解。
插件化原理:
Activity插件化:

主要实现方式有三种:

  • 反射:对性能有影响,主流的插件化框架没有采用此方式。
  • 接口:dynamic-load-apk采用。
  • Hook:主流。

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是一个接口,建议这里采用动态代理。

  • 拦截startActivity方法,获取参数args中保存的Intent对象,它是原本要启动插件TargetActivity的Intent。
  • 新建一个subIntent用来启动StubActivity,并将前面得到的TargetActivity的Intent保存到subIntent中,便于以后还原TargetActivity。
  • 最后,将subIntent赋值给参数args,这样启动的目标就变为了StubActivity,用来通过AMS的校验。

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

  • 当版本大于等于26时,使用反射获取ActivityManager的IActivityManagerSingleton字段,小于时则获取ActivityManagerNative中的gDefault字段。
  • 然后,通过反射获取对应的Singleton实例,从上面得到的2个字段中拿到对应的IActivityManager。
  • 最后,使用Proxy.newProxyInstance()方法动态创建代理类IActivityManagerProxy,用IActivityManagerProxy来替换IActivityManager。

2、还原插件Activity:

  • 前面用占坑Activity通过了AMS的校验,但是我们要启动的是插件TargetActivity,还需要用插件TargetActivity来替换占坑的SubActivity,替换时机为图中步骤2之后。
  • 在ActivityThread的H类中重写的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,最终会调用Activity的onCreate方法。在Handler的dispatchMessage处理消息的这个方法中,看到如果Handelr的Callback类型的mCallBack不为null,就会执行mCallback的handleMessage方法,因此mCallback可以作为Hook点。我们可以用自定义的Callback来替换mCallback。

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

3、插件Activity的生命周期:

  • AMS和ActivityThread之间的通信采用了token来对Activity进行标识,并且此后的Activity的生命周期处理也是根据token来对Activity进行标识的,因为我们在Activity启动时用插件TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity之前,因此performLaunchActivity的r.token就是TargetActivity。所以TargetActivity具有生命周期。

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。具体为:

  • 首先检查TargetActivity是否已经注册,如果没有则将TargetActivity的ClassName保存起来用于后面还原。接着把要启动的TargetActivity替换为StubActivity,最后通过反射调用execStartActivity方法,这样就可以用StubActivity通过AMS的验证。
  • 在newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。最后使用反射用InstrumentationProxy替换mInstumentation。
资源插件化:

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

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

  • 1、合并资源方案,将插件的资源全部添加到宿主的Resources中,这种方案插件可以访问宿主的资源。
  • 2、构建插件资源方案,每个插件都构造出独立的Resources,这种方案插件不可以访问宿主资源。
so的插件化:

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

插件的加载机制方案:
  • 1、Hook ClassLoader。
  • 2、委托给系统的ClassLoader帮忙加载。

2、模块化和组件化

模块化的好处

www.jianshu.com/p/376ea8a19…

分析现有的组件化方案:

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

组件化开发的好处:
  • 避免重复造轮子,可以节省开发和维护的成本。
  • 可以通过组件和模块为业务基准合理地安排人力,提高开发效率。
  • 不同的项目可以共用一个组件或模块,确保整体技术方案的统一性。
  • 为未来插件化共用同一套底层模型做准备。
跨组件通信:

跨组件通信场景:

  • 第一种是组件之间的页面跳转 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类类型)。
  • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)。
跨组件通信方案分析:
  • 第一种组件之间的页面跳转不需要过多描述了, 算是 ARouter 中最基础的功能, API 也比较简单, 跳转时想传递不同类型的数据也提供有相应的 API。
  • 第二种组件之间的自定义类和自定义方法的调用要稍微复杂点, 需要 ARouter 配合架构中的 公共服务(CommonService) 实现:
提供服务的业务模块:

在公共服务(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技术的理解?
说说你了解的编译插桩技术?

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
给文章留个小赞,就可以免费领取啦~

戳我领取:Android对线暴打面试指南超硬核Android面试知识笔记3000页Android开发者架构师核心知识笔记

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

资料已经上传在我的GitHub,或者关注后简信我【666】即可领取(无偿)。


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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,


《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

n、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

[外链图片转存中…(img-QYGv68h0-1712829731211)]
[外链图片转存中…(img-WIU8FeYm-1712829731211)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值