欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

程序员文章站 2023-11-28 23:24:04
这篇文章写的非常好,深入浅出,关键还是一位大三学生自己剖析的心得。这是我喜欢此文的原因。下面请看正文: 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过...

这篇文章写的非常好,深入浅出,关键还是一位大三学生自己剖析的心得。这是我喜欢此文的原因。下面请看正文:

作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想。android源码中包含了大量的设 计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了。这不,前几天为了了解android的消息处理机 制,我看了looper,handler,message这几个类的源码,结果又一次被googler的设计震撼了,特与大家分享。

android的消息处理有三个核心类:looper,handler和message。其实还有一个message queue(消息队列),但是mq被封装到looper里面了,我们不会直接与mq打交道,因此我没将其作为核心类。下面一一介绍:

线程的魔法师 looper

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

复制代码 代码如下:

publicclass looperthread extends thread {
@override
publicvoid run() {
// 将当前线程初始化为looper线程
looper.prepare();

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

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

通过上面两行核心代码,你的线程就升级为looper线程了!!!是不是很神奇?让我们放慢镜头,看看这两行代码各自做了什么。

1)looper.prepare()

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

通过上图可以看到,现在你的线程中有一个looper对象,它的内部维护了一个消息队列mq。注意,一个thread只能有一个looper对象,为什么呢?咱们来看源码。

复制代码 代码如下:

publicclass looper {
// 每个线程中的looper对象其实是一个threadlocal,即线程本地存储(tls)对象
privatestaticfinal threadlocal sthreadlocal =new threadlocal();
// looper内的消息队列
final messagequeue mqueue;
// 当前线程
thread mthread;
// 。。。其他属性

// 每个looper对象中有它的消息队列,和它所属的线程
private looper() {
mqueue =new messagequeue();
mrun =true;
mthread = thread.currentthread();
}

// 我们调用该方法会在调用线程的tls中创建looper对象
publicstaticfinalvoid prepare() {
if (sthreadlocal.get() !=null) {
// 试图在有looper的线程中再次创建looper将抛出异常
thrownew runtimeexception("only one looper may be created per thread");
}
sthreadlocal.set(new looper());
}
// 其他方法
}

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为threadlocal。如果你还不清楚什么是threadlocal,请参考《理解threadlocal》。

2)looper.loop()

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

调用loop方法后,looper线程就开始真正工作了,它不断从自己的mq中取出队头的消息(也叫任务)执行。其源码分析如下:

复制代码 代码如下:

publicstaticfinalvoid loop() {
looper me = mylooper(); //得到当前线程looper
messagequeue queue = me.mqueue; //得到当前looper的mq

// 这两行没看懂= = 不过不影响理解
binder.clearcallingidentity();
finallong ident = binder.clearcallingidentity();
// 开始循环
while (true) {
message msg = queue.next(); // 取出message
if (msg !=null) {
if (msg.target ==null) {
// message没有target为结束信号,退出循环
return;
}
// 日志。。。
if (me.mlogging!=null) me.mlogging.println(
">>>>> dispatching to "+ msg.target +""
+ msg.callback +": "+ msg.what
);
// 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
msg.target.dispatchmessage(msg);
// 还是日志。。。
if (me.mlogging!=null) me.mlogging.println(
"<<<<< finished to "+ msg.target +""
+ msg.callback);

// 下面没看懂,同样不影响理解
finallong newident = binder.clearcallingidentity();
if (ident != newident) {
log.wtf("looper", "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);
}
// 回收message资源
msg.recycle();
}
}
}

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

looper.mylooper()得到当前线程looper对象

复制代码 代码如下:

publicstaticfinal looper mylooper() { // 在任意线程调用looper.mylooper()返回的都是那个线程的looperreturn (looper)sthreadlocal.get(); }

getthread()得到looper对象所属线程:
复制代码 代码如下:

public thread getthread() { return mthread; }

quit()方法结束looper循环:
复制代码 代码如下:

publicvoid quit() {
// 创建一个空的message,它的target为null,表示结束循环消息
message msg = message.obtain();
// 发出消息
mqueue.enqueuemessage(msg, 0);
}

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

1.每个线程有且最多只能有一个looper对象,它是一个threadlocal

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

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

那么,我们如何往mq上添加消息呢?下面有请handler!(掌声~~~)

异步处理大师 handler

什么是handler?handler扮演了往mq上添加消息和处理消息的角色(只处理由自己发出的消息),即通知mq它要执行一个任务(sendmessage),并在loop到自己的时候执行该任务(handlemessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

复制代码 代码如下:

publicclass handler {

final messagequeue mqueue; // 关联的mq
final looper mlooper; // 关联的looper
final callback mcallback;
// 其他属性

public handler() {
// 没看懂,直接略过,,,
if (find_potential_leaks) {
final class<?extends handler> klass = getclass();
if ((klass.isanonymousclass() || klass.ismemberclass() || klass.islocalclass()) &&
(klass.getmodifiers() & modifier.static) ==0) {
log.w(tag, "the following handler class should be static or leaks might occur: "+
klass.getcanonicalname());
}
}
// 默认将关联当前线程的looper
mlooper = looper.mylooper();
// looper不能为空,即该默认的构造方法只能在looper线程中使用
if (mlooper ==null) {
thrownew runtimeexception(
"can't create handler inside thread that has not called looper.prepare()");
}
// 重要!!!直接把关联looper的mq作为自己的mq,因此它的消息将发送到关联looper的mq上
mqueue = mlooper.mqueue;
mcallback =null;
}

// 其他方法
}

下面我们就可以为之前的looperthread类加入handler:
复制代码 代码如下:

publicclass looperthread extends thread {
private handler handler1;
private handler handler2;

@override
publicvoid run() {
// 将当前线程初始化为looper线程
looper.prepare();

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

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

加入handler后的效果如下图:

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

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

handler发送消息

有了handler之后,我们就可以使用 post(runnable), postattime(runnable, long), postdelayed(runnable, long), sendemptymessage(int), sendmessage(message), sendmessageattime(message, long)sendmessagedelayed(message, long)这些方法向mq上发送消息了。光看这些api你可能会觉得handler能发两种消息,一种是runnable对象,一种是message对象,这是直观的理解,但其实post发出的runnable对象最后都被封装成message对象了,见源码:

复制代码 代码如下:

// 此方法用于向关联的mq上发送runnable对象,它的run方法将在handler关联的looper线程中执行
publicfinalboolean post(runnable r)
{
// 注意getpostmessage(r)将runnable封装成message
return sendmessagedelayed(getpostmessage(r), 0);
}

privatefinal message getpostmessage(runnable r) {
message m = message.obtain(); //得到空的message
m.callback = r; //将runnable设为message的callback,
return m;
}

publicboolean 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对象

handler处理消息

说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchmessage(message msg)与钩子方法handlemessage(message msg)完成的,见源码

复制代码 代码如下:

// 处理消息,该方法由looper调用
publicvoid 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消息
privatefinalvoid handlecallback(message message) {
message.callback.run(); //直接调用run方法!
}
// 由子类实现的钩子方法
publicvoid handlemessage(message msg) {
}

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

handler的用处

我在小标题中将handler描述为“异步处理大师”,这归功于handler拥有下面两个重要的特点:

1.handler可以在任意线程发送消息,这些消息会被添加到关联的mq上。

android的消息处理机制(图文+源码分析)—Looper/Handler/Message              

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

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

这就解决了android最经典的不能在其他非主线程中更新ui的问题。android的主线程也是一个looper线程(looper 在android中运用很广),我们在其中创建的handler默认将关联主线程mq。因此,利用handler的一个solution就是在 activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新ui。(过程如图)

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

下面给出sample代码,仅供参考

复制代码 代码如下:

publicclass testdriveractivity extends activity {
private textview textview;

@override
protectedvoid 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();
}

publicvoid appendtext(string msg) {
textview.settext(textview.gettext() +"\n"+ msg);
}

class myhandler extends handler {
@override
publicvoid handlemessage(message msg) {
string result = msg.getdata().getstring("message");
// 更新ui
appendtext(result);
}
}
}

复制代码 代码如下:

publicclass sampletask implements runnable {
privatestaticfinal string tag = sampletask.class.getsimplename();
handler handler;

public sampletask(handler handler) {
super();
this.handler = handler;
}

@override
publicvoid 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;
}

}

当然,handler能做的远远不仅如此,由于它能post runnable对象,它还能与looper配合实现经典的pipeline thread(流水线线程)模式。请参考此文《android guts: intro to loopers and handlers》封装任务 message

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

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

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

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