“刨根问底”之Android 消息机制
首先我们要明白的一个问题是Android为什么会设计这样一个“消息机制”,它有什么作用??
要解答这个问题,我们首先要了解,Andorid中的UI控件都是运行在我们应用程序中的主线程中,它们是非线程安全的,也就说所有涉及到UI的相关操作都只能在主进程中进行操作,如果要进行跨线程的操作会报异常。
错误示例代码如下:
1: TextView textView;
2: Override
3: ublic void onCreate(Bundle savedInstanceState) {4: super.onCreate(savedInstanceState);5: setContentView(R.layout.main);
6:
7: textView = (TextView) findViewById(R.id.textView);
8: Button btnAdd = (Button) findViewById(R.id.btnAdd);
9: btnAdd.setOnClickListener(new View.OnClickListener() {10:
11: @Override
12: public void onClick(View v) {13:
14: //开启一个线程执行更新UI操作15: new Thread(new Runnable() {16:
17: @Override
18: public void run() {19:
20: //......执行一些费时的操作(省略)21:
22:
23: //执行成功之后将textview上的文字改为成功24: textView.setText("操作成功!");25:
26: }
27:
28: }).start();
29: }
30:
31: });
毫无疑问,上面的代码肯定会报错(textView是UI控件,只能在主线程进行操作)。那么要怎么解决这个问题呢,这时候就需要用到Android中的“消息机制”了,
要了解消息机制,我们先来了解下这里面涉及到五个关键对象:
Message: 消息对象,包含了一些描述信息和数据。
Handler: 负责消息对象的传递与处理(使用该类时我们必须先继承Handler类,然后重写Handler类中的handlerMessage(msg) 方法,在此方法中进行相关处理操作,如: 更新UI )
MessageQueue: 消息队列。存储由Handler发送过来的Message对象。
Looper: 负责为当前线程运行一个消息循环(MessageQueue)。它会循环抽取MessageQueue中的Message,并通过handler对象将其分发并处理。
消息机制的完整流程:
1. 生成消息
Message msg = handler.obtainMessage() / Message.obtain()
注意:虽然Message有公有的构造方法,但是建议还是使用上面写的这两种方法。因为,在调用这两个方法时,会去对象池中获取可重复利用的Message对象,避免了重新创建。
2. 发送消息
handler.sendMessage(msg); 或者 message.sendToTarget(); (它其实内部也是调用了handler.sendMessage(msg)方法)
接下来 我们通过分析源码来探究下“发送消息”这个操作的内部机制
通过查看源码我们可以了解sendMessage() 只是调用了Handler类中的sendMessageAtTime(),所以这里我们只用分析此方法就行-----源码如下:
类: Handler -451行
1: public boolean sendMessageAtTime(Message msg, long uptimeMillis)2: {
3: boolean sent = false;4: MessageQueue queue = mQueue; // 3. 问题:mQueue 从何而来????5: if (queue != null) {6: // 1. 将当前的handler对象保存在message的target属性中7: msg.target = this;8: // 2. 将message对象放入MessageQueue中9: sent = queue.enqueueMessage(msg, uptimeMillis);
10: }
11: else {12: RuntimeException e = new RuntimeException(13: this + " sendMessageAtTime() called with no mQueue");14: Log.w("Looper", e.getMessage(), e);15: }
16: return sent;17: }
上面的这个方法,其实只是做了两个操作:
1. 将当前对象(handler)保存在message.target 中, 方便在处理消息对象时使用。
2. 将message对象存入到MessageQueue-- (入队)。 其实 MessageQueue 类似于我们的链表结构,我们入队的Message对象遵循“先进先出”的原则,越早被入队的Message,会被越早处理。
除了以上的这两个操作,上面的源码中还留了一个问题?
3. 该方法中出现的MessageQueue从何而来?? 下面我们来仔细探讨下:
从刚才的sendMessageAtTime() 我们知道, 将message入队到MessageQueue对象实际上是 Handler 类中一个成员变量 : mQueue 。它是在Handler的构造放被初始化的,接下来我们来看下源码中Handler的构造方法:
类: Handler -109行
1: public Handler() {2: if (FIND_POTENTIAL_LEAKS) {3: final Class<? extends Handler> klass = getClass();4: if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&5: (klass.getModifiers() & Modifier.STATIC) == 0) {
6: Log.w(TAG, "The following Handler class should be static or leaks might occur: " +7: klass.getCanonicalName());
8: }
9: }
10: // 获取Looper对象11: mLooper = Looper.myLooper();
12: if (mLooper == null) {13: throw new RuntimeException(14: "Can't create handler inside thread that has not called Looper.prepare()");15: }
16: //从Looper对象中拿到MessageQueue对象并赋值给Handler类中的成员变量mQueue17: mQueue = mLooper.mQueue;
18: mCallback = null;
19: }
从以上代码可知,MessageQueue 对象 实际是位于Looper类中,,在构造handler对象的时候,我们从Looper中取出,并将其赋值给handle类中的成员变量。
到这里我想大家已经能明白: “发送消息”这个操作的内部操作:通过调用handler.sendMessage(msg) 方法,将传入的message对象传入到Looper对象(此对象唯一关联一个线程对象)的消息队列(MessageQueue)当中,等待被处理。
3. 执行消息
在第二步操作中我们已经知道,消息对象已经被handler发送到Looper中的MessageQueue中了。那么接下来该Message对象又做了什么呢??它最终又是怎么被执行的呢??
首先,我们先在Looper的源码中找到存放的消息对象的MessageQueue
1: //.... 部分代码省略2: public class Looper {3:
4: final MessageQueue mQueue;5:
6: //在构造方法中对MessageQueue对象进行初始化7: private Looper() {8:
9: mQueue = new MessageQueue();10:
11: }
12: }
接下来我们来看下,传入的Looper类中MessageQueue中的消息对象又是怎么执行的呢?
类: Looper -106行
1: public static final void loop() {2: Looper me = myLooper();
3: MessageQueue queue = me.mQueue;
4: while (true) {5: Message msg = queue.next();
6: if (msg != null) {7: if (msg.target == null) {8: return;9: }
10: msg.target.dispatchMessage(msg);
11:
12: msg.recycle();
13: }
14: }
15: }
通过查看上面的源码我们看到,Looper类中有一个Loop() 方法,它里面维护了一个死循环,负责抽取该类中的MessageQueue中的消息对象,然后该消息对象被对应的handler对象(在第二步我们已经知道,handler对象已经被保存在msg的 target属性上了), 传到 handler 对象中的dispatchMessage方法中。 源码如下:
类: Handler -90行
1: public void dispatchMessage(Message msg) {2: if (msg.callback != null) {3: handleCallback(msg);
4: } else {5: if (mCallback != null) {6: if (mCallback.handleMessage(msg)) {7: return;8: }
9: }
10: handleMessage(msg);
11: }
12: }
在此处方法中只是回调了handler.handlerMessage(msg) 方法, 该处理方法默认为空 。 所以,如果我们要对message进行的处理的话,必须继承Handler类然后重写handlerMessage方法,来完成自己的处理。例如:更新 UI。
好的,到这里Android的消息机制我们已经大概明白了, 过程如下:
①生成消息(生成Message对象)
–》
②发送消息(消息入队) –》
③ 执行消息
(Loop()方法将Message从消息队列中抽取出并传递到与Message相关的handler对象中)
– 》
我们重写handler方法中的handlerMessage() 进行相关处理
正确示例代码如下:
1: public class HandlerDemoActivity extends Activity {2:
3: TextView textView;
4: MyHandler myHandler;
5:
6: @Override
7: public void onCreate(Bundle savedInstanceState) {8: super.onCreate(savedInstanceState);9: setContentView(R.layout.main);
10:
11: //生成handler对象(注意: handler对象的创建位置非常关键,这个待会儿会详解)12: myHandler = new MyHandler();13:
14: textView = (TextView) findViewById(R.id.textView);
15: Button btnAdd = (Button) findViewById(R.id.btnAdd);
16: btnAdd.setOnClickListener(new View.OnClickListener() {17:
18: @Override
19: public void onClick(View v) {20:
21: //开启一个线程执行更新UI操作22: new Thread(new Runnable() {23: Handler handler;
24: @Override
25: public void run() {26:
27: //......执行一些费时的操作(省略)28:
29:
30: //执行成功之后将textview上的文字改为成功31: //textView.setText("操作成功!");-- 这是错误的32:
33:
34: // 1. 生成消息35: Message message = handler.obtainMessage();
36: //message.setData(data); 可以设置一些参数数据37:
38: // 2. 发送消息39: handler.sendMessage(message);
40:
41:
42: }
43:
44: }).start();
45: }
46:
47: });
48: }
49:
50: //继承Handler类,并重写handlerMessage方法51: class MyHandler extends Handler{52:
53:
54: public MyHandler() {55: super();56:
57: }
58:
59: //3. 处理Message60: @Override
61: public void handleMessage(Message msg) {62:
63:
64: textView.setText("操作成功!");65:
66: }
67: }
68: }
上面的代码是一个完整的Android消息机制实现,我们先来通过代码重温下 Android消息机制的过程:
① 示例代码第30行:
创建了一个消息对象(我们可以设置一些额外的数据,通过Message对象上的属性)
② 示例代码第39行:
将创建的消息对象发送出去,该消息对象会被发送到Looper中的MessageQueue中。
③Looper类源码第106行:( Loop() 方法 )
1: public static final void loop() {2: Looper me = myLooper();
3: MessageQueue queue = me.mQueue;
4: while (true) {5: Message msg = queue.next();
6: if (msg != null) {7: if (msg.target == null) {8: return;9: }
10: msg.target.dispatchMessage(msg);
11:
12: msg.recycle();
13: }
14: }
15: }
循环MessageQueue并调用所对应的hangdler对象的dispatchMessage(msg) 方法,将其分发出去,被分发出去的方法将会在Handler的handlerMessage方法接收到。
④ 示例代码第61行: (handlerMessage() 方法)
我们消息对象终于到到“目的地”,并且可以被处理了。
通过这个过程我们发现 Message对象是由handler发送,然后绕了一大圈儿之后又被送回到handler对象, 并进行相应的处理。 它之所以这么“麻烦”就是为了变相解决“在别的线程操作UI” 的问题。----- 在其它线程中进行一些相关操作(一般是比较费时的)后,并不是在当前线程中更新UI,而是传递一个消息到与UI线程相关联的Looper中, 然后会在Looper中的Loop方法被抽取并传送至handler对象的handlerMessage方法。
最后,我们还要解决一个疑问就是: 为什么能在handler中进行UI操作 -- textView.setText("操作成功!"); --- 示例代码 39 行
答案的关键点就是handler对象的创建位置:
1: public class HandlerDemoActivity extends Activity {2:
3: TextView textView;
4: MyHandler myHandler;
5:
6: @Override
7: public void onCreate(Bundle savedInstanceState) {8: super.onCreate(savedInstanceState);9: setContentView(R.layout.main);
10:
11: //生成handler对象(此操作是在UI主线程中)12: myHandler = new MyHandler();13:
14: //..........以下代码省略
从上面代码我们看出来我们的handler对象实际是在UI线程中创建的,,当我们创建在UI线程中创建handler对象时,android会将此对象和当前UI线程对象中的消息队列绑定, 源码如下:
1: public Handler() {2:
3: //.... 部分代码省略4:
5: //从当前线程中取出Looper对象并向其赋值给Handler的成员变量mLooper6: mLooper = Looper.myLooper();
7: if (mLooper == null) {8: throw new RuntimeException(9: "Can't create handler inside thread that has not called Looper.prepare()");10: }
11: // 从Looper中取出MessageQueue对象并赋值12: mQueue = mLooper.mQueue;
13: mCallback = null;
14: }
下面是Looper.myLooper()方法的源码:
1: public static final Looper myLooper() {2: //从当前线程中获取Looper对象3: return (Looper)sThreadLocal.get();4: }
通过这两段代码我们就明白了,handler其实是间接关联(handler关联Looper,而Looper又唯一对应一个线程,并负责为当前线程运行消息循环)当前线程对象,如果当前线程是UI线程的话那么我们就可以处理同在UI线程中的UI控件了。
另外,有一点要提醒的就是,如果我们的Handler对象不是在UI线程中进行初始化的,那么我们就得自己为当前线程构造一个Looper对象,如果要运行的话还得自己启动位于Looper中的消息循环(Loop()方法). 示例代码如下:
1: class LooperThread extends Thread {2: public Handler mHandler;3:
4: public void run() {5: //构造一个Looper对象,并向其放入到当前线程中(感兴趣的同学可以通过源码查看明细)6: Looper.prepare();
7:
8: mHandler = new Handler() {9: public void handleMessage(Message msg) {10: // process incoming messages here11: }
12: };
13:
14: //启动消息处理循环15: Looper.loop();
16: }
17: }
如果handler是在UI线程中进行构造的,那么上面代码中注释的两句代码,android会自动帮我们执行。
ok, 到此,此文结束!