Android开发 - 性能优化

优质
小牛编辑
139浏览
2023-12-01

ANR

ANR全称Application Not Responding,意思就是程序未响应。

出现场景

  • 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
  • 主线程中存在耗时的计算
  • 主线程中错误的操作,比如Thread.wait或者Thread.sleep等

Android系统会监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框

  • 应用在5秒内未响应用户的输入事件(如按键或者触摸)
  • BroadcastReceiver未在10秒内完成相关的处理

如何避免

基本的思路就是将IO操作在工作线程来处理,减少其他耗时操作和错误操作

  • 使用AsyncTask处理耗时IO操作。
  • 使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同。
  • 使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
  • ActivityonCreateonResume回调中尽量避免耗时的代码
  • BroadcastReceiveronReceive代码也要尽量减少耗时,建议使用IntentService处理。

如何改善

通常100到200毫秒就会让人察觉程序反应慢,为了更加提升响应,可以使用下面的几种方法

  • 如果程序正在后台处理用户的输入,建议使用让用户得知进度,比如使用ProgressBar控件。
  • 程序启动时可以选择加上欢迎界面,避免让用户察觉卡顿。
  • 使用SystraceTraceView找出影响响应的问题。

如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。

OOM

在实践操作当中,可以从四个方面着手减小内存使用,首先是减小对象的内存占用,其次是内存对象的重复利用,然后是避免对象的内存泄露,最后是内存使用策略优化。

减小对象的内存占用

  • 使用更加轻量级的数据结构:例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,HashMap都显示效率低下,更占内存。另外,SparseArray更加高效在于,避免了对key与value的自动装箱,并且避免了装箱后的解箱

  • 避免使用Enum:在Android中应该尽量使用int来代替Enum,因为使用Enum会导致编译后的dex文件大小增大,并且使用Enum时,其运行时还会产生额外的内存占用。

  • 减小Bitmap对象的内存占用

    • inBitmap:如果设置了这个字段,Bitmap在加载数据时可以复用这个字段所指向的bitmap的内存空间。但是,内存能够复用也是有条件的。比如,在Android 4.4(API level 19)之前,只有新旧两个Bitmap的尺寸一样才能复用内存空间。Android 4.4开始只要旧 Bitmap 的尺寸大于等于新的 Bitmap 就可以复用了

    • inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

    • decode format:解码格式,选择ARGB_8888 RBG_565 ARGB_4444 ALPHA_8,存在很大差异。

      ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位
      ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位
      RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位
      ALPHA_8:每个像素占四位,只有透明度,没有颜色。

  • 使用更小的图片:在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。

内存对象的重复使用

大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码的时候显式的在程序里面去创建对象池,然后处理好复用的实现逻辑,要么就是利用系统框架既有的某些复用特性达到减少对象的重复创建,从而减少内存的分配与回收。

  • 复用系统自带资源:Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

  • ListView ViewHodler

  • Bitmap对象的复用:在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。

  • inBitmap使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。

    • 使用inBitmap,在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。
    • 新申请的bitmap与旧的bitmap必须有相同的解码格式
  • 避免在onDraw方法里面执行对象的创建:类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

  • StringBuilder:在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

避免内存泄漏

  • 内部类引用导致Activity的泄漏:最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
  • Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏
  • 考虑使用Application Context而不是Activity Context
  • 注意临时Bitmap对象的及时回收
  • 注意监听器的注销
  • 注意缓存容器中的对象泄漏:不使用的对象要将引用置空。
  • 注意Cursor对象是否及时关闭

内存优化策略

  • 综合考虑设备内存阈值与其他因素设计合适的缓存大小

  • onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用情况,通常来说,当所有的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。

  • onTrimMemory():Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用

  • 资源文件需要选择合适的文件夹进行存放:例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下

  • 谨慎使用static对象

  • 优化布局层次,减少内存消耗

  • 使用FlatBuffer等工具序列化数据

  • 谨慎使用依赖注入框架

  • 使用ProGuard来剔除不需要的代码

卡顿优化

导致Android界面滑动卡顿主要有两个原因:

  • UI线程(main)有耗时操作

  • 视图渲染时间过长,导致卡顿

众所周知,界面的流畅度主要依赖FPS这个值,这个值是通过(1s/渲染1帧所花费的时间)计算所得,FPS值越大视频越流畅,所以就需要渲染1帧的时间能尽量缩短。正常流畅度的FPS值在60左右,即渲染一帧的时间不应大于16 ms

如果想让应用流畅运行 :

  • 不要阻塞UI线程;
  • 不要在UI线程之外操作UI;
  • 减少UI嵌套层级

针对界面切换卡顿,一般出现在组件初始化的地方。屏幕滑动卡顿,ui嵌套层级,还有图片加载,图片的话,滑动不加载,监听scrollListener