一、性能优化
1、UI 优化
- 合理选择
RelativeLayout
、LinearLayout
、FrameLayout
,RelativeLayout
会让子View
调用2次onMeasure
,而且布局相对复杂时,onMeasure
相对比较复杂,效率比较低,LinearLayout
在weight > 0
时也会让子View
调用2次onMeasure
,LinearLayout weight
测量分配原则; - 使用标签
include
、merge
,使用ViewStub
; - 减少布局层级,可以通过手机开发者选项 > GPU 过渡绘制查看,一般层级控制在4层以内,超过5层时需要考虑是否重新排版布局;
- 自定义
View
时,重写onDraw()
方法,不要在该方法中新建对象,否则容易触发GC
,导致性能下降; - 使用
ListView
时需要复用contentView
,并使用Holder
减少findViewById
加载View
; - 去除不必要背景,
getWindow().setBackgroundDrawable(null)
; - 使用
TextView
的leftDrawabel / rightDrawable
代替ImageView + TextView
布局。
2、内存优化
主要为了避免
OOM
和频繁触发到GC
导致性能下降。(可以使用LeakCanary检测内存泄露)
Bitmap.recycle()
,Cursor.close
,inputStream.close()
;- 大量加载
Bitmap
时,根据View
大小加载Bitmap
,合理选择inSampleSize
,RGB_565
编码方式;使用LruCache
缓存; - 使用静态内部类 +
WeakReference
代替内部类,如Handler
、线程、AsyncTask
; - 使用线程池管理线程,避免线程的新建;
- 使用单例持有
Context
,需要记得释放,或者使用全局上下文; - 静态集合对象注意释放;
- 属性动画造成内存泄露;
- 使用
webView
,在Activity.onDestory
需要移除和销毁,webView.removeAllViews()
和webView.destory()
。
3、响应速度优化
Activity
如果5秒之内无法响应屏幕触碰事件和键盘输入事件,就会出现ANR
,而BroadcastReceiver
如果10秒之内还未执行操作也会出现ANR
,Server
20秒会出现ANR
为了避免ANR
,可以开启子线程执行耗时操作,但是子线程不能更新UI
,因此需要Handler
消息机制、AsyncTask
、IntentService
进行线程通信。
4、其他性能优化
- 常量使用
static final
修饰; - 使用
SparseArray
代替HashMap
; - 使用线程池管理线程;
ArrayList
遍历使用常规for
循环,LinkedList
使用foreach
;- 不要过度使用枚举,枚举占用内存空间比整型大;
- 字符串的拼接优先考虑
StringBuilder
和StringBuffer
; - 数据库存储是采用批量插入 + 事务。
二、ANR 的常见原因
- 耗时的网络访问;
- 大量的数据读写;
- 数据库操作;
- 硬件操作(比如
camera
); - 调用
thread
的join()
方法、sleep()
方法、wait()
方法或者等待线程锁的时候; service binder
的数量达到上限;system server
中发生WatchDog ANR
;service
忙导致超时无响应;- 其他线程持有锁,导致主线程等待超时;
- 其它线程终止或崩溃导致主线程一直等待。
三、内存泄漏的场景和解决办法
1、非静态内部类的静态实例
非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类
2、多线程相关的匿名内部类和非静态内部类
匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务
3、Handler 内存泄漏
Handler
导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler
内部message
是被存储在MessageQueue
中的,有些message
不能马上被处理,存在的时间会很长,导致handler
无法被回收,如果handler
是非静态的,就会导致它的外部类无法被回收,解决办法是:1.使用静态handler
,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息
4、Context 导致内存泄漏
根据场景确定使用
Activity 的
Context还是
Application的
Context,因为二者生命周期不同,对于不必须使用
Activity的
Context的场景(
Dialog),一律采用
Application的
Context,单例模式是最常见的发生此泄漏的场景,比如传入一个
Activity的
Context` 被静态类引用,导致无法回收
5、静态 View 导致泄漏
使用静态
View
可以避免每次启动Activity
都去读取并渲染View
,但是静态View
会持有Activity
的引用,导致无法回收,解决办法是在Activity
销毁的时候将静态View
设置为null
(View
一旦被加载到界面中将会持有一个Context
对象的引用,在这个例子中,这个context
对象是我们的Activity
,声明一个静态变量引用这个View
,也就引用了activity
)
6、WebView 导致的内存泄漏
WebView
只要使用一次,内存就不会被释放,所以WebView
都存在内存泄漏的问题,通常的解决办法是为WebView
单开一个进程,使用AIDL
进行通信,根据业务需求在合适的时机释放掉
7、资源对象未关闭导致
如
Cursor
,File
等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null
8、集合中的对象未清理
集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的
9、Bitmap 导致内存泄漏
bitmap
是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap
对象
10、监听器未关闭
很多需要
register
和unregister
的系统服务要在合适的时候进行unregister
,手动添加的listener
也需要及时移除
四、如何避免OOM?
1、使用更加轻量的数据结构:如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,因为它需要额外的实例对象来记录Mapping操作,SparseArray更加高效,因为它避免了Key Value的自动装箱,和装箱后的解箱操作
2、枚举的使用,可以用静态常量或者注解@IntDef替代
3、Bitmap优化:
- a、尺寸压缩:通过InSampleSize设置合适的缩放
- b、颜色质量:设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
- c、
inBitmap
:使用inBitmap
属性可以告知Bitmap
解码器去尝试使用已经存在的内存区域,新解码的Bitmap
会尝试去使用之前那张Bitmap
在Heap
中所占据的pixel data
内存区域,而不是去问内存重新申请一块区域来存放Bitmap
。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4
之前只能重用相同大小的Bitmap
的内存,而Android 4.4
及以后版本则只要后来的Bitmap
比之前的小即可。使用inBitmap
参数前,每创建一个Bitmap
对象都会分配一块内存供其使用,而使用了inBitmap
参数后,多个Bitmap
可以复用一块内存,这样可以提高性能