先从平时的应用入手吧。试想这样一个场景,我们有一个下载文件的需求,而且我们在界面上要显示下载的状态:未下载,下载中,已下载。这个时候我们该怎么办?首先可以在界面上放一个Button,显示未下载,然后设置点击事件,点击后显示下载中,并开启一个线程去下载(这里用线程sleep代替,实际未下载)。等下载完成后,再把Button上的文字改为已下载。我们来试一下:
public class MainActivity extends AppCompatActivity {
private Button mButton;
private Thread mThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.bt_1);
initThread();
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mButton.setText("开启下载线程下载中");
mThread.start();
mButton.setClickable(false);
}
});
}
private void initThread() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mButton.setText("已下载完成");
}
});
}
}
编译运行,一切正常,然后我们点击一下按钮:肏,crash掉了。好吧,淡定,淡定,我们看一下log:
03-10 16:15:22.707 10592-11189/com.example.gray_dog3.handlertest E/AndroidRuntime: FATAL EXCEPTION: Thread-302
Process: com.example.gray_dog3.handlertest, PID: 10592
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7177)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1065)
好吧,用我过了四级的英语水平给翻译下吧,错误的线程调用异常,发生Crash的地点是ViewRootImpl中调用checkThread方法中。温馨提示是:只有创建View的线程才能对摸摸它创建的View。。。好吧,顺着代码一瞅,果然,我再mThread中摸了一把在UI线程中创建的mButton。所以报了这个错。为了更直观的看到报错原因,我们直接跳到源码ViewRootImpl的checkThread方法,看它做了什么。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
就是这货了,但是Android为什么要这样搞呢?我们仔细看这句话,只有创建了View的线程才能对这个View进行操作。而我们一般View都是为了显式在UI上的。Android正是为了防止我们在非UI线程去操作这些UI上的控件,才加了限制的。因为UI体验对用户来说是最直观的,如果谁都有权限去搞一发,那UI要么很乱,要么控制很复杂。但是总不能难搞就不让搞把?实际上是可以的,Android为我们提供一个工具,来让我们进行线程间的消息传递,就是我们接下来要讲的主角:Handler。
对于Handler来说上面的问题可以轻易解决,看代码:
public class MainActivity extends AppCompatActivity {
private Button mButton;
private Thread mThread;
private Handler mHandler;
private int a=3,b=5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
mButton.setText("已下载完成");
}
};
initThread();
mButton = (Button) findViewById(R.id.bt_1);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mButton.setText("开启下载线程下载中");
mThread.start();
mButton.setClickable(false);
}
});
}
private void initThread() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendMessage(Message.obtain());
}
});
}
}
在上面的代码中,我们只是在主线程里创建一个Handler,重写它的handleMessage方法,然后子线程里拿到这个Handler,下载结束后用Handler给主线程发一个消息。然后主线程收到消息后,再去改变Button上显示的文字。
上面只是Handler最普通的一个应用场景,发送消息,接收消息。实际上这也是它所有的能做的事。有人说还有postRunnable的吗?怎么能说是只有发送和就收消息呢?好,我们带着这个疑问来看下Handler究竟是什么:
首先看Android源码注释,
A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread's {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread's message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it -- from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.