Android开发优化宝典

时间:2021-05-07 21:06:43

I. 网络相关

  • http头信息带Cache-Control域 确定缓存过期时间 防止重复请求
  • 直接用IP直连,不用域名,策略性跟新本地IP列表。 – DNS解析过程耗时在百毫秒左右,并且还有可能存在DNS劫持。
  • 图片、JS、CSS等静态资源,采用CDN(当然如果是使用7牛之类的服务就已经给你搭建布置好了)
  • 全局图片处理采用漏斗模型全局管控,所请求的图片大小最好依照业务大小提供/最大不超过屏幕分辨率需要,如果请求原图,也不要超过GL10.GL_MAX_TEXTURE_SIZE
  • 全局缩略图直接采用webp,在尽可能不损失图片质量的前提下,图片大小与png比缩小30% ~ 70%
  • 如果列表里的缩略图服务器处理好的小图,可以考虑直接在列表数据请求中,直接以base64在列表数据中直接带上图片(国内还比较少,海外有些这种做法,好像web端比较常见)
  • 轮询或者socket心跳采用系统AlarmManager提供的闹钟服务来做,保证在系统休眠的时候cpu可以得到休眠,在需要唤醒时可以唤醒(持有cpu唤醒锁)
  • 可以通过将零散的网路的请求打包进行一次操作,避免过多的无线信号引起电量消耗。

1. 传输数据格式选择

  • 如果是基本需要全量数据的,考虑使用Protobuffers (序列化反序列化性能高于json)
  • 如果传输回来的数据不需要全量读取,考虑使用Flatbuffers (序列化反序列化几乎不耗时,耗时是在读取对象时(就这一部分如果需要优化,可以参看Flatbuffer Use Optimize

2. 输入流

使用具有缓存策略的输入流

建议替换为
InputStream BufferedInputStream
Reader BufferedReader

II. 数据结构

如果已知大概需要多大,就直接给初始大小,减少扩容时额外开销。

1. List

ArrayList

里面就一数组,内存小,有序取值快,扩容效率低

LinkedList

里面就一双向链表,内存大,随机插入删除快,扩容效率高。

2. Hash

HashSet

里面就一个HashMap,用key对外存储,目的就是不允许重复元素。

ConcurrentHashMap

线程安全,采用细分锁,锁颗粒更小,并发性能更优

Collections.synchronizedMap

线程安全,采用当前对象作为锁,颗粒较大,并发性能较差。

3. Int作为Key的Map

针对该特性进行了优化,采用二分法查找,简单数组存储。

SparseArraySparseBooleanArraySparseIntArray

III. 数据库相关

建多索引的原则: 哪个字段可以最快的减少查询结果,就把该字段放在最前面

无法使用索引的情况

  • 操作符BETWEENLIKEOR
  • 表达式
  • CASE WHEN

不推荐

  • 不要设计出索引是其他索引的前缀(没有意义)
  • 更新时拒绝直接全量更新,要更新哪列就put哪列的数据
  • 如果最频繁的是更新与插入,别建很多索引 (原本表就很小就也没必要建)
  • 拒绝用大字符串创建索引
  • 避免建太多索引,查询时可能就不会选择最好的来执行

推荐

  • 多使用整型索引,效率远高于字符串索引
  • 搜索时使用SQL参数("?", parameter)代替字符串拼接(底层有特殊优化与缓存)
  • 查询需要多少就limit多少(如判断是否含有啥,就limit 1就行了嘛)
  • 如果出现很宽的列(如blob类型),考虑放在单独表中(在查询或者更新其他列数据时防止不必要的大数据i/o影响性能)

IV. JNI抉择

Android JVM相关知识,可参看: ART、Dalvik

Android JNI、NDK相关知识,可参看: NDK

JNI不一定显得更快,有些会更慢。

特点: 不用在虚拟机的框子下写代码

  • 可以调用更底层的高性能的代码库 – Good
  • 如果是Dalvik,将省去了由JIT编译期转为本地代码的这个步骤。 – Good
  • Java调用JNI的耗时较Java调用Java肯定更慢,虽然随着JDK版本的升级,差距已经越来越小(JDK1.6版本是5倍Java调用Java方法的耗时) – Bad
  • 内存不在Java Heap,没有OOM风险,有效减少gc。 – Good

一些重要的参数之类,也可以考虑放在Native层,保证安全性。参考: Android应用程序通用自动脱壳方法研究

V. 多进程抉择

360 17个进程: 360手机卫士 Android开发 InfoQ视频 总结

  • 充分独立,解耦部分
  • 大内存(如临时展示大量图片的Activity)、无法解决的crash、内存泄漏等问题,考虑通过独立进程解决
  • 独立于UI进程,需要在后台长期存活的服务(参看Android中线程、进程与组件的关系)
  • 非己方第三方库(无法保证稳定、性能等问题,并且独立组件),可考虑独立进程

最后,多进程存在的两个问题: 1. 由于进程间通讯或者首次调起进程的消耗等,带来的cpu、i/o等的资源竞争。2. 也许对于部分同事来说,会还有可读性问题吧,毕竟多了层IPC绕了点。

VI. UI层面

相关深入优化,可参看Android绘制布局相关

对于卡顿相关排查推荐参看: Android性能优化案例研究(上)Android性能优化案例研究(下)

  • 减少不必要的不透明背景相互覆盖,减少重绘,因为GPU不得不一遍又一遍的画这些图层
  • 保证UI线程一次完整的绘制(measure、layout、draw)不超过16ms(60Hz),否则就会出现掉帧,卡顿的现象
  • 在UI线程中频繁的调度中,尽量少的对象创建,减少gc等。
  • 分步加载(减少任务颗粒)、预加载、异步加载(区别出耗时任务,采用异步加载)

VII. 库推荐

可以参考Falcon Pro作者的推荐: Falcon Pro 3如何完成独立开发演讲分析

1. 代码编写习惯

RxJava (响应式编程,代码更加简洁,异步处理更快快捷、异常处理更加彻底、数据管道理念)

相关了解可以参看: RxJava

2. 图片加载:

3. 网络底层库:

Okhttp: 默认gzip、缓存、安全等

4. 网络基层:

Retrofit: 非常好用的REST Client,结合RxJava简单API实现、类型安全,简单快捷

5. 数据库层:

Realm: 效率极高(Falcon Pro 3的作者Joaquim用了该库以后,所有数据库操作都放到了UI线程)(基于TightDB,底层C++闭源,Java层开源,简单使用,性能远高于SQLite等)

6. Crash上报:

Fabric: 全面的信息(新版本还支持JNI Crash获取和上报)、稳定的数据、及时的通知、强大的反混淆(其实在混淆后有上传mapping)

7. 内存泄漏自动化检测

LeakCanary: 自动化泄漏检测与分析 ( 可以看看这个LeakCanary使用总结Leakcanary Square的一款Android/Java内存泄漏检测工具)

8. 其他

VIII. 内存泄漏相关

  • 无法解决的泄漏(如系统底层引起的)移至独立进程(如2.x机器存在webview的内存泄漏)
  • 大图片资源/全屏图片资源,要不放在assets下,要不放在nodpi下,要不都带,否则缩放会带来额外耗时与内存问题
  • 4.x在AndroidManifest中配置largeHeap=true,一般dvm heep最大值可增大50%以上。
  • Activity#onDestory以后,遍历所有View,干掉所有View可能的引用(通常泄漏一个Activity,连带泄漏其上的View,然后就泄漏了大于全屏图片的内存)。
  • 万金油: 静态化内部类,使用WeakReference引用外部类,防止内部类长期存在,泄漏了外部类的问题。

图片Decode

  • 全局统一BitmapFactory#decode出口,捕获此处decode oom,控制长宽(小于屏幕分辨率大小 )
  • 如果采用RGB_8888 oom了,尝试RGB_565(相比内存小一半以上(wh2(bytes)))
  • 如果还考虑2.x机器的话,设置BitmapFactory#optionsInNativeAlloc参数为true,此时decode的内存不会上报到dvm中,便不会oom。

IX. 编译与发布

  • 考虑采用DexGuard,或ProGuard结合相关资源混淆来提高安全与包大小,参考: DexGuard、Proguard、Multi-dex
  • 结合Gradle、Gitlab-CI 与Slack(Incoming WebHooks),快速实现,打相关git上打相关Tag,自动编相关包通知Slack。
  • 结合Gitlab-CI与Slack(Incoming WebHooks),快速实现,所有的push,Slack快速获知。
  • 结合Gradle中Android提供的productFlavors参数,定义不同的variations,快速批量打渠道包

X. 其他

  • final能用就用(高效: 编译器在调用final方法时,会转入内嵌机制)
  • 懒预加载,如简单的ListView、RecyclerView等滑动列表控件,停留在当前页面的时候,可以考虑直接预加载下个页面所需图片
  • 智能预加载,通过权重等方式结合业务层面,分析出哪些更有可能被用户浏览使用,然后再在某个可能的时刻进行预加载。如,进入朋友圈之前通过用户行为,智能预加载部分原图。
  • 做好有损体验的准备,在一些无法避免的问题面前做好有损体验(如,非UI进程crash,可以自己解决就不要让用户感知,或者UI进程crash了,做好场景恢复)
  • 做好各项有效监控:crash(注意还有JNI的)、anr(定期扫描文件)、掉帧(绘制监控、activity生命周期监控等)、异常状态监控(本地Log根据需要不同级别打Log并选择性上报监控)等
  • 文件存储推荐放在/sdcard/Android/data/[package name]/里(在应用卸载时,会随即删除)(Context#getExternalFilesDir()),而非/sdcard/根目录建文件夹(节操问题)
  • 通过gradle的shrinkResourcesminifyEnabled参数可以简单快速的在编包的时候自动删除无用资源
  • 由于resources.arsc在api8以后,aapt中默认采用UTF-8编码,导致资源中大都是中文的resources.arsc相比采用UTF-16编码更大,此时,可以考虑aapt中指定使用UTF-16
  • 谷歌建议,大于10M的大型应用考虑安装到SD卡上: App Install Location
  • 当然运维也是一方面: Optimize Your App
  • 在已知并且不需要栈数据的情况下,就没有必要需要使用异常,或创建Throwable生成栈快照是一项耗时的工作。