安卓性能优化、ANR、内存泄漏等常见问题总结

2,833 阅读6分钟

一、性能优化

1、UI 优化
  • 合理选择 RelativeLayoutLinearLayoutFrameLayoutRelativeLayout 会让子 View 调用2次 onMeasure ,而且布局相对复杂时, onMeasure 相对比较复杂,效率比较低, LinearLayoutweight > 0 时也会让子 View 调用2次 onMeasureLinearLayout weight 测量分配原则;
  • 使用标签 includemerge ,使用 ViewStub
  • 减少布局层级,可以通过手机开发者选项 > GPU 过渡绘制查看,一般层级控制在4层以内,超过5层时需要考虑是否重新排版布局;
  • 自定义 View 时,重写 onDraw() 方法,不要在该方法中新建对象,否则容易触发 GC ,导致性能下降;
  • 使用 ListView 时需要复用 contentView ,并使用 Holder 减少 findViewById 加载 View
  • 去除不必要背景,getWindow().setBackgroundDrawable(null)
  • 使用 TextViewleftDrawabel / rightDrawable 代替 ImageView + TextView 布局。
2、内存优化

主要为了避免 OOM 和频繁触发到 GC 导致性能下降。(可以使用LeakCanary检测内存泄露)

  • Bitmap.recycle()Cursor.closeinputStream.close()
  • 大量加载 Bitmap 时,根据 View 大小加载 Bitmap ,合理选择 inSampleSizeRGB_565 编码方式;使用 LruCache 缓存;
  • 使用静态内部类 + WeakReference 代替内部类,如 Handler 、线程、 AsyncTask
  • 使用线程池管理线程,避免线程的新建;
  • 使用单例持有 Context ,需要记得释放,或者使用全局上下文;
  • 静态集合对象注意释放;
  • 属性动画造成内存泄露;
  • 使用 webView ,在 Activity.onDestory 需要移除和销毁, webView.removeAllViews()webView.destory()
3、响应速度优化

Activity 如果5秒之内无法响应屏幕触碰事件和键盘输入事件,就会出现 ANR ,而 BroadcastReceiver 如果10秒之内还未执行操作也会出现 ANRServer 20秒会出现 ANR 为了避免 ANR ,可以开启子线程执行耗时操作,但是子线程不能更新 UI ,因此需要 Handler 消息机制、 AsyncTaskIntentService 进行线程通信。

4、其他性能优化
  • 常量使用 static final 修饰;
  • 使用 SparseArray 代替 HashMap
  • 使用线程池管理线程;
  • ArrayList 遍历使用常规 for 循环, LinkedList 使用 foreach
  • 不要过度使用枚举,枚举占用内存空间比整型大;
  • 字符串的拼接优先考虑 StringBuilderStringBuffer
  • 数据库存储是采用批量插入 + 事务。

二、ANR 的常见原因

  • 耗时的网络访问;
  • 大量的数据读写;
  • 数据库操作;
  • 硬件操作(比如 camera );
  • 调用 threadjoin() 方法、 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还是ApplicationContext,因为二者生命周期不同,对于不必须使用ActivityContext的场景(Dialog),一律采用ApplicationContext,单例模式是最常见的发生此泄漏的场景,比如传入一个ActivityContext` 被静态类引用,导致无法回收

5、静态 View 导致泄漏

使用静态 View 可以避免每次启动 Activity 都去读取并渲染 View ,但是静态 View 会持有 Activity 的引用,导致无法回收,解决办法是在 Activity 销毁的时候将静态 View 设置为 nullView 一旦被加载到界面中将会持有一个 Context 对象的引用,在这个例子中,这个 context 对象是我们的 Activity ,声明一个静态变量引用这个 View ,也就引用了 activity

6、WebView 导致的内存泄漏

WebView 只要使用一次,内存就不会被释放,所以 WebView 都存在内存泄漏的问题,通常的解决办法是为 WebView 单开一个进程,使用 AIDL 进行通信,根据业务需求在合适的时机释放掉

7、资源对象未关闭导致

CursorFile 等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为 null

8、集合中的对象未清理

集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的

9、Bitmap 导致内存泄漏

bitmap 是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的 bitmap 对象

10、监听器未关闭

很多需要 registerunregister 的系统服务要在合适的时候进行 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 会尝试去使用之前那张 BitmapHeap 中所占据的 pixel data 内存区域,而不是去问内存重新申请一块区域来存放 Bitmap 。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在 Android 4.4 之前只能重用相同大小的 Bitmap 的内存,而 Android 4.4 及以后版本则只要后来的 Bitmap 比之前的小即可。使用 inBitmap 参数前,每创建一个 Bitmap 对象都会分配一块内存供其使用,而使用了 inBitmap 参数后,多个 Bitmap 可以复用一块内存,这样可以提高性能
4、StringBuilder替代String: 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”
5、避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC甚至内存抖动
6、减少内存泄漏也是一种避免OOM的方法