Android一些常见的异常以及如何性能优化

一、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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值