Android中的消息处理实例与分析
摘要
本文介绍了Android中的消息处理机制,给出了Android消息处理中的几个重点类Handler、Message、MessageQueue、Looper、Runnable、Thread的详细介绍,提供了两个消息处理的实例代码,并深入分析了使用Android消息机制应该遵循的几个原则。
阅读本文的收获
在具有java基础的情况下,Android的学习比较轻松,很多人在没有深刻了解Android消息处理机制的背景下,已经能够开发出可用的app,很多人开始想学习Android消息处理机制的第一个动机是遇到了一个著名的bug“CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.”。这个bug的含义即“只有创建一个view层次的线程能够更新此view”,在更多情况下,它是想说“只有主线程能够更新UI”。
本文即是来解释如何利用Android的消息机制从主线程或者子线程中更新UI或完成其他操作。你可以学到:
1. 如何使用Android的消息实现同步、异步操作;
2. 如何在主线程和子线程发送并接收消息;
3. 消息是如何得到处理的;
4. 使用Android的消息处理机制应该遵循的几个原则;
5. 两个具体的消息处理实例源代码。
阅读本文需要的技术背景
你可能需要如下技术背景才能完全理解本文的内容,如何你没有以下背景,建议先学习相关知识:
1. Java语言基础
2. Java多线程技术
3. Android开发基本知识
第一个例子:在主线程和子线程中发送消息,在主线程中处理消息
先来看一个代码,代码地址为:
http://download.csdn.net/detail/logicteamleader/8827099
本例的作用是:创建一个Handler(处理消息的类),在界面上点击按钮就会向此Handler发送消息,Handler可以处理这些消息(后面会详细解释,消息的处理其实是多个类共同合作的结果),对UI进行修改。界面上有八个按钮,从上至下其效果分别是:
1. 使用Handler的post来传递一个Runnable的实例,该实例的run方法会在按钮点击时运行;
2. 使用Handler的postDelayed(Runnable r, long delayMillis)来传递一个Runnable的实例,该实例的run方法会在一段时间delayMillis后执行;
3. 使用Handler的sendMessage方法传递一个消息,该消息在Handler的handleMessage方法中被解析执行;
4. 使用Message的sendToTarget方法向获得该Message的Handler传递一个消息,该消息在Handler的handleMessage方法中被解析执行;
第5、6、7、8个方法与上面四个方法类似,不同的是它们都是在子线程中调用的。
源代码如下:
package com.example.wxbhandlertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.MessageQueue;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* @author wxb
* Android中的消息处理实例之一
* * 一、在主线程内发送消息
* 1.使用post
* 2.使用postDelay
* 3.使用sendMessage
* 4.使用Message.sentToTarget
* 二、在子线程中使用Handler
* 1.使用post
* 2.使用postDelay
* 3.使用sendMessage
* 4.使用Message.sentToTarget
*/
public class MainActivity extends Activity {
private Runnable runnable=null;
private Runnable runnableDelay=null;
private Runnable runnableInThread=null;
private Runnable runnableDelayInThread=null;
private static TextView tv;
private static TextView tvOnOtherThread;
//自定义Message类型
public final static int MESSAGE_WXB_1 = 1;
public final static int MESSAGE_WXB_2 = 2;
public final static int MESSAGE_WXB_3 = 3;
public final static int MESSAGE_WXB_4 = 4;
public final static int MESSAGE_WXB_5 = 5;
private static Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
tv.setText("invoke sendMessage in main thread");
break;
case MESSAGE_WXB_2:
tv.setText("Message.sendToTarget in main thread");
break;
case MESSAGE_WXB_3:
tvOnOtherThread.setText("invoke sendMessage in other thread");
break;
case MESSAGE_WXB_4:
tvOnOtherThread.setText("Message.sendToTarget in other thread");
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.tvOnMainThread);
tvOnOtherThread = (TextView) this.findViewById(R.id.tvOnOtherThread);
//方法1.post
runnable = new Runnable(){
public void run(){
tv.setText(getString(R.string.postRunnable));
}
};
Button handler_post = (Button) this.findViewById(R.id.btnHandlerpost);
handler_post.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.post(runnable);
}
});
//方法2:postDelay
runnableDelay = new Runnable(){
public void run(){
tv.setText(getString(R.string.postRunnableDelay));
}
};
Button handler_post_delay = (Button) this.findViewById(R.id.btnHandlerPostdelay);
handler_post_delay.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.postDelayed(runnableDelay, 1000); //1秒后执行
}
});
//方法3:sendMessage
Button btnSendMessage = (Button) this.findViewById(R.id.btnSendMessage);
btnSendMessage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_1;
mHandler.sendMessage(msg);
}
});
//方法4:Message.sendToTarget
Button btnSendtoTarget = (Button) this.findViewById(R.id.btnSendtoTarget);
btnSendtoTarget.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_2;
msg.sendToTarget();
}
});
//在其他线程中发送消息
//1.post
runnableInThread = new Runnable(){
public void run(){
tvOnOtherThread.setText(getString(R.string.postRunnableInThread));
}
};
Button btnPost = (Button) this.findViewById(R.id.btnPost);
btnPost.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.post(runnableInThread);
}
}.start();
}
});
//2.postDelay
runnableDelayInThread = new Runnable(){
public void run(){
tvOnOtherThread.setText(getString(R.string.postRunnableDelayInThread));
}
};
Button btnPostDelay = (Button) this.findViewById(R.id.btnPostDelay);
btnPostDelay.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.postDelayed(runnableDelayInThread, 1000);
}
}.start();
}
});
//3.sendMessage
Button btnSendMessage2 = (Button) this.findViewById(R.id.btnSendMessage2);
btnSendMessage2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_3;
mHandler.sendMessage(msg);
}
}.start();
}
});
//方法4:Message.sendToTarget
Button btnSendToTarget2 = (Button) this.findViewById(R.id.btnSendToTarget2);
btnSendToTarget2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.obtainMessage(MESSAGE_WXB_4).sendToTarget();
}
}.start();
}
});
}
}
几个规则
看完代码后,在解释具体类之前,先了解几个规则:
第一, 只有创建view的线程能够更新此view;一般来说,创建UI的是Android主线程,因此只有在主线程中才能更新UI;
第二, 处理消息的类是Handler,它依附于创建自己的线程;如果在主线程中创建Handler mHandler,则向mHandler发送的消息会在主线程中被解析;如果在子线程中创建Handler sHandler,则向sHandler发送的消息会在子线程中被解析;
第三, 发送消息有三类方法,Handler的post方法,Handler的sendMessage方法,以及Message的sentToTarget方法,它们最终其实都调用了Handler的sendMessage方法;
第四, 消息的方法可以即时也可以延时,代表就是post和postDelay,以及sendMessage和sendMessageDelayed;延时发送消息可以实现很多用户需要的界面延时效果,例如最常用的SplashWindow。
第五, post类型的方法只需要实例化一个Runnable接口,且不需要重载Handler. handleMessage方法,比较简单易用;
第六, sendMessage和sentToTarget需要重载Handler. handleMessage方法,实现对不同消息的解析。
Handler
Handler是Android消息处理中最重要的一个类,不管什么编程语言或者框架,叫Handler的类一般都很重要,也都很复杂,当年的Windows句柄类更是让人一头雾水。
幸好,Android的Handler很容易理解和使用。它就是一个消息处理的目标类,其主要作用有两个:1)它安排要执行的消息和runnable在未来某个时间点执行;2)处理来自不同线程的消息并执行操作。
使用Handler要注意以下几点:
1. Handler属于创建它的线程,并与线程的消息循环关联,同时与此线程的消息队列(MessageQueue)关联。
2. Handler发送消息的方法有两类:post类和sendMessage类,用法见例子;
3. 如果使用post发送消息,则消息中包含的Runnable会在解析消息时执行;
4. 如果使用sendMessage发送消息,则需要重载Handler. handleMessage方法,实现对不同消息的解析
Message
Message是消息类,它有几个重要的属性:
public int what:消息类型
public int arg1:参数1
public int arg2:参数2
public Object obj:对象参数
以上几个参数用户可以随意定制。
值得注意的有几点:
1. Android使用消息池模式来提高消息的效率,因此一般不适用new来创建消息,而是使用obtain方法来从消息池中获取一个Message的实例;Handler类也有obtain方法;
2. Message有一个Handler作为Target,一般来说就是获取该消息的所在线程的关联Handler;因此使用sendToTarget方法发送消息时,将消息发给它的Target;
MessageQueue
MessageQueue是消息队列,一个线程最多拥有一个消息队列,这个类一般不会被程序员直接使用。它的入队方法enqueueMessage用来将一个Message加入队列,这个方法是线程安全的,因此才保证了Android的消息处理机制是线程安全的。
MessageQueue的next方法用来获取下一个Message,没有任何消息时,主线程常常在此方法处等待。
Runnable
Java的接口,它代表一个可执行的代码片段,如下所示:
public interface Runnable {
/**
* Starts executing the active part of the class‘ code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
Thread
Java的线程类,大家都应该很熟悉了。
第二个例子:在子线程中创建消息处理循环
第一个例子描述了如何在多个线程中发送消息,并在主线程中统一接收和处理这些消息;第二个例子则描述如何在子线程中建立一个消息循环,并从主线程发送消息给子线程,让子线程处理这些消息。
第二个例子中有两个消息循环,两个Handler,主线程首先向子线程发送一个消息,子线程的收到消息后再向主线程发送一个消息,主线程收到消息后更新UI。
例子代码如下:
http://download.csdn.net/detail/logicteamleader/8827401
源代码如下:
package com.example.wxbloopinthread;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private static TextView tv = null;
//自定义Message类型
public final static int MESSAGE_WXB_1 = 1;
//主线程中创建Handler
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
tv.setText("主线程发送,子线程接收消息后回发,主线程修改UI");
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.textView1);
//创建子线程
new LooperThread().start();
//点击按钮向子线程发送消息
Button btn = (Button) this.findViewById(R.id.btnSendMessage);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LooperThread.sHandler.sendEmptyMessage(MESSAGE_WXB_1);
}
});
}
//定义子线程
static class LooperThread extends Thread {
public static Handler sHandler = null;
public void run() {
//创建消息循环
Looper.prepare();
sHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
mHandler.sendEmptyMessage(MESSAGE_WXB_1);
break;
}
}
};
//开启消息循环
Looper.loop();
}
}
}
第二个例子使用了另一个重要的类Looper,它是代表Android中的消息循环处理类。
Looper
Looper被用来创建一个线程的消息循环。线程默认情况下是没有消息循环的,要创建消息循环,必须先调用Looper.prepare,然后在合适的地方调用Looper.loop,这个loop方法就开始循环处理本线程接收到的消息,直到loop循环被停止。
在大部分情况下,Looper都是和Handler一起使用,它通过Handler接收和处理消息。
使用Looper要注意以下几点:
1. Android的主线程是默认已经创建了Looper对象的,因此不能在主线程中调用Looper.prepare;
2. Looper.prepare和Looper.loop都是静态方法,调用时要注意,不要使用new来创建一个Looper;
总结
使用Android消息的方法有以下几种:
1. 使用Handler和Runnable,即时或延时发送一个消息;
2. 使用Handler和Message,即时或延时发送一个消息,需重载Handler. handleMessage方法;
需要注意的规则有以下几条:
1. 只有创建view的线程能够更新此view;一般来说,创建UI的是Android主线程,因此只有在主线程中才能更新UI;
2. 处理消息的类是Handler,它依附于创建自己的线程;如果在主线程中创建Handler mHandler,则向mHandler发送的消息会在主线程中被解析;如果在子线程中创建Handler sHandler,则向sHandler发送的消息会在子线程中被解析;
3. Looper被用来创建一个线程的消息循环,要创建消息循环,必须先调用Looper.prepare,然后在合适的地方调用Looper.loop。
具体的体会,还是要多看代码才行。