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

深入浅析Android消息机制

程序员文章站 2024-02-26 20:19:34
在android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。 每一个...

在android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。

每一个android应用在启动的时候都会创建一个线程,这个线程被称为主线程或者ui线程,android应用的所有操作默认都会运行在这个线程中。
但是当我们想要进行数据请求,图片下载,或者其他耗时操作时,是不可能在这个ui线程做的,因为android在3.0以后的版本已经禁止了这件事情,直接抛出一个异常。所以我们需要一个子线程来处理那些除ui操作的事情。

但是这个又会有一个问题,我们只能在ui线程进程ui操作,只能在子线程进行耗时操作,如果我们需要在耗时操作结束后在android界面上显示一个view,我们应该怎么做?我们是不可能在子线程直接刷新ui的。这时我们需要用到android的消息机制,来实现主线程和子线程的通信。简单来说,就是子线程获取到数据之后,不直接进行ui更新,而是把数据装到消息中发送到主线程,主线程中有一个循环轮询会立即收到子线程发过来的信息,然后拿到消息数据后在主线程更新ui 。说起来比较简单,我们来仔细的看一下具体是怎么说的。

处理消息的手段——handler, looper, messagequeue

handler

我们先讲解一下handler,handler顾名思义就是处理者,通常对他的用法是在ui线程中新建一个handler,并覆写他的handlemessage, 然后再耗时的线程中将消息post给ui线程,例子如下:

class myhandler extends handler{
@override
public void handlemessage(message msg){
//更新ui
}
}
myhandler mhandler = new myhandler();
new thread(){
public void run(){
mhandler.sendemptymessage(123);
};
}.start(); 

这里规定了handler必须在主线程创建,因为只有在ui线程创建才会让handler关联到已有的messagequeue。而messagequeue被封装到looper中,而looper又通过threadlocal封装到一个线程中,最后相当于messagequeue关联了一个线程。所以简单来说就是handler将消息投递到一个关联了线程的messagequeue中,然后handler在从messagequeue中取出消息,并且处理它。

我们看一下handler的2个常用的方法

void handlemessage(message msg) : 处理消息的方法
final boolean sendmessage(message msg) : 立即发送消息

第一个方法 我们通常在ui线程中执行,一般用来刷新ui,至于如果创建了一个非静态内部类产生对内存泄漏,建议参考这篇博客handler引发的内存泄漏.第二个方法我们通常在子线程中执行,需要一个handler的实例化对象,通常是由主线程去去传递给子线程。并且需要一个message对象,指定他的msg.what作为消息的标示,但是如果我们只是用handler去处理一个消息的时候,选择post方法是个更好的选择,例子如下:

private handler mhandler = new handler();
new thread(new runnable() {
@override
public void run() {
mhandler.post(new runnable() {
@override
public void run() {
//ui操作
...
}
});
}
}).start(); 

下面我们接着讨论下消息的循环队列messagequeue与包装他的looper循环

looper和messagequeue

上面提到了在ui线程中创建并实例化handler对象不需要looper和messagequeue,因为我们的应用在启动的时候先执行了activitythreadmain,在这个方法就是java语言运行的入口public

static void main(string [] args) 在这里面创建了一个mainlooper,创建的过程如下:
public static void main(string[] args){
//初始化
looper.preparemainlooper();
activitythread thread = new activitythread();
thread.attach(false);
if(smainthreadhandler == null){
smainthreadhandler = thread.gethandler();
}
asynctask.init();
//动起来
looper.loop();
} 

这里面并没有messagequeue的出现,我们可以看一看looper类的源码,来了解在初始化的时候发生了什么有趣的事情。

public class looper {
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());
}
// 其他方法
} 

我们一行行的看这段代码,首先是实例化一个threadlocal对象,这个用来实现looper循环的本地化存储,关于threadlocal可以看这篇文章为什么用threadlocal,简而言之就是当多个线程同时访问looper对象的时候,我们不用synchronized同步机制来处理他,而是为每个线程创建一个自己的looper副本,a线程改变了a的looper副本,不影响b线程的looper,从而比较高效的实现线程安全。后面几句依次定义了messagequeue,并对looper进行了私有化构造,在prepare方法中将looper对象设置给了sthreadlocal 这样messagequeue包装在了looper对象中,同时通过threadlocal使得线程和looper关联上,从而消息队列与线程关联上,并且不同的线程就不能访问对方的消息队列。

如下图所示:

深入浅析Android消息机制

接着就是looper.loop 循环执行起来,我们看一下,在loop方法里面执行了发生了什么事情

public static final void loop() {
looper me = mylooper(); //得到当前线程looper
messagequeue queue = me.mqueue; //得到当前looper的mq
while (true) {
message msg = queue.next(); // 取出message
if (msg != null) {
if (msg.target == null) {
return;
}
msg.target.dispatchmessage(msg);
msg.recycle();
}
}
}

这是省略版的代码,我们从这里看出无限循环执行,首先从消息队列中不断取出消息,然后不断msg是否为空,msg.target是否为空,不空的话,执行dispatchmessage方法,这个方法是handler的一个方法,由此我们可以看出msg.target是handler的类型,至此,通过looper.prepare和loop.loop实现了messagequeue,looper,handler三者之间的关联。而handler与looper,和messagequeue关联则是在handler的默认构造器中,通过looper.getlooper获取loop对象,从而获取messagequeue,其源码如下:

public handler(){
//直接把关联looper的mq作为自己的mq,因此它的消息将发送到关联looper的mq上
mlooper = looper.mylooper();
mqueue = mlooper.mqueue;
mcallback = null;
}

然后我们的流程图可以多些内容,如下所示:

深入浅析Android消息机制

我们接下来看一下dispatchmessage() 方法,在该方法中实际上只是一个分发方法,如果runable类型的callback为空,则执行handlermessage来处理消息,该方法为空,需要覆写。如果不为空,则执行handlecallback。实际上,如果我们用handle的post方法,则就执行了callback,如果用sendmessage,则就执行了handlemessage
这里无论是post(runnable callback)还是handlermessage实际上都是在调用一个方法sendmessagedelayed(message msg) 只不过handlermessage是直接接受一个参数,而runable callback实际上是将这个runable对象赋给了message对象的callback成员变量,最后将message对象插入消息队列里面。最后looper不断从messagequeue中读取消息,并且调用handler的dispatchmessage消息,在根据callback是否为空,来采用不同的方法执行。android消息机制分析到此结束。

回到最开始

我们这次知道了为什么要在主线程中实例化handler对象才能更新ui刷新,因为只有发送到ui线程的消息,才能被ui线程的handler处理,如果我们要在非ui线程中,实例化handler,则必须先将线程变成looperthread,在实例化。也就是说执行如下的代码:

loop.prepare();
hander = new handler;

loop.loop

至于原因相信读完上面的讲解,应该知道。
现在我们看一下我们最开始的代码,最后脑补一下handler的工作流程。

class myhandler extends handler{
@override
public void handlemessage(message msg){
//更新ui
}
}
myhandler mhandler = new myhandler();
new thread(){
public void run(){
mhandler.sendemptymessage(123);
};
}.start(); 

在handler实例化成mhandler的时候,系统通过handler默认的构造函数完成了handler与looper的关联,并通过looper关联到了messagequeue。而主线程的looper则早在系统启动的时候通过loop.prepare就已经构造完成了,并与ui线程通过threadlocal关联起来,然后在新的线程中执行mhandler.sendemptymessage,将message发送给了messagequeue,looper.loop在循环的时候,不断取出message,交给handler处理,在我们覆写的handlemessage中,识别出我们发送的消息,将消息处理。当然这里只是一个empty消息,所以在handlemessage中没有去执行msg.what的判断。

以上内容是小编给大家介绍的android消息机制,希望对大家有所帮助!