我们首先来看一个普通的Android Studio项目结构:
由图所示,我们开始由下往上说吧
第三方包
- 很多开源的library不是为移动网络环境编写,如果要集成,应该注意这点。同时了解是否应该去做针对移动环境下的迁移
- 不要为了简单的一两个功能而引入一个大而全的library,如果没有适合的library,可以考虑自己去实现
- 不要使用一些依赖注入框架(类似Guice或RoboGuice),的确是帮忙省了一些代码,但这些框架在初始化时候就把你的代码扫描进内存,而且这些mapping的内存会一直占用着
build.grade
- build.grade中可以开启Proguard功能,Proguard除了混淆代码外,还能做压缩,剔除一些不需要的代码
AndroidManifest.xml
- 在
application
标签下,有个android:largeHeap
属性,可以把它设置为true,这样可以通过getLargeMemoryClass()
来获取到一个更大的heap size。但不同的设备会有不同的限制,应该使用getMemoryClass()
获取真正实际大小 - 设置组件多进程使用,在组件声明中添加属性
android:process=":background"
即可运行于另一进程,但只有app需要在后台运行与前台一样的大量的任务的时候才合适使用(如音乐播放器)。使用不当会增加内存,而非减少
图片优化
- 当bitmap比设备分辨率大时,做适当的压缩
- 以其他色彩模式读取bitmap,如ARGB_4444(ALPHA_8/RGB_565/),不过KITKAT之后,使用这个模式系统会自动使用ARGB_8888去读取,详见Bitmap.Config枚举中对此模式的解释
bitmap.recycle()
在Android 3.0之后就不必主动调用了,图片像素存储已经放到heap中,会自动回收- 设置
Options.inPurgeable
和inInputShareable
:让系统能及时回收内存 - 设置图片Cache
- 使用WebP格式的图片
布局优化
- 善用Hierarchy View、Lint工具
- 如果层级关系较深,考虑使用RelativeLayout代替LinearLayout,但只有同样层级时,效率由高到低排序
FrameLayout > LinearLayout > RelativeLayout
总之尽量减少布局的层级 - 用一个compound drawable 替代一个包含 ImageView 和 TextView 的 LinearLayout 会更有效率
- 使用
merge
/include
/ViewStub
标签,特别是ViewStub
做懒加载 ViewStub
的一个缺陷是,它目前不支持使用merge
标签的 Layout- 避免overdraw,可以在开发者选项中debug GPU overdraw信息
代码优化(首要考虑的是选择合适的算法与数据结构)
- 协议Protocol buffers
- 注意代码”抽象”,”抽象”会导致更多的代码被加载到内存中,所以如果”抽象”没有为你的应用提供灵活性、可扩展性,就不要使用它了
- 使用SparseArray, SparseBooleanArray, 与 LongSparseArray这些优化过的容器,他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。相比之下HashMap更消耗内存
- 使用IntentService替代Service,避免service完成任务之后因为停止service失败而引起的内存泄漏
- 使用StringBuffer替换String做字符串拼接
- 需要取出子字符串时,使用substring方法。这个子串对象是和原string共享内部char[]空间
- 把多维的数据分解成一维的数组
- 不需要访问一个对象的值时,请保证这个方法是static类型的
- 常量声明为Static Final,因为final声明的常量进入了静态dex文件的域初始化部分,而非final的静态变量则放到class常量表中
- 类内部直接访问变量,避免内部的Getters/Setters,但如果使用了ProGuard,则ProGuard可以帮助做inline,两者的差异就比较小
- 对于ArrayList的遍历使用ArrayList.get(index),但对于其他实现了Iterable 接口集合,使用增强for循环(for(A a : Array))
- 避免使用float类型,它的存取速度是int类型的一半
- 谨慎使用Native函数。Native 代码是在你已经有本地代码,想把它移植到Android平台时有优势,而不是为了优化已有的Android Java代码使用
- 在没有JIT的设备上,使用一种确切的数据类型确实要比抽象的数据类型速度要更有效率,比如调用HashMap map要比调用Map map效率更高,但在有JIT之后效率没有太大差异
- 注意枚举,Enums的内存消耗通常是static constants的2倍
- 内部类访问外部类时,外部类对象使用package访问权限而非private权限。因为private权限时,内部类会在编译时生成静态方法,而通过方法访问变量总是比直接访问慢
- Traceview/dmtracedump,使用traceView测量的数据是未经过JIT优化的,实际运行效果比测量值要好一些
- systrace
其他:
- ANR
- 什么时候会穿ANR
1.1. 对输入事件(例如硬件点击或者屏幕触摸事件),5秒内都无响应
1.2. BroadReceiver不能够在10秒内结束接收到任务 - 解决办法
2.1. 使用worker线程做耗时任务(AsyncTask/IntentService/ThreadPool/Thread/Runnable/HandlerThread),注意(Runnable/Thread)调用Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);设置线程优先级,以减少创建的新线程和和UI线程之间的资源竞争
2.2. 从3.0开始,AsyncTask可以通过方法executeOnExecutor运行在自定义的线程池中
2.3. 线程池中Thread.interrupt()只能停止那些处于等待状态的线程,却不能中断那些占据CPU或者耗时的连接网络的任务。为了避免拖慢系统速度或造成系统死锁,在尝试执行耗时操作之前,应该测试当前是否存在处于挂起状态的中断请求,即
- 什么时候会穿ANR
if (Thread.interrupted()) {
return;
}
- ListView优化
- 复用convertView
- 使用ViewHolder
- 滑动过程中不进行图片加载,滑动结束后加载(设置OnScrollListener监听)
- 建议使用RecycleView
- 对最终的APK使用zip align
- 一些参考资料