findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 sleepTest();
 }
 });
sleepTest方法详情
public void sleepTest(){
 new Handler().postDelayed(new Runnable() {
 @Override
 public void run() {
 Button button = findViewById(R.id.btn);
 button.setText(“jackie”);
 }
 },7000);
 try {
 Thread.sleep(21_000);
 Log.i(TAG, “onCreate: ====2”);
 } catch (InterruptedEx
 ception e) {
 e.printStackTrace();
 }
 }
点击按钮,是否会发生ANR,答案是不会。这就是耗时操作不一定会导致没有响应,和onCreate方法中进行sleep同理,但是如果你点击了按钮之后又点击了返回键,这个时候就会出现ANR了,因为主线程被休眠了,你点击了按键在5s内没有响应,这个时候就会出现ANR。
Service
在Service中的onCreate方法,默认创建的启动Activity创建的是前台服务(20s),所以会造成ANR。
@Override
 public void onCreate() {
 super.onCreate();
 try {
 Log.i(TAG, “onCreate: ==1=”);
 //Thread.sleep(19000); //不发生anr
 Thread.sleep(21000); //发生anr
 Log.i(TAG, “onCreate: 2”);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
ContentProvider
@Override
 public boolean onCreate() {
 Log.i(TAG, “onCreate: =1==”);
 try {
 Thread.sleep(21_000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 Log.i(TAG, “onCreate: =2===”);
 return false;
 }
这里也不会产生ANR,前面说到ContentProvider在publish过超时10s,”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。而通过查看ContentProvider源码和启动流程,我们可以看到onCreate方法在publish之前执行,所以在onCreate方法中并不会造成ANR。
BroadcastReceiver
发送广播
Intent intent = new Intent(“android.intent.jackie”);
 //设置为前台广播,默认是后台广播
 intent.setPackage(getPackageName()); //一定要设置包名,否则无法显示anr,具体暂不深究
 //intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
 Test2Activity.this.sendBroadcast(intent);
onReceive中执行耗时
public class TestBroadcastReceiver extends BroadcastReceiver {
 private static final String TAG = “TestBroadcastReceiver”;
 @Override
 public void onReceive(Context context, Intent intent) {
 Log.i(TAG, “onReceive: =1==”);
 try {
 //这里接受的都是前台广播
 //Thread.sleep(9000); //不发生anr
 Thread.sleep(21_000); //发生anr
 //这里接受的都是后台广播
 //Thread.sleep(65000); //发生anr
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 Log.i(TAG, “onReceive: =2==”);
 }
 }
对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s,后台广播的耗时是60s。所以这里会产生ANR。注意发送广播的时候要添加intent的包名。
Application
顺便做一下Application的测试
@Override
 public void onCreate() {
 super.onCreate();
 Log.i(TAG, “onCreate: a=”);
 try {
 Thread.sleep(21_000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 Log.i(TAG, “onCreate: =b=”);
}
并不会发生ANR,Android中的ANR并没有这个场景。
小结
除了输入事件造成的ANR,我们可以把其他ANR比作是引爆炸弹,那么整个流程包含三部分组成:
- 埋定时炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。
- 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。
- 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续的案件侦破(调试分析),最后是炸毁目标。
ANR的流程可以分为,埋炸弹->拆炸弹->引爆炸弹,如果埋的炸弹在一定时间内没有被拆除,就是**发送延迟消息一定时间内没有被移除,那就会引爆(触发),产生ANR。**详细分析可以查看该文章。
输入事件造成的ANR(特殊)
再来一个灵魂拷问:
在一个Activity中一个按钮点击事件中设置Thread.sleep(10_000),如果连续点击两次,会不会造成ANR,如果连续点击三次呢?
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 try {
 Thread.sleep(10_000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 });
在Android29上(之前的版本并没有测试,预计应该一样的),连续点击两次并不会造成ANR,对于input事件,如果仅仅点击没有后续的点击输入事件不会造成ANR,可我们不是连续点击了两次了吗?因为第一次点击事件已经被消费了,在onClick中才进行sleep,然后我们点击第二次,这个时候只有一个input事件,没有后续的点击输入,所以不会造成ANR。
而如果我们连续点击三次,除去第一次被消费掉,这个时候就会有连续的input输入,会产生ANR。如果我们把Thread.sleep写在onTouch中,只要一次点击就行,因为该方法会被回调两次,down和up是两次input时间。 注意:如果是按键事件(假如点击返回键),如果主线程在sleep(),这个时候点击了返回键,5s没响应,直接抛出ANR。
Android R上的修改
在Android R(11)上,对于上面的输入事件造成的ANR的那个问题,只需要连续两次点击就可以造成ANR,该版本上对input机制做了较大的调整,有兴趣可以自行研究一下。
如何手动设计一个ANR监听
FileObserver
Android中发送ANR之后,Android系统会将ANR Log输出至/data/anr/trace.txt,**本方案就是监听trace.txt文件。**在Android中可以通过FileObserver来实现对文件的监听,FileObserver是一个用于监听文件的访问,创建,修改,删除,移动等操作的监听器。但是在android5.0的普通用户app,不一定有权限来实现对文件的监听,一些系统的app可以用该方式实现。
public class ANRFileObserver extends FileObserver {
public ANRFileObserver(String path) {//data/anr/
 super(path);
 }
public ANRFileObserver(String path, int mask) {
 super(path, mask);
 }
@Override
 public void onEvent(int event, @Nullable String path) {
 switch (event)
 {
 case FileObserver.ACCESS://文件被访问
 Log.i(“Jackie”, "ACCESS: " + path);
 break;
 case FileObserver.ATTRIB://文件属性被修改,如 chmod、chown、touch 等
 Log.i(“Jackie”, "ATTRIB: " + path);
 break;
 case FileObserver.CLOSE_NOWRITE://不可写文件被 close
 Log.i(“Jackie”, "CLOSE_NOWRITE: " + path);
 break;
 case FileObserver.CLOSE_WRITE://可写文件被 close
 Log.i(“Jackie”, "CLOSE_WRITE: " + path);
 break;
 case FileObserver.CREATE://创建新文件
 Log.i(“Jackie”, "CREATE: " + path);
 break;
 case FileObserver.DELETE:// 文件被删除,如 rm
 Log.i(“Jackie”, "DELETE: " + path);
 break;
 case FileObserver.DELETE_SELF:// 自删除,即一个可执行文件在执行时删除自己
 Log.i(“Jackie”, "DELETE_SELF: " + path);
 break;
 case FileObserver.MODIFY://文件被修改
 Log.i(“Jackie”, "MODIFY: " + path);
 break;
 case FileObserver.MOVE_SELF://自移动,即一个可执行文件在执行时移动自己
 Log.i(“Jackie”, "MOVE_SELF: " + path);
 break;
 case FileObserver.MOVED_FROM://文件被移走,如 mv
 Log.i(“Jackie”, "MOVED_FROM: " + path);
 break;
 case FileObserver.MOVED_TO://文件被移来,如 mv、cp
 Log.i(“Jackie”, "MOVED_TO: " + path);
 break;
 case FileObserver.OPEN://文件被 open
 Log.i(“Jackie”, "OPEN: " + path);
 break;
 default:
 //CLOSE : 文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
 //ALL_EVENTS : 包括上面的所有事件
 Log.i(“Jackie”, “DEFAULT(” + event + "): " + path);
 break;
 }
 }
 }
ANRWatchDog
我们知道android是基于消息处理机制的,整个程序的运行都是在一个loop死循环中,通过handler对消息进行分发,所以我们可以开启一个线程通过Handler定时发送消息到我们的主线程,如果该消息超过一定时间未被处理,我们就可以判断发生了ANR。
public class ANRWatchDog extends Thread {
private static final String TAG = “ANR”;
 private int timeout = 5000;
 private boolean ignoreDebugger = true;
static ANRWatchDog sWatchdog;
private Handler mainHandler = new Handler(Looper.getMainLooper());
private class ANRChecker implements Runnable{
private boolean mCompleted;
 private long mStartTime;
 //这个值记录了系统启动到当前时刻经过的时间
 private long executeTime = SystemClock.uptimeMillis();
@Override
 public void run() {
 synchronized (ANRWatchDog.this) {
 mCompleted = true;
 executeTime = SystemClock.uptimeMillis();
 }
 }
void schedule() {
 mCompleted = false;
 mStartTime = SystemClock.uptimeMillis();
 //每次都post到该队列的前面
 mainHandler.postAtFrontOfQueue(this);
 }
boolean isBlocked() {
 return !mCompleted || executeTime - mStartTime >= 5000;
 }
 }
public interface ANRListener {
 void onAnrHappened(String stackTraceInfo);
 }
private ANRChecker anrChecker = new ANRChecker();
private ANRListener anrListener;
public void addANRListener(ANRListener listener){
 this.anrListener = listener;
 }
public static ANRWatchDog getInstance(){
 if(sWatchdog == null){
 sWatchdog = new ANRWatchDog();
 }
 return sWatchdog;
 }
private ANRWatchDog(){
 super(“ANR-WatchDog-Thread”);
 }
@Override
 public void run() {
 //super.run();
 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //设置为后台线程
 while (true){
 while (!isInterrupted()){
 synchronized (this){
 anrChecker.schedule();
 long waitTime = timeout;
 long start = SystemClock.uptimeMillis();
 //确保sleep一定的时间,防止假唤醒
 while (waitTime > 0){
 try{
 wait(waitTime);
 } catch (InterruptedException e){
 Log.w(TAG, "run: ", e);
 }
 waitTime = timeout - (SystemClock.uptimeMillis() - start);
 }
 if (!anrChecker.isBlocked()){
 continue;
 }
 }
 if (!ignoreDebugger && Debug.isDebuggerConnected()){
 continue;
 }
 String stackTraceInfo = getStackTraceInfo();
 if (anrListener != null){
 anrListener.onAnrHappened(stackTraceInfo);
 }
 }
 anrListener = null;
}
}
 private String getStackTraceInfo() {
 StringBuilder stringBuilder = new StringBuilder();
 for (StackTraceElement stackTraceElement : Looper.getMainLooper().getThread().getStackTrace()) {
 stringBuilder
 .append(stackTraceElement.toString())
 .append(“\r\n”);
 }
 return stringBuilder.toString();
 }
 }
模拟ANR的场景
void ANRTest(){
 handler = new Handler();
 ANRWatchDog.getInstance().addANRListener(new ANRWatchDog.ANRListener() {
 @Override
 public void onAnrHappened(String stackTraceInfo) {
 Log.i(TAG, "onAnrHappened: =======发生了ANR: "+ stackTraceInfo);
 }
 });
 ANRWatchDog.getInstance().start();
 //模拟场景,通过handler发送消息sleep 10秒,我们ANR的检测工具就会检测到自己发送的消息超过5s没有被处理,发生了"ANR"了。
 handler.postDelayed(new Runnable() {
 @Override
 public void run() {
 try {
 Thread.sleep(1000 * 10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 },3000);
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
 一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取
 ch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 },3000);
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-Z3AiWNNn-1719086902126)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取