为什么线程不能直接通信
Android提供了Handler和Looper来进行线程间的通信,那么首先我们需要明白,线程间为什么不能直接通信,而需要借助Handler-Looper机制来完成。
一个应用程序的多个线程分为主线程(MainThread)和其它线程(WorkerThread)。MainThread主要负责接收用户的输入以及将运算结果反馈给用户,同时负责对UI的操作,而整个运算过程都是交给WorkerThread来处理的,因为在运算的过程中很可能会造成线程阻塞,为了保证主线程不会阻塞,一定要将可能会造成阻塞的操作放到WorkerThread中。而且在WorkerThread中也不能直接对UI进行操作,所以必须在主线程和其它线程之间建立一种机制来满足它们之间的通信,这种机制就是Handler-Looper机制。
什么是Handler、Looper以及MessageQueue
从上面这张图我们可以看出,Handler的作用是将消息push进消息队列中,而Looper将消息从队列中取出,再送到Handler处让Handler对消息进行处理。如果不清楚原理,看到这肯定已经懵逼了…为什么Handler将消息发出去最后又要回到Handler呢?那么接下来我们将来探讨这个问题。
不过在这之前呢,我们需要明白,Android所有UI相关的代码都是运行在主线程(MainThread)中的,在一个WorkerThread中是无法直接对UI(ProgressBar例外)进行操作的
WorkerThread到MainThread的通信
在WorkerThread中调用Handle的sendMessage() 方法将消息发送到消息队列中,然后Looper会将消息从队列中取出,调用与该消息对应的Handler(位于主线程中)的handlerMessage()方法将消息传送给Handler。具体实现代码如下
public class MainActivity extends ActionBarActivity {
private TextView textView;
private Button button;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.textViewId);
button = (Button)findViewById(R.id.buttonId);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Thread t = new WorkerThread();
t.start();
}
});
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
String s = (String)msg.obj;
textView.setText(s);
}
};
}
class WorkerThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String s = "网络返回数据";
Message msg = handler.obtainMessage();
msg.obj = s;
handler.sendMessage(msg);
}
}
MainThread到WorkerThread的通信
在WorkerThread中创建Handler对象,同时Looper通过Looper.loop()方法不断循环接收消息,接受到之后,调用与该消息对应的handler的handlerMessage()方法处理消息(如果消息队列中没有消息对象,则线程阻塞),而在Mainthread的button监听器中通过handler.sendMessage()方法将消息送至消息队列中。具体代码实现如下:
public class MainActivity extends ActionBarActivity {
private Button button;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.buttonId);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
}
});
WorkerThread wt = new WorkerThread();
wt.start();
}
class WorkerThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
System.out.println("消息已收到");
}
};
Looper.loop();
}
}
从上面的代码可以看出,MainThread和workerThread之间正是利用了消息从Handler到Looper再到Handler这样的机制巧妙的解决了在WorkerThread中不能访问位于MainThread中的UI控件的问题。
同时还必须了解Handler在创建消息、发送消息和处理消息时的原理,以下来自
1.Handler创建消息
每一个消息都需要被指定的Handler处理,通过Handler创建消息便可以完成此功能。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。消息的创建流程如图所示。
2.Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
Handler、Looper、MessageQueue的初始化流程如图所示:
Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。
3.Handler处理消息
UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。