Android基础面试常常死在这几个问题上,小白也能看明白

2023-11-19

前言

疫情一过,我相信将会是面试求职的高峰时期,如果此时手里有份高质量的面试宝典,那么你将得心应手面对考官各种问题。虽然不敢保证你能应聘上心仪的职位,但是能保证看完这些内容你的收获将超乎你的想象! 此份面试宝典搜集各大网络平台(如果侵权,请您告知),在此感谢他们的用心总结,才有这份足够全面的面试宝典!

内容点较丰富,建议找工作的小伙伴一定要慢慢细细品,我这里随意展示一下,保证不会让你失望!

背景介绍

Android 项目一般使用 gradle 作为构建打包工具,而其执行速度慢也一直为人所诟病,对于今日头条 Android 项目这种千万行级别的大型工程来说,全量编译一次的时间可能高达六七分钟,在某些需要快速验证功能的场景,改动一行代码的增量编译甚至也需要等两三分钟,这般龟速严重影响了开发体验与效率,因此针对 gradle 编译构建耗时进行优化显得尤为重要。

在今日头条 Android 项目上,编译构建速度的优化和恶化一直在交替执行,18 年时由于模块化拆分等影响,clean build 一次的耗时达到了顶峰 7 分 30s 左右,相关同学通过模块 aar 化,maven 代理加速,以及增量 java 编译等优化手段,将 clean build 耗时优化到 4 分钟,增量编译优化到 20~30s 。但是后面随着 kotlin 的大规模使用,自定义 transform 以及 apt 库泛滥,又将增量编译速度拖慢到 2 分 30s ,且有进一步恶化的趋势。为了优化现有不合理的编译耗时,防止进一步的恶化,最近的 5,6 双月又针对编译耗时做了一些列专项优化(kapt,transform,dexBuilder,build-cache 等) 并添加了相关的防恶化管控方案。 从 4.27 截止到 6.29 ,整体的优化效果如下:

历史优化方案

由于 18 年左右客户端基础技术相关同学已经对今日头条 Android 工程做了许多 gradle 相关的优化,且这些优化是近期优化的基础,因此先挑选几个具有代表性的方案进行介绍,作为下文的背景同步。

maven 代理优化 sync 时间

背景

gradle 工程往往会在 repositories 中添加一些列的 maven 仓库地址,作为组件依赖获取的查找路径,早期在今日头条的项目中配置了十几个 maven 的地址,但是依赖获取是按照 maven 仓库配置的顺序依次查找的,如果某个组件存在于最后一个仓库中,那前面的十几个仓库得依次发起网络请求查找,并在网络请求返回失败后才查找下一个,如果项目中大多组件都在较后仓库的位置,累加起来的查找时间就会很长。

优化方案

  1. 使用公司内部搭建的 maven 私服,在私服上设置代理仓库,为其他仓库配置代理(例如 google、jcenter、mavenCentral 等仓库),代理仓库创建好后,在 Negative Cache 配置项中关闭其 cache 开关:如果查找时没有找到某版本依赖库时会缓存失败结果,一段时间内不会重新去 maven 仓库查找对应依赖库,即使 maven 仓库中已经有该版本的依赖库,查找时仍然返回失败的结果。
  2. 建立仓库组,将所有仓库归放到一个统一的仓库组里,依赖查找时只需要去这个组仓库中查找,这样能大大降低多次发起网络请求遍历仓库的耗时。

模块 aar 化

背景

今日头条项目进行了多次组件化和模块化的重构,分拆出了 200 多个子模块,这些子模块如果全都 include 进项目,那么在 clean build 的时候,所有子模块的代码需要重新编译,而对于大多数开发人员来说,基本上只关心自己负责的少数几个模块,根本不需要改动其他模块的代码,这些其他 project 的配置和编译时间就成为了不必要的代价。

优化方案

对于以上子模块过多的解决方案是:将所有模块发布成 aar ,在项目中全部默认通过 maven 依赖这些编译好的组件,而在需要修改某个模块时,通过配置项将该模块的依赖形式改为源码依赖,做到在编译时只编译改动的模块。但是这样做会导致模块渐渐的又全部变为源码依赖的形式,除非规定每次修改完对应模块后,开发人员自己手动将模块发布成 aar ,并改回依赖形式。这种严重依赖开发人员自觉,并且在模块数量多依赖关系复杂的时候会显得异常繁琐,因此为了开发阶段的便利,设计了一整套更完整细致的方案:

  1. 开发时,从主分支拉取的代码一定是全 aar 依赖的,除了 app 模块没有任何子模块是源码引入。
  2. 需要修改对应模块时,通过修改 local.properties 里的 INCLUDES 参数指定源码引入的模块。
  3. 开发完成后,push 代码至远端,触发代码合并流程后,在 ci 预编译过程与合码目标分支对比,检测修改的模块,将这些模块按照依赖关系依次发布成 aar ,并在工程中修改依赖为新版本的 aar, 这一步保证了每次代码合入完成后,主分支上的依赖都是全 aar 依赖的。

收益

通过上述改造,将源码模块切换成 aar 依赖后,clean build 耗时从 7,8 分钟降低至 4,5 分钟,收益接近 50%,效果显著。

增量 java/kotlin 编译

背景

在非 clean build 的情况下,更改 java/kotlin 代码虽然会做增量编译,但是为了绝对的正确性,gradle 会根据一些列依赖关系计算,选择需要重新编译的代码,这个计算粒度比较粗,稍微改动一个类的代码,就可能导致大量代码重新执行 apt, 编译等流程。

由于 gradle 作为通用框架,其设计的基本原则是绝对的正确,因此很容易导致增量编译失效,在实际开发中,为了快速编译展示结果,可以在编译正确性和编译速度上做一个折中的方案:

  1. 禁用原始的 javac/kotlinCompile 等 task, 自行实现代码增量修改判断,只编译修改的代码。
  2. 动态禁用 kapt 相关的 task, 降低 kapt,kaptGenerateStub 等 task 的耗时。

以上方案(下文全部简称为 fastbuild) 虽然在涉及常量修改,方法签名变更方面 存在一定的问题(常量内联等),但是能换来增量编译从 2 分多降低至 20~30s,极大的提升编译效率,且有问题的场景并不常见,因此整体上该方案是利大于弊的。

编译耗时恶化

通过上文介绍的几个优化方案和其他优化方式,在 18 年时,今日头条 Android 项目的整体编译速度(clean build 4~5min, fast 增量编译 20~30s)在同量级的大型工程中来说是比较快的 ,然而后期随着业务发展的需求,编译脚本添加了很多新的逻辑:

  1. kotlin 大规模使用,kapt 新增了很多注解处理逻辑。
  2. 引入对 java8 语法的支持 , java8 语法的 desugar(脱糖)操作增加了编译耗时。
  3. 大量的字节码插桩需求,添加了许多 transform ,大幅度提升了增量编译耗时。

这些逻辑的引入,使得增量编译耗时恶化到 2 分 30s,即使采用 fastbuild,改动一行代码编译也需要 1 分 30s 之多,开发体验非常差。而下文将着重描述最近一段时间对上述问题的优化过程。

近期优化方案

app 壳模块 kapt 优化

背景

今日头条工程经过多次模块化,组件化重构后, app 模块(NewsArticle)的大部分代码都已经迁移到子模块(上文已经介绍过子模块可以采用 aar 化用于编译速度优化,app 模块只剩下一个壳而已。

但是从 build profile 数据(执行 gradle 命令时添加 --profile 参数会在编译完成后输出相关 task 耗时的统计文件) 中发现到一个异常 case:明明只有 2 个类的 app 模块 kapt(annotationProcessor 注解处理) 相关耗时近 1 分钟。

通过进一步观察,虽然 app 模块拆分后只有 2 个简单类的代码,但是却用了 6 种 kapt 库, 且实际生效的只是其中 ServiceImpl 一个注解 (内部 ServiceManager 框架,用于指示生产 Proxy 类,对模块之间代码调用进行解耦)。如此一顿操作猛如虎,每次编译却只生成固定的两个 Proxy 类,与 53s 的高耗时相比,投入产出比极低。

优化方案

把固定生成的 Proxy 类从 generate 目录移动到 src 目录,然后禁止 app 模块中 kapt 相关 task ,并添加相关管控方案(如下图: 检测到不合理情况后立刻抛出异常),防止其他人添加新增的 kapt 库。

收益

  1. 在 mac clean build 中平均有 40s 收益
  2. 在 ci clean build 中平均有 20s 收益

kapt 隔离优化

背景

通过上文介绍在 app 模块发现的异常的 kapt case, 进而发现在工程中为了方便,定义了一个 library.gradle ,该文件的作用是定义项目中通用的 Android dsl 配置和共有的基础依赖,因此项目中所有子模块均 apply 了这个文件,但是这个文件陆陆续续的被不同的业务添加新的 kapt 注解处理库,在全源码编译时,所有子模块都得执行 library 模块中定义的全部 6 个 kapt ,即使该模块没有任何注解相关的处理也不例外。

而上述情况的问题在于:相比纯 java 模块的注解处理,kotlin 代码需要先通过 kaptGenerateStub 将 kt 文件转换成为 java ,让 apt 处理程序能够统一的面向 java 做注解扫描和处理。但是上面讲到其实有很多模块是根本不会有任何实际 kapt 处理过程的,却白白的做了一次 kt 转 java 的操作,源码引入的模块越多,这种无意义的耗时累加起来也非常可观。

为了能够弄清楚到底有哪些子模块真正用到了 kapt ,哪些没用到可以禁用掉 kapt 相关 task ,对项目中所有子模块进行了一遍扫描:

  1. 获取 kapt configuration 的所有依赖,可以得到 kapt 依赖库的 jar 包,利用 asm 获取所有 annotation.
  2. 遍历所有 subproject 的 sourceset 下所有 .java,.kt 源文件,解析 import 信息,看是否有步骤 1 中解析的 annotation
  3. package task 完成后遍历 所有 subproject 所有 generate/apt ,generate/kapt 目录下生成的 java 文件

使用上述方案,通过全源码打包最终扫描出来大概是 70+模块不会进行任何 kapt 的实际输出,且将这些不会进行输出的 kapt,kaptGenerateStub 的 task 耗时累加起来较高 217s (由于 task 并发执行所以实际总时长可能要少一些).

获取到不实际生成 kapt 内容的模块后,开始对这些模块进行细粒度的拆分,让它们从 apply library.gradle 改为没有 kapt 相关的 library-api.gradle ,该文件除了禁用 kapt 外,与 library 逻辑一致。

但是这样做算是在背后偷偷做了些更改,很可能后续新来的同学不知道有这种优化手段,可能新增了注解后却没有任何输出且找不到原因,而优化效果最好是尽量少给业务同学带来困扰。为了避免这种情况,便对这些 library-api 模块依赖的注解做隔离优化,即:把这些模块依赖的注解库全部 自动 exclude 掉,在尝试使用注解时会因获取不到引用(如下图所示),第一时间发现到依赖被移除的问题。

另一方面在编译出现错误时,对应 gradle 插件会自动解析找不到的符号,如果发现该符号是被隔离优化的注解,会提示将 library-api 替换成 library,尽可能降低优化方案对业务的负面影响。

收益

  1. mac 全源码场景中有 58s 左右的加速收益。
  2. ci 机器上由于 cpu 核数更多 ,task 并发性能更好,只有 10s 左右的收益。

写在最后

本次我的分享也接近尾声了,感谢你们在百忙中花上一下午来这里聆听我的宣讲,希望在接下来的日子,我们共同成长,一起进步!!!

最后放上一个大概的Android学习方向及思路(详细的内容太多了~),提供给大家:

对于程序员来说,要学习的知识内容、技术有太多太多,这里就先放上一部分,其他的内容有机会在后面的文章向大家呈现出来,不过我自己所有的学习资料都整理成了一个文档,一直在不断学习,如今整理的资料不知不觉居然已经有将近80G了,在这里作为读者福利免费分享给大家,希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

资料获取传送门:点击免费获取Android架构设计

群内有许多技术大牛,有任何问题,欢迎广大网友一起来交流,群内还不定期免费分享高阶Android学习视频资料和面试资料包~

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!**

Android架构师之路很漫长,一起共勉吧!

如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android基础面试常常死在这几个问题上,小白也能看明白 的相关文章

  • 使用holoeverywhere滑块插件时如何从活动中获取当前可见的片段?

    我想知道如何执行这些操作无处不在的全息 https github com Prototik HoloEverywhere 将滑块插件与 tabber 结合使用时 从活动中获取对当前可见和活动片段的引用 从活动 其他片段获取对 TabsTab
  • 视图无法解析为类型

    这里的视图似乎有什么问题 我该如何解决它 错误 视图无法解析为类型 public void onItemClick AdapterView
  • Android Studio Beta 频道、Android Studio Canary 频道、Android Studio Dev 频道有什么区别? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我是 android 新手 想知道要安装哪个 studio Android Studio Beta 频道 Android Studio Ca
  • 多种语言的多种字体

    我最近在开发应用程序时遇到了一种情况 我必须在文本视图中显示不同的语言 目前我正在展示一些使用字体 字体像这样 Typeface tf Typeface createFromAsset this getAssets DroidHindi t
  • 如何使用数据绑定将点击侦听器设置为 LinearLayout

    我目前正在尝试将点击侦听器设置为LinearLayout查看在 xml使用数据绑定的布局文件 我已经设法让它在其他视图上很好地工作 比如Button or TextView 但由于某种原因 它不能与LinearLayout 这是我尝试的基本
  • 如何实现 ALTER TABLE 的示例[重复]

    这个问题在这里已经有答案了 我已经多次问过这个问题 但尚未得到完整的答案 如何实现 ALTER TABLE 语句以向数据库添加列 有人可以给我举个例子吗 请阅读SQLite ALTER TABLE 参考 http sqlite org la
  • Android:文本淡入和淡出

    我已阅读此 stackoverflow 问题和答案 并尝试实现文本淡入和淡出 Android中如何让文字淡入淡出 https stackoverflow com questions 8627211 how to make text fade
  • Android studio - 如何保存先前活动中选择的数据

    这是我的代码片段 这Textview充当按钮并具有Onclicklistner在他们 当cpu1000时Textview单击它会导致cpu g1000其代码如下所示的类 public class Game 1000 extends AppC
  • SQLite FTS4 使用特殊字符进行搜索

    我有一个 Android 应用程序 它使用 FTS4 虚拟表在 SQLite 数据库中搜索数据 它工作正常 但是当表中的数据包含特殊字符 如 或 时 SQLite MATCH 函数不会给出任何结果 我现在迷路了 谢谢 注意 默认的分词器真的
  • 更新到 Kotlin 1.3.30 后出现“未解析的引用:Parcelize”

    我使用 Kotlin 1 3 21 很长时间了kotlin android extensions插件长期处于实验模式 今天我通过升级版本切换到 Kotlin 1 3 30 现在无论我使用什么 Parcelize注释我看到错误 Unresol
  • 表面视图+gl表面视图+框架布局

    我是 java 和 OpenGL 的新手 我正在尝试获得一个相机预览屏幕 能够 同时显示 3D 对象 浏览完样本后 api 演示 我想结合示例的代码 api 演示就足够了 但不知何故它不起作用 迫使我 启动时关闭 错误被称为空指针 例外 有
  • Android Gradle 问题 - Flutter / Dart

    我的 Gradle 同步有问题 我使用 IntelliJ 和 Android Studio 构建 Flutter Dart 应用程序 我添加了 2 个新的依赖项 现在 Gradle 出现了问题 在 Android Studio 中一切正常
  • Android NDK 支持区域设置吗?

    我真正想做的就是使用格式化日期strftime x 以正确的顺序 在大多数平台上调用setlocale 足够 在 Android 上 我不断收到 美国日期 那么 Android 不支持语言环境吗 No setlocale and strft
  • 如何使用 Retrofit 解析嵌套 json....?

    我不知道该怎么办使用 Retrofit 解析 json 熟悉使用 Retrofit 解析简单的 json 但不熟悉解析嵌套Json using Retrofit 这是我的 Json 数据 current observation image
  • Android 无法解析日期异常

    当尝试解析发送到我的 Android 客户端的日期字符串时 我得到一个无法解析的日期 这是例外 java text ParseException 无法解析的日期 2018 09 18T00 00 00Z 位于 偏移量 19 在 java t
  • 带有 backstack Resume 的嵌套片段

    在我的应用程序中有几个fragments in an activity我正在维护一个backStack对于这些fragment 一切都很好 但其中有一个嵌套的片段 当我把它放入backStack然后再次按后退按钮恢复 该片段看起来与先前的内
  • Android应用主题更换流畅

    我正在开发一个提供白天和夜间主题的项目 我正在更改主题 夜间主题 AppCompatDelegate setDefaultNightMode AppCompatDelegate MODE NIGHT YES 日主题 AppCompatDel
  • Android 4.4 Kitkat 自定义视图操作栏未填充整个宽度

    我试图拥有一个带有自定义视图的简单操作栏 但我得到以下结果 为了演示 我创建了一个带有黄色背景颜色的简单 xml 它应该占据整个宽度 这是 XML
  • 找不到数据库路径是不可能的

    我对 android 开发很陌生 现在我正在尝试通过扩展 SQLiteOpenHelper 的类创建数据库 我确信数据存储在我的 Nexus 7 我用来测试应用程序的设备 上的某个位置 但是我找不到数据库的路径 我四处寻找其他类似的问题 所
  • 在线性布局内的 ScrollView 内并排对齐 TextView

    我有一个带有滚动视图的线性布局 我想保留它的当前格式 但只需将 textView2a 和 textView3a 并排放置 而不会破坏我当前的布局格式 我已经包含了我最近的尝试 但它们似乎不正确 提前致谢 Java菜鸟 当前有效的 XML

随机推荐

  • yagmail发送邮件

    分五步实现 1 导入yagmail第三方库 2 连接邮箱 3 添加邮件内容 4 发送邮件 5 释放邮箱 导入yagmail第三方库 import yagmail yagmail SMTP user 邮箱名 host SMTP服务器域名 ya
  • Vue 下拉框值变动事件传多个参数

    在使用 Vue 进行开发时 下拉框值变动事件 change 是很常用的 其传参一般分为两种方式 默认传参和自定义传参 默认传参 change 默认会传选中项标识的参数 在传参处不用定义 在方法中直接接受即可
  • Linux下的ssh

    SSH 为 Secure Shell 的缩写 由 IETF 的网络工作小组 Network Working Group 所制定 SSH 为建立在应用层和传输层基础上的安全协议 SSH 是目前较可靠 专为远程登录会话和其他网络服务提供安全性的
  • 将MindManager添加到鼠标右键新建项

    事情缘起于自己做事习惯为每个项目添加一个思维导图作为项目总看板 但每次都需要自己通过软件新建一个空白文件 再将空白文件索引到项目文件夹 再更名保存 虽然步骤不太多 但每次都需要这样的操作确实让我很困扰 所以就心想能不能让Mindmanage
  • ORM 的功能

    ORM要完成对象的初始化以及CRUD功能 在这些功能中尤其是query和update已经蕴含了相应的map的功能 除此之外还要提供transaction和concurrency的功能 这些基本的功能很好理解 不过相应的实现是比较复杂的 还要
  • 机器学习之朴素贝叶斯

    机器学习之朴素贝叶斯 1 朴素贝叶斯 2 朴素贝叶斯应用 3 代码实现贝努力朴素贝叶斯 4 代码实现高斯朴素贝叶斯 5 代码实现多项式朴素贝叶斯 6 总结 前言 主要介绍朴素贝叶斯的概念 公式 以及代码实现贝努利 高斯 多项式朴素贝叶斯 1
  • PyQt6 Designer与实际运行不一致问题

    我们在Designer设计布局时 会先定义好布局然后只在某个布局内存放元素 其他布局为空 可能就会产生布局不一致问题 其实已经存在了对应的布局只是里面为空 所以剩余空间优先被有元素的布局使用 我们在每个布局中加入某个组件即可解决
  • VC++ CMemDC类的扩展(新版)

    上一版本链接 https blog csdn net u012156872 article details 103755254 测试过程中发现存在问题 于是进行了功能补充 源码实现 CSWMemDC h pragma once namesp
  • vant + moment插件自定义count-down倒计时

    根据vant官网https vant ui github io vant v2 zh CN按需引入找到倒计时 自定义时间格式参考下图
  • matlab2016b版本安装

    安装包下载地址 链接 https pan baidu com s 1RrUp8TBIa7g7mhfSUtqAsg 提取码 foc1 1 解压文件包 2 在 matlab R2016b 64bit 文件下 找到 setup 文件 右击选择 以
  • 【C++】_5.模板

    目录 1 泛型编程 2 函数模板 2 1 概念 2 2 函数模板格式 2 3 函数模板原理 2 4 函数模板的实例化 2 5 函数模板的匹配原则 3 类模板 4 非类型模板参数 5 模板的特化 5 1 概念 5 2 函数模板特化 5 3 类
  • 解析request.getParameter() 和request.getAttribute() 区别

    一 request getParameter 和request getAttribute 区别 1 request getParameter 取得是通过容器的实现来取得通过类似post get等方式传入的数据 request setAttr
  • django高并发部署

    django高并发部署
  • React解密:React Hooks函数之useCallback和useMemo

    之所以将useCallback和useMemo放到一起 从某种意义上说 他们都是性能优化的始作俑者 他们也有很多的共性 我们先来回顾一下class组件性能优化的点 调用 setState 就会触发组件的重新渲染 无论前后 state 是否相
  • 64位系统树莓派部署yolo-fatestv2---超多坑

    最近在研究yolo fastest 开始面对作者大大的一堆部署的指令在pycharm的终端里面一顿操作 然后一路报错 后来才发现原来都是linux的指令 后来在虚拟机上也尝试部署过 成功之后本来想直接挪到树莓派上 但是尝试许久都以失败告终
  • mysql修改权限

    mysql权限 显示用户权限 mysql gt show grants for jeffrey localhost 一般 数据库管理员首先建立用户 定义其非特权特征 例如其密码 是否使用安全链接以及对服务器资源的访问限制 而后使用grant
  • java枚举类的定义和使用

    开始时间 2018年8月11日11 04 35 结束时间 2018年8月11日12 30 37 累计 1小时 枚举类的对象是有限个 对象个数 一个可以看做单例模式的实现 多个 为枚举类 1 如何定义 cccccccc 1 私有化类的构造器
  • 服务器虚拟化解决方案

    根据以往经验推断 一台主流双路 PC 服务器可以承担 3 6 个应用系统在其 上运行 本期项目总共有 N 个业务系统 考虑到硬件资源需具备一定的冗余能 力和实现高可用 HA 在线迁移 动态调度 后期扩展等诸多因素 推荐 2 台双 路 PC
  • 智能指针 -- unique_ptr

    源码分析 源码链接 gcc unique ptr h at master gcc mirror gcc GitHub 上面链接中的源码是unique ptr的完整定义 我们来简化其类结构看看 template
  • Android基础面试常常死在这几个问题上,小白也能看明白

    前言 疫情一过 我相信将会是面试求职的高峰时期 如果此时手里有份高质量的面试宝典 那么你将得心应手面对考官各种问题 虽然不敢保证你能应聘上心仪的职位 但是能保证看完这些内容你的收获将超乎你的想象 此份面试宝典搜集各大网络平台 如果侵权 请您