您的位置:首页 > 移动开发 > Android开发

源码分析-Android中的消息机制详解

2016-04-09 15:34 531 查看
本人原创,转载请注明出处哈:http://blog.csdn.net/tyhj_sf/article/details/51105453

1 引言

个人认为,不亲自阅读和熟悉Android的源码,不了解Android源码背后的实现机理,就不能真正成为Android开发的高手。前两天为了了解android的消息处理机制,我阅读和分析了Looper,Handler,MessageQueue,Message这几个类的源码,不得不对Google大牛们膜拜下,现在把学习的笔记拿出来特与大家分享。

2 消息机制的基本原理

从大的方面讲,不光是Android平台,各种平台的消息机制的原理基本上都是相近的,其中用到的主要概念大概有:

1)消息发送者;

2)消息队列;

3)消息处理循环。

示意图如下:



图中表达的基本意思是,消息发送者通过某种方式,将消息发送到某个消息队列里,同时还有一个消息处理循环,不断从消息队列里摘取消息,并进一步解析处理。

在Android平台上,把上图的右边部分包装成了一个Looper类,这个类的内部具有对应的消息队列(MessageQueue mQueue)和loop函数。

但是Looper只是个简单的类而已,它虽然提供了循环处理方面的成员函数loop(),却不能自己凭空地运行起来,而只能寄身于某个真实的线程。而且,每个线程最多只能运作一个Looper对象,这一点应该很容易理解。

Android平台上另一个关键类是Handler。当消息循环在其寄身的线程里正式运作后,外界就是通过Handler向消息循环发出事件的。我们再画一张示意图如下:



在Android应用中为线程创建消息处理程序的常规用法如下示例代码:

class LooperThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();

mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};

Looper.loop();
}
}


3 Android消息机制相关的核心类

android的消息处理有四个核心类:Looper,Handler和MessageQueue(消息队列),Message。其中MessageQueue被封装到Looper里面了,我们不会直接与MessageQueue打交道。下面一一介绍:

3.1 Looper类

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread {
@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();

// ...其他处理,如实例化handler

// 开始循环处理消息队列
Looper.loop();
}
}


让我们看看与Looper相关的这两行代码各自做了什么。

1)Looper.prepare()的源码:

public class Looper {
// 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
Thread mThread;
// 。。。其他属性

// 每个Looper对象中有它的消息队列,和它所属的线程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}

// 我们调用该方法会在调用线程的TLS中创建Looper对象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}


通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象保存到ThreadLocal中去。如果你还不清楚什么是ThreadLocal,可以去看看它源码,这里不对它进行细说,我们只需要知道是每一个线程对象都有一个ThreadLocal类型的成员变量,而这段源码里的sThreadLocal就是当前线程的ThreadLocal类型的成员变量的引用。

2)Looper.loop()方法的源码:

当调用loop方法后,Looper线程就开始真正工作了,loop()方法内部的For(;;)死循环不断从自己的MessageQueue中取出队头的消息(也叫任务)执行。其源码分析如下:

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}

msg.target.dispatchMessage(msg);

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

msg.recycle();
}
}


除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如:

prepareMainLooper(),如果是主线程,调用此方法为主线程准备一个looper,主线程的looper有应用程序初始化;

Looper.myLooper()得到当前线程looper对象;

getThread()得到looper对象所属线程;

quit()方法结束looper循环。

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且最多只能有一个Looper对象,它被添加到ThreadLocal中去。每个线程都有一个ThreadLocal类型成员变量。

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

3.2 Message类

Message类中含有一个Message类型的成员变量next。学过数据结构就可立刻知道,这是链表节点的结构,因此MessageQueue实际上维护的是一个Message链表。Message部分源码:

public final class Message implements Parcelable {

public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
··················
··················


在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点做开发时需要注意(有待补充):

1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

3.擅用message.what来标识信息,以便用不同方式处理message。

3.3 Handler类

3.3.1 什么是handler?

handler扮演了往MessageQueue上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以通过它的几个有参构造方法来指定looper的。

下面我们就可以为之前的LooperThread类加入Handler:

public class LooperThread extends Thread {
private Handler handler1;
private Handler handler2;

@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();

// 实例化两个handler
handler1 = new Handler();
handler2 = new Handler();

// 开始循环处理消息队列
Looper.loop();
}
}


可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

3.3.2 Handler发送消息

有了handler之后,我们就可以使用

post(Runnable),

postAtTime(Runnable, long),

postDelayed(Runnable, long),

sendEmptyMessage(int),

sendMessage(Message),

sendMessageAtTime(Message, long)

sendMessageDelayed(Message, long)

这些方法向消息队列上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:

// 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
public final boolean post(Runnable r)
{
// 注意getPostMessage(r)将runnable封装成message
return  sendMessageDelayed(getPostMessage(r), 0);
}

private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();  //得到空的message
m.callback = r;  //将runnable设为message的callback,
return m;
}

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;  // message的target必须设为该handler!
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}


其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);


2.post发出的message,其callback为Runnable对象

3.3.3 Handler处理消息

消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码:

// 处理消息,该方法由looper调用
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果message设置了callback,即runnable消息,处理callback!
handleCallback(msg);
} else {
// 如果handler本身设置了callback,则执行callback
if (mCallback != null) {
/* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message没有callback,则调用handler的钩子方法handleMessage
handleMessage(msg);
}
}

// 处理runnable消息
private final void handleCallback(Message message) {
message.callback.run();  //直接调用run方法!
}
// 由子类实现的钩子方法
public void handleMessage(Message msg) {
}


可以看到,除了handleMessage(Message msg)和Runnable对象的run( )方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!

3.3.4 Handler的作用

1.只要线程A的handler传进另一个线程B中,就可以在线程B中向线程B发送消息,这些消息会被添加到关联的MessageQueue上。



2.handler是在它关联的线程中处理消息的。

handler创建时,成员变量包含了一个looper,而looper关联到一个线程的。因此可以判断handler关联到哪个线程。

这就解决了android不能在其他非主线程中更新UI的问题。

Activity是在主线程运行的,在Activity中创建handler,那么这个handler就属于主线程,我们可以将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知Activity更新UI。

过程如下图所示:



下面给出一个在worker thread通过发消息更新UI的例子(代码仅供参考):

public class TestDriverActivity extends Activity {
private TextView textview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview = (TextView) findViewById(R.id.textview);
// 创建并启动工作线程
Thread workerThread = new Thread(new SampleTask(new MyHandler()));
workerThread.start();
}

public void appendText(String msg) {
textview.setText(textview.getText() + "\n" + msg);
}

class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String result = msg.getData().getString("message");
// 更新UI
appendText(result);
}
}
}


public class SampleTask implements Runnable {
private static final String TAG = SampleTask.class.getSimpleName();
Handler handler;

public SampleTask(Handler handler) {
super();
this.handler = handler;
}

@Override
public void run() {
try {  // 模拟执行某项任务,下载等
Thread.sleep(5000);
// 任务完成后通知activity更新UI
Message msg = prepareMessage("task completed!");
// message将被添加到主线程的MQ中
handler.sendMessage(msg);
} catch (InterruptedException e) {
Log.d(TAG, "interrupted!");
}

}

private Message prepareMessage(String str) {
Message result = handler.obtainMessage();
Bundle data = new Bundle();
data.putString("message", str);
result.setData(data);
return result;
}

}


4 分析源码后对几个疑惑的回答

问:消息机制相关的类几个类?

答:Thread、 Handler、Looper、MessageQueue、Message

问:Thread如何获得Looper?

答:对于Main Thread,调用Looper.getMainLooper( ),因为Looper已由应用创建。对于worker Thread,调用Looper.prepare( )创建一个关联的looper。

问:handler如何从消息队列中取消息并处理?

答:(1)handler持有Looper类型变量mLooper,handler在初始化时,将相关联的线程的Looper赋值给了mLooper。(2)handler持有MessageQueue类型变量mQueue,而mQueue通过赋值语句(mQueue=Looper.mQueue;)实例化。

问:Message怎样被传递给Handler.handleMessage(msg)方法的?

答:在Looper.loop()方法中,for死循环中调用了Handler.dispatchMessage(msg)方法(该方法中调用了handleMessage(msg)钩子方法)直到消息队列为空退出循环。

问:一个线程的handler怎么知道另外的线程发送了msg消息并从消息队列取消息处理呢?

答:在调用handler.sendMessage(msg)发送了msg消息时,sendMessage(msg)方法内部调用了enqueueMessage()方法,enqueueMessage()内部则调用了C/C++代码接口方法nativeWake( ),在这里我们不在向下研究C++层的代码。通过查资料得知:我们在构造Looper对象时,其内部创建了一个监听机制来监听消息入队,一旦有消息入消息队列进进行消息处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息