文章目录


ANR

Application Not Responding
应用程序无响应

Android 系统中,ActivityManagerService(简称AMS) 和 WindowManagerService(简称WMS) 会检测 App 的响应时间,如果 App 在特定时间无法相应屏幕触摸或键盘输入时间,或者特定事件没有处理完毕,就会出现ANR。

以下四个条件都有可能造成 ANR:
1、InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
2、BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的​​​onReceive()​​​函数时10秒没有处理完成,后台为 60 秒。
3、Service Timeout :前台服务 20 秒内,后台服务在 200 秒内没有执行完毕。
4、ContentProvider Timeout :ContentProvider的publish在 10s 内没进行完。

所以,在Android中,尽量避免在主线程(UI线程)中作耗时操作。应该由子线程完成。处理ANR的解决办法就是开子线程。

Android UI 线程模型

在 Android 系统中,主线程用于处理 UI(User Interface) 相关操作,例如创建 View 对象,对 View 相关的操作进行响应等,所以主线程也称之为 UI线程。

系统约定了,只有创建 view 的线程才能操作 view,所以,可以小结为:只有主线程才可以调用控件的方法,而子线程不可以。

【进阶】
只有拥有 ViewRoot 的线程才可以操控 View,在 Android APP 中,主线程默认就有ViewRoot,而子线程没有

栗子1:每秒输出时间

对 Button 添加点击事件,要执行的操作是:每隔一秒打印一个时间,制造一个时钟的效果
【达内课程】Thread中ANR_线程

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn;
private TextView tvTime;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Log.d("Thread", "MainActivity oncreate()" + Thread.currentThread().getId());

btn = findViewById(R.id.button);
tvTime = findViewById(R.id.textView);

btn.setOnClickListener(this);
}

private class InnerThread extends Thread {
SimpleDateFormat sdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();

@Override
public void run() {
super.run();

for (int i = 0; i < 60; i++) {
date.setTime(System.currentTimeMillis());

Log.d("Thread", "Thread Id=" + getId());

runOnUiThread(() -> {
tvTime.setText(sdt.format(date));
Log.d("Thread", "runOnUiThread()->=" + Thread.currentThread().getId());
});

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
InnerThread innerThread = new InnerThread();
innerThread.start();
break;
}
}
}

对代码的解释

那么为什么不直接在 onClick 代码中写相应的操作呢?

因为我们的操作需要有 60s 才能完成,可能导致程序 ANR,所以需要一个线程来完成操作。

因此写一个内部类 InnerThread 继承 Thread,重写​​run()​​方法,把需要执行的操作放进去即可。

再看 InnerThread 类中,只有拥有 ViewRoot 的线程才可以操控 View,在 Android APP 中,主线程默认就有 ViewRoot,而子线程没有因为子线程无法操作,所以写了一个​​runOnuiThread()​​​方法来通知主线程来操作 view,修改 TextView 的显示
通过打印日志可知,runOnUiThread 中的​​​run()​​方法就是执行在主线程的

栗子2:ProgressBar自动增长

【达内课程】Thread中ANR_ide_02

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ProgressBar progressBar;
private Button button;
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

progressBar = findViewById(R.id.progressBar);
button = findViewById(R.id.button);
textView = findViewById(R.id.textView);
button.setOnClickListener(this);
}

private class InnerThread extends Thread {
private int i;

@Override
public void run() {
Runnable runnable = () -> {
progressBar.setProgress(i);
textView.setText(progressBar.getProgress() + "/" + progressBar.getMax());

};

for (i = 0; i <= 100; i++) {
runOnUiThread(runnable);

try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

@Override
public void onClick(View view) {
InnerThread thread = new InnerThread();
thread.start();
}
}