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 添加点击事件,要执行的操作是:每隔一秒打印一个时间,制造一个时钟的效果
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自动增长
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();
}
}