小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
文章目录
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();
}
}