文章目录
ANR 的模拟
ANR 的模拟,需要两步:
- 创造 ANR 的环境
- 触发 ANR
一、创造 ANR 的环境
产生 ANR 的环境是主线程阻塞
,我们只要造成主线程阻塞即可。
可通过以下方式来造成主线程阻塞。
1.1 sleep
public void sleepTest() {
SystemClock.sleep(100000);
}
1.2 wait
public void waitTest() {
String s = "";
synchronized (s) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.3 synchronized
public void synchronizedTest() {
new Thread(new Runnable() {
@Override
public void run() {
synchronizedInThread();
}
}).start();
runOnUiThread(new Runnable() {
@Override
public void run() {
synchronizedInMain();
}
});
}
public synchronized void synchronizedInThread() {
SystemClock.sleep(30000);
}
public synchronized void synchronizedInMain() {
}
1.4 staticSynchronized
public void staticSynchronizedTest() {
new Thread(new Runnable() {
@Override
public void run() {
Util.get();
}
}).start();
Util.get();
}
public class Util {
public static synchronized void get() {
SystemClock.sleep(100000);
}
}
1.5 无限循环
public void forTest() {
for (; ; ) {
}
}
二、触发 ANR
ANR 的触发点有:触摸事件
、Service
、BroadcastReceiver
、ContentProvider
。
我们在页面设置一个按钮,点击它去产生一个 ANR。以下以 sleepTest
为例去创造 ANR 环境,第一节中所述其他方式与其效果一致。
(测试机型:mix2,Android 8.0)
2.1 触摸事件
/**
* 点击后,阻塞主线程,然后进行用户操作
*
* 连续点击空白处多次(mix2 上需4次),10s 左右有 ANR 的log,再过 10s 左右弹框
* 或点击返回键(mix2 上只需1次),10s 左右会有 ANR 的 log,再过 10s 左右弹框
*/
public void produceANR(View view) {
sleepTest();
}
2.2 Service
2.2.1 Service 启动超时
/**
* 点击后,阻塞主线程,再启动一个 Service
*
* 20s 左右会有 ANR 的log,再过 10s 左右有 ANR 的弹框
*/
public void produceANRByService(View view) {
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000);
Intent intent = new Intent(AnrActivity.this, MyService.class);
startService(intent);
}
}).start();
sleepTest();
}
2.2.2 Service 处理超时
/**
* 点击后,启动一个 Service,在 Service 内阻塞主线程
*
* 20s 左右会有 ANR 的log,再过 10s 左右有 ANR 的弹框
*/
public void produceANRByService(View view) {
Intent intent = new Intent(AnrActivity.this, MyService.class);
startService(intent);
}
public class MyService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
sleepTest();
}
}
2.2.3 对比
要注意,以上两种情况是不一样的,一个是 Service 外出了问题,一个是 Service 内出了问题。
看一下两种情况的 log:
启动超时 log:
处理超时 log:
可以发现,两个 ANR 的触发点是一致的,都是 Service。
再看一下两种情况的 traces.txt:
启动超时 traces:
处理超时 traces:
可以发现,两个 ANR 的产生环境是不一样的,一个是在启动 Service 的 Activity 里阻塞,一个是在 Service 自己的生命周期中阻塞。
所以遇到 log 中的 service(或 Receiver)ANR,需要通过 traces 来进一步判断是 Service 自己的问题还是外部环境的问题。
2.3 BroadcastReceiver
2.3.1 Receiver 接收超时
/**
* 点击后,阻塞主线程,注册并发送一个自定义 BroadCast
*
* 发送广播后,60s 左右有 ANR 的 log,再过 10s 左右弹框(自定义广播默认是后台广播)
*/
public void produceANRByOwnBroadCast(View view) {
IntentFilter filter = new IntentFilter();
filter.addAction("anr_test");
registerReceiver(registerReceiver, filter);
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
Intent intent = new Intent("anr_test");
sendBroadcast(intent);
}
}).start();
sleepTest();
}
/**
* 点击后,阻塞主线程,注册一个系统 BroadCast (如 ACTION_TIME_TICK)
*
* 系统发送广播后,10s 左右有 ANR 的 log,再过 10s 左右弹框
*/
public void produceANRBySystemBroadCast(View view) {
IntentFilter filter = new IntentFilter();
filter.addAction("anr_test");
filter.addAction(Intent.ACTION_TIME_TICK);
registerReceiver(registerReceiver, filter);
}
前台广播超时时间为 10s,后台广播超时时间为 60s。自定义广播默认是后台广播,所以会在 60s 后产生 ANR,当通过 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
将其设置为前台广播后,那也会在 10s 后出现 ANR。
2.3.2 Receiver 处理超时
/**
* 点击后,注册一个 BroadCast (如 ACTION_TIME_TICK),在广播的处理内阻塞主线程
*
* 收到广播后,10s 左右有 ANR 的 log,再过 10s 左右弹框
*/
public void produceANRByBroadCastHandle(View view) {
IntentFilter filter = new IntentFilter();
filter.addAction("anr_test");
filter.addAction(Intent.ACTION_TIME_TICK);
registerReceiver(registerReceiver, filter);
}
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
sleepTest();
}
}
Receiver 的接收超时、处理超时与 Service 的启动超时、处理超时类似。从 log 看,触发点都是 Receiver,产生环境不一样。
2.3.3 对比
接收超时 log:
处理超时 log:
接收超时 traces:
处理超时 traces:
2.4 ContentProvider
用得不多,以后遇到再补充吧。