假设这样一种产品需求:
Android主线程崩溃后,向用户弹出一个UI提醒(一个dialog或者一个toast),告知用户APP异常崩溃。
主线程崩溃后,给用户弹出一个UI提醒
一般我们的做法是这样:
CrashHandler.java
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Looper;
public class CrashHandler implements Thread.UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
//--------------单例begin-------------
private static CrashHandler INSTANCE = new CrashHandler();
private CrashHandler() {
}
public static CrashHandler getInstance() {
return INSTANCE;
}
//-------------单例end-------------
private Context mContext;
public void init(Context ctx) {
this.mContext = ctx;
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
System.out.println("uncaughtException");
new Thread() {
@Override
public void run() {
Looper.prepare();
//
new AlertDialog.Builder(mContext).setTitle("提示").setCancelable(false)
.setMessage("居然崩溃了,呜呜呜...").setNeutralButton("哈哈终于崩溃了...", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
}).create().show();
//
Looper.loop();
}
}.start();
}
}
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//传入参数必须为Activity,否则AlertDialog将不显示。
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
}
这里,我们注意到,异步线程居然在操作UI,为什么异步线程可以操作UI呢?
new Thread() {
@Override
public void run() {
Looper.prepare();
//
new AlertDialog.Builder(mContext).setTitle("提示").setCancelable(false)
.setMessage("居然崩溃了,呜呜呜...").setNeutralButton("哈哈终于崩溃了...", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.exit(0);
}
}).create().show();
//
Looper.loop();
}
}.start();
为什么异步线程可以操作UI呢?
在android.view.ViewRootImpl中有一个checkThread方法:
了解ViewRootImpl,推荐 从ViewRootImpl类分析View绘制的流程
android.view.ViewRootImpl
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
}
}
checkThread方法在requestLayout、焦点变化时,被调用;
用来检查,当前线程与View的创建线程是否在同一线程;
并未进行是否为主线程的判断。
所以其实我们在子线程中也是可以更新UI的,只要将操作UI对象的代码写在Looper.prepare()和Looper.loop()之间。
当然前提还是操作的UI对象必须得是子线程自己创建的。因此即使子线程可以操作自己的UI对象,比如弹出一个Toast(使用getApplicationContext),但是子线程仍然不能直接操作主线程的UI。同理主线程也不能操作子线程创建的UI对象,想要操作也必须引用子线程中的Handler发送消息来更新。