Android工程化最佳实践

更多详情

内容简介: 本书从工程实践角度详细阐述了Android的知识内容,全书分为基础知识和工程优化两部分。在工程优化部分专门增加了常用的App编译提速和瘦身的内容,对于大型分层项目的测试技巧也有所涉及。本书涵盖Android开发的实际业务知识,涉及Dialog、Intent、Fragment等源码的核心细节分析,并扩展了一部分框架设计的内容,章节最后总结了开箱即用的开源库方案,实现从理论到实际的完整论述。最后还给出了抓包工具的使用技巧,帮助读者能方便地寻找到适合自己的工具集。本书适合中、高级Android程序员阅读,也可以作为初级程序员进阶学习的参考书。

目录: 第1章 探寻高效易用的反射API
1.1 反射的能力
1.1.1 得到Class对象
1.1.2 操作Field
1.1.3 调用Method
1.1.4 动态代理
1.2 反射封装库——JOOR
1.2.1 反射的流程
1.2.2 VirtualApp中的反射
1.2.3 一行代码建立对象
1.2.4 简化Field的相关操作
1.2.5 简化方法调用
1.2.6 封装动态代理
1.3 注意事项
1.3.1 反射的性能问题
1.3.2 反射的使用时机
1.3.3 如何降低反射的性能损耗
1.3.4 反射的危险性
1.3.5 反射和混淆的关系
1.4 总结
第2章 打造高扩展性的Log系统
2.1 基本概念
2.2 命令行操作Log
2.2.1 输出日志
2.2.2 过滤日志
2.3 Android Studio中的Log
2.3.1 设置模板
2.3.2 正则过滤
2.3.3 热部署Log
2.4 微信的Xlog
2.4.1 设计和开发目标
2.4.2 编译、引入和使用
2.4.3 对Log文件进行优化
2.5 美团的Logan
2.6 扩展Log的功能
2.6.1 TAG的自动化
2.6.2 文本内容的设计
2.6.3 开关的设计
2.7 封装Log库
2.7.1 Timber
2.7.2 LogDelegate
2.7.3 Logger
2.7.4 扩展Timber的功能
2.7.5 分发日志
2.8 实用日志
2.8.1 操作耗时日志
2.8.2 页面跳转日志
2.8.3 网络请求日志
2.9 总结
第3章 万变不离其宗的Intent
3.1 源码分析
3.1.1 静态变量的写法
3.1.2 Intent的深拷贝
3.1.3 makeMainActivity
3.1.4 Intent的Chooser
3.1.5 用URI代替Intent
3.1.6 存取值的底层实现
3.1.7 区分显式和隐式Intent
3.1.8 抛弃Bundle的传值策略
3.2 序列化方案
3.2.1 Serializable/Externalizable
3.2.2 Android中的Parcelable
3.2.3 Google的Protocol Buffer
3.2.4 Twitter的Serial
3.3 常见问题
3.3.1 父类的序列化
3.3.2 类型转换异常
3.3.3 重复启动的问题
3.3.4 传递大对象
3.4 简单的传值库——Parceler
3.4.1 降低Key的维护成本
3.4.2 自动维护Intent的Key
3.4.3 Jetpack中的自动化
3.4.4 自动保存状态
3.4.5 处理ClassCastException
3.4.6 IntentLauncher
3.4.7 统一存取的API
3.5 总结
第4章 SharedPrefrences的再封装
4.1 源码分析
4.1.1 缓存机制
4.1.2 SharedPreferencesImpl
4.1.3 值操作
4.1.4 提交操作
4.2 异常处理
4.2.1 name为null
4.2.2 管理好Key的取名
4.2.3 清空操作失效
4.2.4 磁盘写入异常
4.2.5 出现ANR
4.2.6 存序列化对象
4.2.7 多App和多进程访问异常
4.3 性能优化
4.3.1 避免储存大量数据
4.3.2 尽可能提前初始化
4.3.3 避免Key过长
4.3.4 多次操作,批量提交
4.3.5 缓存Editor对象
4.3.6 不存放HTML和JSON
4.3.7 拆分高频和低频操作
4.4 封装SharedPreferences
4.4.1 PreferenceDataStore
4.4.2 通过接口提高内聚
4.4.3 得到SharedPreferences
4.4.4 多用户存储设计
4.4.5 统一管理Key
4.4.6 自动判断返回值类型
4.4.7 决定是否使用Apply
4.4.8 存放序列化对象
4.4.9 支持数据格式转换器
4.5 思维扩展
4.5.1 偏好界面的实现方案
4.5.2 监听数据的改变
4.5.3 利用Tray实现多进程访问
4.5.4 React Native中的使用
4.6 总结
第5章 寻找Fragment的继任者
5.1 使用场景
5.1.1 日夜间模式
5.1.2 缓存界面数据
5.1.3 作为搜索页
5.1.4 作为Presenter
5.2 源码分析
5.2.1 Transaction简介
5.2.2 提交操作
5.2.3 commitAllowingStateLoss
5.2.4 Add操作的原理
5.2.5 Replace操作的本质
5.2.6 Fragment的可见性监听
5.2.7 ViewPager中的懒加载
5.3 常见问题
5.3.1 Activity为空
5.3.2 startActivityForResult
5.3.3 ViewPager的getItem
5.3.4 FragmentPagerAdapter
5.3.5 显示一个对话框
5.3.6 重叠显示的问题
5.3.7 Fragment的StateLoss
5.4 Fragment的替代品
5.4.1 Jetpack的Navigation
5.4.2 Square的Flow
5.4.3 简化版的Fragment
5.5 Shatter库
5.5.1 建立Shatter类
5.5.2 设计ShatterManager
5.5.3 分发生命周期
5.5.4 使用方式
5.6 总结
第6章 让alertDialog为我所用
6.1 Dialog
6.1.1 Dialog和Window
6.1.2 Show和Dismiss方法
6.2 alertDialog
6.2.1 alertController
6.2.2 alertDialog.Bulder
6.3 dialogFragment
6.3.1 Fragment和Dialog
6.3.2 Show和Dismiss方法
6.4 实际问题
6.4.1 无法弹出输入法
6.4.2 如何支持层叠弹窗
6.4.3 容易引起内存泄露
6.4.4 修改尺寸、背景和动画
6.4.5 点击后会自动关闭
6.4.6 在关闭或开启时出现崩溃
6.5 封装dialogFragment
6.5.1 用现成的alertParams
6.5.2 让Builder类支持继承
6.5.3 建立dialogFragment框架
6.6 easyDialog
6.6.1 基本用法
6.6.2 自定义一个Dialog
6.6.3 BottomSheetDialog
6.6.4 设置全局样式
6.6.5 支持动态样式
6.6.6 避免丢失监听器
6.7 可全局弹出的Dialog
6.8 总结
第7章 Gradle的使用技巧
7.1 全局配置
7.1.1 设定UTF-8
7.1.2 依赖Google仓库
7.1.3 支持Groovy
7.1.4 定义全局变量
7.1.5 配置Lint选项
7.2 操控Task
7.2.1 更改输出的APK的名字
7.2.2 更改AAR输出的位置
7.2.3 跳过AndroidTest
7.2.4 找出耗时的Task
7.2.5 抽离Task脚本
7.3 动态化
7.3.1 动态设置buildConfig
7.3.2 填充Manifest中的值
7.3.3 让buildType支持继承
7.3.4 让Flavor支持继承
7.3.5 内测版本用特定的Icon
7.3.6 不同渠道不同包名
7.3.7 自动填充版本信息
7.4 远程依赖
7.4.1 配置Maven仓库
7.4.2 依赖相关的API
7.4.3 组合依赖
7.4.4 依赖传递
7.4.5 动态版本号
7.4.6 强制版本号
7.4.7 exclude关键字
7.4.8 依赖管理
7.5 本地依赖
7.5.1 引用AAR
7.5.2 依赖Module/Jar
7.5.3 自建本地仓库
7.5.4 本地依赖React Native
7.5.5 重新打包第三方Jar
7.6 资源管理
7.7 总结
第8章 缩减APK的编译时间
8.1 分析项目现状
8.1.1 Gradle Profile
8.1.2 BuildTimeTracker
8.1.3 Dexcount GradlePlugin
8.1.4 经验小结
8.2 编译环境优化
8.2.1 升级硬件设备
8.2.2 升级软件
8.2.3 优化工程配置
8.2.4 配置Studio的可用内存
8.2.5 提升JVM的堆内存
8.2.6 开启并行编译
8.2.7 启用Demand模式
8.2.8 配置DexOptions
8.3 善用缓存
8.3.1 减少动态方法
8.3.2 硬编码BuildConfig和Res
8.3.3 拆分脚本
8.3.4 拆分代码
8.3.5 写死库的版本号
8.4 精简工程
8.4.1 差异化加载Plugin
8.4.2 使用WebP和SVG
8.4.3 精简语言和图片资源
8.4.4 善用no-op
8.4.5 Exclude无用库
8.4.6 删减Module
8.4.7 去掉MultiDex
8.4.8 删除无用的资源
8.5 综合技巧
8.5.1 构建开发时的Flavor
8.5.2 优化MultiDex
8.5.3 跳过无用的Task
8.5.4 关闭AAPT的图片优化
8.5.5 调试时关闭Crashlytics
8.5.6 谨慎使用AspectJ
8.6 多渠道打包工具
8.6.1 MultiChannelPackageTool
8.6.2 美团的Walle
8.6.3 腾讯的VasDolly
8.7 总结
第9章 APP终极瘦身实践
9.1 安装包的构成
9.1.1 Assets
9.1.2 Lib
9.1.3 Resources.arsc
9.1.4 META-INF
9.1.5 Res
9.1.6 Dex
9.2 优化Assets目录
9.2.1 删除无用的字体
9.2.2 减少IconFont的使用
9.2.3 动态下载资源
9.2.4 压缩资源文件
9.3 优化Lib目录
9.3.1 配置ABI Filters
9.3.2 根据CPU引入so
9.3.3 动态加载so
9.3.4 避免复制so
9.3.5 谨慎处理so
9.4 优化Resources.arsc
9.4.1 删除无用的映射
9.4.2 进行资源混淆
9.5 优化META-INF
9.5.1 MANIFEST.MF
9.5.2 CERT.SF
9.5.3 CERT.RSA
9.5.4 优化建议
9.6 优化Res目录
9.6.1 通过IDE删除无用资源
9.6.2 打包时剔除无用资源
9.6.3 删除无用的语言
9.6.4 控制Raw中的资源大小
9.6.5 减少Shape文件
9.6.6 减少Menu文件
9.6.7 减少Layout文件
9.6.8 动态下载图片
9.6.9 分目录放置图片
9.6.10 合理使用图片资源
9.6.11 丢弃特定的资源
9.6.12 开启严格模式
9.6.13 移除Lib库中的配置文件
9.7 优化图片资源
9.7.1 使用VectorDrawable
9.7.2 使用WebP
9.7.3 替换support库中的图
9.7.4 精简动画图片
9.7.5 复用相同的Icon
9.7.6 使用Tint
9.7.7 复用按压效果
9.7.8 通过旋转复用
9.8 优化Dex
9.8.1 分析Dex
9.8.2 利用Lint分析无用代码
9.8.3 删除R文件
9.8.4 启用ProGuard
9.8.5 使用拆分后的support库
9.8.6 尽量不用Mulitdex
9.8.7 使用更小库或合并现有库
9.8.8 根据环境依赖库
9.9 总结
第10章 编写针对性的TestCase
10.1 基础概念
10.1.1 什么代码应被测试
10.1.2 编写易于被测试的代码
10.1.3 测试框架的选型
10.2 逻辑测试
10.2.1 Junit测试
10.2.2 Mockito
10.2.3 Robolectric的使用
10.2.4 Espresso
10.3 集成测试网络层
10.3.1 编写网络层逻辑
10.3.2 建立测试对象
10.3.3 测试HTTP的异常情况
10.3.4 测试业务代码的正确性
10.3.5 用Interceptor模拟返回值
10.4 总结
第11章 Android Studio使用经验
11.1 调试篇
11.2 插件篇
11.2.1 统计相关
11.2.2 工具相关
11.3 总结
第12章 抓包工具Whistle实践
12.1 抓包工具
12.1.1 Charles
12.1.2 Fiddler
12.1.3 AnyProxy
12.1.4 Whistle
12.2 Whistle的安装和使用
12.2.1 安装和更新
12.2.2 查看Request和Response
12.2.3 代理技巧
12.2.4 过滤规则
12.3 Whistle的各项功能
12.3.1 替换域名
12.3.2 修改请求参数
12.3.3 修改返回值
12.3.4 模拟低网速的情形
12.3.5 查看WebView的Console
12.4 总结

前言: 前    言
开发者的焦虑
当今,Android和iOS手机无论在系统还是外形上都趋于雷同,App功能的差异也是微乎其微,移动端发展趋于缓慢已成为开发者的共识。移动开发者面对小程序、React Native、Flutter和Weex跨平台方案都显得焦躁不安,对于自己未来需要发展和生根的方向或多或少地会感到迷茫。
从市场的角度来说,公司缺少的并非基础的开发人员,更希望可以招聘到有大量实践经验的开发者。要进阶成为高级开发者,更需要阅读有深度、偏重实践的书籍。因此,我从2015年8月开始在GitHub上创建了“Android-Best-Practices”项目。这个项目的目的是将经实际验证的经验分享出来,力求达到“学完即用”的效果。在收到出版社的邀请后,我将相关的文章进行重写,同时新增大量内容,目的是让更多的开发者可以在短时间内得到更多的实际经验。
面对当前移动开发的生存环境,开发者的无力感会越发严重。作为移动应用开发者,应该了解与本职工作相关的知识,同时多涉猎其他邻域。如果你想快速巩固移动开发的知识后再投身到大前端中去,那么这本书很适合你。当阅读完本书的每一章后,你收获的是已经经过时间检验的知识,需要做的仅仅是去实践它、理解它。
写作风格
本书不会像其他书籍一样提供完整的代码下载,也不会花大量的篇幅分析源码。官方每隔几个月就会对源码的实现进行各种修改,而源码的设计思路则较为稳定。“授人以鱼不如授人以渔”,本书会将分析源码的过程提炼出来,主要阐述设计思路和关键的代码,甚至会为了可读性删除没必要的代码逻辑。这样做的目的是希望读者既可以快速理解源码的思路,又不会陷入代码的困境中。
本书内容
本书分为基础知识和工程优化两部分。基础知识部分讲述基础知识,读者可以找到合适的第三方库来提高业务开发速度;工程优化部分讲述工程实践方面的内容,仔细阅读此部分,可以提升自身内在的知识广度。
基础知识
在实际工程中,Android本身的API大多不会被直接使用,需要做二次封装和扩展。基础知识部分将基础的知识提升到一个实践的层面,不再讲述如何使用它们,而是讲如何通过封装这些API解决实际中的各种问题。
第1章 Java的反射是必知必会的内容,我们需要知道:
?    什么时候该用反射,Android中的反射和JDK中的反射有何不同?
?    反射低效的原因是什么,如何让其变得高效。反射的缓存该如何实现?
?    为什么Android P不让反射被@hide标记的API,其影响是什么?
?    做一个好的反射封装,应注意什么问题,这种封装的意义是什么?
第2章 Log是最简单的代码了,这里要讲的是日志的高级知识:
?    打印日志的时机是什么,什么日志应该留在Release版本中,这么做是否安全?
?    如何区分不同项目组的日志,如何避免日志间的相互干扰?
?    如何优化和封装日志,为什么说日志的分发功能是必须具备的?
?    微信是如何用MMAP提升日志的写盘成功率的,Xlog的设计目的是什么?
第3章 Intent是非常简单的对象,着重来讲:
?    如何封装和扩展Intent,为什么它只能存储基本对象,Intent的本质是什么?
?    如何在使用Intent时避免管理Intent的Key,动态下发Intent的思路是什么?
?    Intent的最大数据存放量是多少,该如何传递一个大对象?
?    Intent传递map对象时会有什么隐患,linkedHashMap可以直接存入Intent吗?
第4章 SharedPreferences作为基本的存储对象,需要了解如下知识点:
?    用SharedPreferences存储Java对象有什么问题,如何清理其缓存?
?    为什么拆分SharedPreferences后可以有效地减少内存的消耗?
?    SharedPreferences的线程、进程安全吗,它是如何引起ANR的?
?    如果将SharedPreferences中的数据放云端,该如何设计这种云同步的功能?
第5章 Fragment是开发者很容易出错的类,这里需要思考:
?    Fragment和Activity的生命周期是依次执行的吗?动态添加时的生命周期会如何执行?
?    Square不建议用Fragment,但不用Fragment的话,该如何模块化地管理UI呢?
?    Google公司提出的Lifecycle-Components和Fragment有关系吗?使用的是什么原理?
?    Fragment的嵌套和回退栈是一个难点,Jetpack中的Navigation是怎么简化它的?
工程优化
工具是开发者的武器,拥有一个好的武器可以战无不胜。在工程优化篇中,会提到如何利用工具和技巧帮助开发者优化项目。这部分的知识点较为零碎,但学起来并不难,很适合读者用碎片时间进行学习。
第6章  Dialog已经不推荐直接使用了,取而代之的是dialogFragment,关于它我们需要知道:
?    Dialog和alertDialog,alertDialog和dialogFragment是什么关系?
?    如何实现一个可以随时展示的全局弹窗,这对于App的任务系统有什么好处?
?    为什么说完全可以通过Style实现自定义的Dialog,Dialog的背景该如何编写?
?    alertDialog引起内存泄露和崩溃的原因是什么,在开发中该如何防御?
第7章 Gradle是常用工具,本章不准备讲它的基础知识,会直接介绍实际的例子:
?    如何自动化生成versionCode和versionName,Gradle还能做哪些自动化处理?
?    如何强制整个App的某个库均使用统一的版本号,如何更好地管理签名文件?
?    如何引入本地的Maven仓库,本地依赖的写法是什么,如何制作一个本地库?
?    如何使用Exclude Lib、Jar、Module等,对于产生类冲突的两个库该如何解决?
第8章 本章除讲述如何提升打Debug包的速度外,还介绍了如下内容:
?    为什么小范围的配置优化不如直接升级机器硬件来得快?
?    Google废弃compile()的原因是什么,新的依赖API有哪些,各有什么不同?
?    如何启用Gradle缓存,减少每次编译时的网络请求是提速的关键吗?
?    Android有几种签名方案,不同签名方案对应的多渠道打包策略有何不同?
第9章 每家公司都希望APK越来越小,本章探讨的是切实有效的瘦身策略,主要解惑的点为:
?    APK是由哪些文件组成的?APK和Zip是什么关系,签名相关的文件在哪个目录中?
?    瘦身开始前应该做哪些准备工作,为什么这个看似无关紧要的工作却很重要。
?    APK中的.9图应该放在哪个文件夹中,到底要不要做屏幕分辨率的适配。
?    ABI是什么意思,从云端下载so时需要注意什么问题,具体的可行性如何?
第10章 测试用例的编写一直被我们忽视,本章是一个入门级的内容,涵盖了如下的知识点:
?    mock的目的是什么,常见的mock框架有哪些,我们一般会如何进行技术选型?
?    测试分为逻辑测试和UI测试,哪个才是移动测试的重点?
?    如果想要在PC端直接测试Android的逻辑,目前最方便的测试框架是哪个?
?    网络层是一个典型的单元测试和集成测试的场景,该如何写好异步代码的测试用例?
第11章 本章在前半部分会讲与Debug相关的内容,后半部分将介绍一些极其好用的插件,阅读后你将知道:
?    如何在Debug时动态打Log,它和在代码中硬编码的Log有什么区别?
?    如何通过Debug工具检查内存泄露,怎样清楚地知道当前内存中的对象数目?
?    Facebook的Sonar是什么,这个平台可以带来怎样的想象力?
?    号称史上最强的数据库调试插件是什么,它真的值得购买吗,免费的替代品是什么?
第12章 使用代理工具是开发人员和测试人员的必会技能,本章会介绍Whistle的主要功能:
?    支持用正则制定过滤规则,返回的JSON能自动进行格式化。
?    支持为多个环境配置不同的代理,可以灵活地进行环境的切换。
?    可以直接编写mock规则,自动化地生成mock数据,并且能统一管理mock的values。
?    支持调试Webview,可以很方便地进行真机上H5页面的调试。
致谢
写书本身是一件很严肃的事情,尤其是技术方面的书籍。我是一个讨厌被限制的人,所以一直以来都是偷懒写“网文”,原本不愿意出版图书。下面要感谢一些朋友,没有他们我可能永远都无法迈出这一步,也无法得到一本真正属于自己的作品。
?    感谢博文观点团队的赏识和邀请,如果没有贵团队,我根本就不会有写书的想法和机会。
?    感谢本书的编辑陈晓猛,他的沟通能力和执行力是保障本书快速出版的关键。
?    感谢“不吃鱼同学”,要不是她的支持和鼓励,我就没有勇气开始做这件长达一年的事情。
?    感谢所有在微博关注我的朋友们,你们的支持和批评是鞭策我前进的动力。
?    感谢聚美、网易和美团的一些朋友们,你们给予了我很多的建议和灵感。
?    更要感谢即将开始阅读本书的你,这本书就是为你而写的,你的阅读是我最大的荣耀!
联系方式
如果你发现了本书的纰漏或是对内容有更好的建议,可以通过下列方式联系到我:
微博:@天之界线2010;
邮箱:developer-kale@foxmail.com;
QQ群:390272666;
GitHub:tianzhijiexian。