服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - android的消息处理机制(图文+源码分析)—Looper/Handler/Message

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

2021-01-06 12:42Android开发网 Android

这篇文章写的非常好,深入浅出;android的消息处理机制(图+源码分析)—Looper,Handler,Message是一位大三学生自己剖析的心得,感兴趣的朋友可以了解下哦,希望对你有所帮助

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

作为一个大三的预备程序员,我学习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。

延伸 · 阅读

精彩推荐
  • AndroidAndroid中AsyncTask详细介绍

    Android中AsyncTask详细介绍

    这篇文章主要介绍了Android中AsyncTask详细介绍,AsyncTask是一个很常用的API,尤其异步处理数据并将数据应用到视图的操作场合,需要的朋友可以参考下...

    Android开发网7432021-03-11
  • AndroidAndroid CardView+ViewPager实现ViewPager翻页动画的方法

    Android CardView+ViewPager实现ViewPager翻页动画的方法

    本篇文章主要介绍了Android CardView+ViewPager实现ViewPager翻页动画的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    Abby代黎明9592022-03-02
  • AndroidAndroid实现固定屏幕显示的方法

    Android实现固定屏幕显示的方法

    这篇文章主要介绍了Android实现固定屏幕显示的方法,实例分析了Android屏幕固定显示所涉及的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    鉴客6182021-03-27
  • AndroidAndroid实现Service获取当前位置(GPS+基站)的方法

    Android实现Service获取当前位置(GPS+基站)的方法

    这篇文章主要介绍了Android实现Service获取当前位置(GPS+基站)的方法,较为详细的分析了Service基于GPS位置的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    Ruthless8332021-03-31
  • Android汇总Android视频录制中常见问题

    汇总Android视频录制中常见问题

    这篇文章主要汇总了Android视频录制中常见问题,帮助大家更好地解决Android视频录制中常见的问题,需要的朋友可以参考下...

    yh_thu5192021-04-28
  • AndroidAndroid程序设计之AIDL实例详解

    Android程序设计之AIDL实例详解

    这篇文章主要介绍了Android程序设计的AIDL,以一个完整实例的形式较为详细的讲述了AIDL的原理及实现方法,需要的朋友可以参考下...

    Android开发网4622021-03-09
  • AndroidAndroid编程解析XML方法详解(SAX,DOM与PULL)

    Android编程解析XML方法详解(SAX,DOM与PULL)

    这篇文章主要介绍了Android编程解析XML方法,结合实例形式详细分析了Android解析XML文件的常用方法与相关实现技巧,需要的朋友可以参考下...

    liuhe68810042021-05-03
  • AndroidAndroid界面效果UI开发资料汇总(附资料包)

    Android界面效果UI开发资料汇总(附资料包)

    android ui界面设计,友好的界面会提高用户体验度;同时也增强了android ui界面设计的难度,本文提供了一些常用开发资料(有下载哦)感兴趣的朋友可以了解下...

    Android开发网4652021-01-03