Android一些常见的异常以及如何性能优化
- 一、ANR(Application Not Responding):应用程序无响应
- 1. Android中哪些操作是在主线程呢?
- 2. 造成ANR的主要原因:
- 3. 典型的ANR问题场景
- 4. ANR的定位和分析
- 5. 如何解决ANR
- 6. ANR的检测
- (1) StrictMode (代码检测)
- (2) BlockCanary(非侵入式性能监控函数库)
- 二、OOM(Out Of Memory: 内存溢出)
- 1. 内存溢出定义
- 2. 如何解决OOM
- (1) 有关bitmap
- (2)其他方法
- 三、Bitmap
- 四、UI卡顿
- 1. UI卡顿原理
- 2. UI卡顿原因分析
- 3. UI卡顿总结
一、ANR(Application Not Responding):应用程序无响应
参考: ANR产生的原因及其定位分析 ANR 全称Application Not Responding(应用程序无响应),一般页面卡顿时间超过(一般是5秒)一定时间就会出现ANR对话框。Logcat一般会发现ANR以及traces.txt等字样。出现ANR主要原因是因为我们在主线程中做了太多耗时操作。
1. Android中哪些操作是在主线程呢?
① Activity的所有生命周期回调都是执行在主线程中的。
② Service默认是执行在主线程中的。
③ BroadcastReceiver的onReceiver回调是执行在主线程中的。
④ 没有使用子线程的Looper的handler的handleMessage,post(Runnable)是执行在主线程中的。
⑤ AsyncTask的回调中除了doInBackground方法,其他几个方法(onPreExecute、publishProgress、onProgressUpdate、onPostExecute)都是执行在主线程中的。
2. 造成ANR的主要原因:
只有当应用程序的UI线程响应超时才会引起ANR,超时产生原因一般有两种:
(1)当前的事件没有机会得到处理,例如UI线程正在响应另外一个事件,当前事件由于某种原因被阻塞了。
(2)当前事件正在处理,但是由于耗时太长没能及时完成。
根据ANR产生的原因不同,超时事件也不尽相同,从本质上讲,产生ANR的原因有三种,大致可以对应到Android 中四大组件中的三个(activity/view,BroadcastReceiver和service)。
(1)KeyDispatchTimeOut类型
最常见的一种类型,原因是View的按键事件或触摸事件在特定的事件(5秒)内无法得到响应
(2)BroadcastTimeOut类型
原因是BroadcastReciver的onReceive()函数运行在了主线程中,在特定的事件(10秒)内无法完成处理
(3)ServiceTimeOut类型
比较少出现的一种类型,原因是Service的各个生命周期函数在特定时间(20秒)内无法完成处理
3. 典型的ANR问题场景
(1)场景一:
应用程序UI线程存在耗时操作,例如在UI线程中进行网络请求,数据库操作或者文件操作等,可能会导致UI线程无法及时处理用户输入等。当然在Android 4.0之后,如果在UI线程中进行网络操作,将会抛出NetworkOnMainTreadException异常。
(2)场景二:
应用程序的UI线程等待子线程释放某个锁,从而无法处理用户的输入。
(3)场景三:
耗时的动画需要大量的计算工作,可能会导致CPU负载过重。
4. ANR的定位和分析
当发生ANR时,可以通过结合Logcat日志和生成的位于手机内部存储的/data/anr/traces.txt文件进行分析和定位。
(1)Logcat日志信息
查看日志
(2)traces.txt日志信息
有助于问题已定位的信息主要内容
- 发生ANR的进程名称、ID,以及时间
- 堆内存信息
- 主工程基本信息
- 主线程的详细信息
- 线程的调度信息
- 线程的上下文信息
- 线程的堆栈信息
5. 如何解决ANR
为了避免开发中可能发生的ANR的问题,切记不要再主线程中做耗时操作。
① 使用AsyncTask处理耗时IO操作
② 使用Thread或者HandlerThread提供优先级
③ 使用handler来处理工作线程的耗时任务
④ Activity的onCreate和onResume回调中尽量避免耗时的代码
6. ANR的检测
可以借助一些工具来进行检测,从而更有效的避免ANR的引入。
(1) StrictMode (代码检测)
严格模式StrictMode (代码检测) 是Android SDK提供的一个用来检测代码是否存在违规操作的工具类,StrictMode 主要检测两大类问题。
a. 线程策略ThreadPolicy
检测可能存在的主线程耗时操作,解决这些检测到的问题能够减少应用发生的ANR的概率。需要注意的是我们只能在debug版本中使用它,发布到市场版本必须关掉。
b. 虚拟机策略VmPolicy
StrictMode使用
使用很简单,我们只需要在应用初始化的地方例如Application或者MainActivity类中onCreate方法中执行如下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
/**开启线程模式*/
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
/**开启虚拟机模式*/
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr);
}
调用detectAll表示检测所有的检测策略,我们也可以根据应用需求只开启一些策略。
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
/**开启线程模式,某些策略*/
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectCustomSlowCalls()
.penaltyLog()
.build());
/**开启虚拟机模式,某些策略*/
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedRegistrationObjects()
.penaltyLog()
.build());
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_anr);
}
(2) BlockCanary(非侵入式性能监控函数库)
BlockCanary是一个非侵入时的性能监控函数库,他的用法和LeakCanary类似,只不过LeakCanary检测内存泄漏,而BlockCanary主要用来监控应用主线程的卡顿,它的基本原理利用主线程的消息队列处理机制,通过对比消息分发开始和结束的事件点来判断是否超过设定的时间,如果是,则判断为主线程卡顿。
集成顺序:
a. build.gradle添加依赖
// 主线程卡顿监控依赖
implementation 'com.github.moduth:blockcanary-android:1.5.0'
// 如果仅在debug 包启用BlockCanary 进行卡顿监控和提示的话,这样写
// debugCompile 'com.github.moduth:blockcanary-android:1.5.0'
// releaseCompile 'com.github.moduth:blockcanary-no-op:1.5.0'
b. 在Application中调用
@Override
public void onCreate() {
super.onCreate();
/**初始化调用*/
BlockCanary.install(this,new AppBlockCanaryContext()).start();
}
public class AppBlockCanaryContext extends BlockCanaryContext{
/**
* 实现各种上下文,包括应用标识符、用户uid、网络类型、卡曼判断阈值、Log保存位置等
*/
}
二、OOM(Out Of Memory: 内存溢出)
1. 内存溢出定义
当前占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就会抛出Out of memory异常。
产生OOM问题:
- java堆内存益处
- 无足够连续内存空间
- FD(文件)数量超出限制(1024)
- 线程数量超出限制(1024)
- 虚拟内存不足
内存溢出和内存泄露的区别:
内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak最终会导致out of memory
2. 如何解决OOM
(1) 有关bitmap
1) 图片显示:当显示缩略图的时候不要调用网络请求加载大图,比如ListView在滑动的时候不要去调用网络请求,只有监听到ListView滑动停止时才加载大图显示到ListView上。
2)及时释放内存:主要释放C/C++代码部分的内存,Java代码部分区域的内存由GC Root进行回收,比如bitmap.recycle()指释放C部分内存。
3)图片压缩:通过bitmap的inSampleSize设置缩放比例。
4)inBitmap属性(图片加载<第六篇>:图片优化之inBitmap)
5)捕获异常:捕获的是error输出(out of error)。
(2)其他方法
① ListView:ConvertView/LRU
② 避免在onDraw方法里面执行对象的创建
③ 谨慎使用多线程
三、Bitmap
1. recycle
2. LRU
LRU:最近最少未使用的缓存对象会被消除队列
LruCache如何实现:内部是利用一个LinkedHashMap来实现,里面提供了get、put方法来完成缓存的添加和获取操作,当缓存满的时候Lru算法会利用trimToSize()方法把较早的缓存对象移除并添加新的缓存对象
3. 计算inSampleSize
4. 缩略图 --injustDecodeBounds属性
5. 三级缓存(网络、本地、内存三级缓存)
当App首次请求一张图片时,它会从网络中请求图片的加载,当图片加载成功之后,它会把bitmap保存到本地和内存各一份,当我们再次通过app请求相同url图片时,它就会直接从内存或本地(SD)中去读取,而不用走网络,减少用户流量。
Bitmap如何优化:
BitmapFactory.Options
- inPreferredConfig
设置图片解码后的像素格式,如ARGB_8888/RGB_565
ARGB_8888:表示A占8个bit,R占8个bit,G占8个bit,B占个bit,即1个像素点占用4 * 8 /8 = 4个字节
而RGB_565:表示16 / 8 = 2个字节
优化点:减少每个像素点占用的字节数 - inSampleSize
设置图片的采样率进行图片的缩放显示。
比如值为2,则加载图片的宽高是原来的1/2,整个图片所占内存的大小就是原图的1/4
优化点:缩放图片的宽高,即减少整个图片的像素点
三级缓存的原理就是当App需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还读取不到就到网络异步读取,读取成功之后再缓存到内存和本地中。
四、UI卡顿
1. UI卡顿原理
60fps --> 16ms (1000/60)
overdraw
原理: 性能问题主要根源来自Android系统的渲染性能做了太多耗时操作,有可能是Layout太复杂,也可能是Layout UI项重叠了其他Layout布局,还有可能动画执行次数过多。
2. UI卡顿原因分析
① 人为在UI线程中做轻微耗时操作,导致UI线程卡顿
② 布局Layout过于复杂,无法在16ms内完成渲染
③ 同一时间动画执行的次数过多,导致CPU或GPU负载过重
④ View过度绘制,导致某些像素在同一帧时间内绘制多次,从而使CPU或GPU负载过重
⑤ View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染
⑥ 内存频繁触发GC过多,导致暂时阻塞渲染操作
⑦ 冗余资源及逻辑等导致加载和执行缓慢
⑧ ANR
3. UI卡顿总结
1)布局优化:相同布局用 include
2)列表及Adapter优化
3)背景及图片等内存分配优化
4)避免ANR