天天看点

android message的作用,Android消息机制之Message解析(面试)

在android的消息机制中,Message其充当着信息载体的一个角色,通俗的来说,我们看作消息机制就是个工厂的流水线,message就是流水线上的产品,messageQueue就是流水线的传送带。之前做面试官的时候,经常会问面试者关于message的问题,如:

1.聊一下你对Message的了解。

2.如何获取message对象

3.message的复用(如果以上问题能答对,加分)

在下面我带着这三个问题,从这段代码开始逐一解析。

Handler handler = new Handler();

private void doSth() {

//开启个线程,处理复杂的业务业务

new Thread(new Runnable() {

@Override

public void run() {

//模拟很复杂的业务,需要1000ms进行操作的业务

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

handler.post(new Runnable() {

@Override

public void run() {

//在这里可以更新ui

mTv.setText("在这个点我更新了:" + System.currentTimeMillis());

}

});

}

}).start();

}

我们创建了一个handler,在doSth()中开启线程模拟处理复杂业务,最后通过handler的post返回结果进行UI操作(子线程不能进行操作UI,后话),我们先从handler的post开始看起,

Handler.java:

public final boolean post(Runnable r) {

//通过getPostMessage获取了message,再往下看

return sendMessageDelayed(getPostMessage(r), 0);

}

在post中,我们传进一个Runnable参数,我们发现有一个getPostMessage(r)函数,我们先从getPostMessage()下手。

Handler.java:

private static Message getPostMessage(Runnable r) {

//在这里,获取一个message,把我们的任务封装进message

Message m = Message.obtain();

m.callback = r;

return m;

}

从getPostMessage函数可得,我们把参数Runnable封装进去message的callback变量中,在这里埋伏一个很重要的概念,在Handler的源码中,是如何获取message对象的。顾名思义,在getPostMessage中,我们就是为了获取把runnable封装好的message。这样,我们可以返回上一层,继续看函数sendMessageDelayed(Message,long)。

Handler.java:

public final boolean sendMessageDelayed(Message msg, long delayMillis) {

//顾名思义的delay,也就是延迟,在上一层我们看到了post里传参是0,继续往下看

if (delayMillis < 0) {

delayMillis = 0;

}

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

//在这里判断handler里的队列是否为空,如果为空,handler则不能进行消息传递,因为生产线的传送带都没有的话,还怎么进行传送

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w("Looper", e.getMessage(), e);

return false;

}

return enqueueMessage(queue, msg, uptimeMillis);

}

在handler中,存在着sendMessageDelayed最终会用sendMessageAtTime,只是sendMessageDelayed中传参为0,使得sendMessageAtTime这函数最大程度能复用,我们继续往enqueueMessage函数看去。

Handler.java

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

//在message中放一个标记

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

//在这里把消息放到队列里面去

return queue.enqueueMessage(msg, uptimeMillis);

}

在enqueueMessage函数中,我们发现有个入参queue,这个入参就是消息队列,也就是之前我所说的流水线的传送带,message需要通过传messagequeue进行传递,我们继续往下探索。

MessageQueue.java:

boolean enqueueMessage(Message msg, long when) {

//这里通过之前的判断,之前放的目标,还有这个消息是否已经在使用了,都需要判断

//还记得之前我们看到的Message是怎么获取的吗?Message.obtain(),这里需要判断msg.isInUse,是否已经在使用这个消息

if (msg.target == null) {

throw new IllegalArgumentException("Message must have a target.");

}

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

//这里就是真正把message放到队列里面去,并且循环复用。

synchronized (this) {

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

Log.w(TAG, e.getMessage(), e);

msg.recycle();

return false;

}

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

if (p == null || when == 0 || when < p.when) {

// New head, wake up the event queue if blocked.

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

// Inserted within the middle of the queue. Usually we don't have to wake

// up the event queue unless there is a barrier at the head of the queue

// and the message is the earliest asynchronous message in the queue.

needWake = mBlocked && p.target == null && msg.isAsynchronous();

Message prev;

for (;;) {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p; // invariant: p == prev.next

prev.next = msg;

}

// We can assume mPtr != 0 because mQuitting is false.

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

enqueueMessage先判断之前的target是否为空,以及这个message是否已使用,后面的代码则是把message放进队列中,往下我们就不探究了,我们看到最终返回的结果return true.

我们看回来此段代码:

public final boolean post(Runnable r) {

//通过getPostMessage获取了message,再往下看

return sendMessageDelayed(getPostMessage(r), 0);

}

@return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting.

我们一层一层往下探索,无非就是把这个这个执行UI操作的Runnable封装成message,再将这个message放进我们的消息队列messagequeue中。在post如果返回true则成功添加进去消息队列,如果返回false则代表失败。

这个流程相信大家也清晰了吧,现在我之前所说的问题,handler中如何获取message对象的。

Handler.java:

private static Message getPostMessage(Runnable r) {

//在这里,获取一个message,把我们的任务封装进message

Message m = Message.obtain();

m.callback = r;

return m;

}

在这里,为什么Message不是通过new一个对象,而是通过其静态函数obtain进行获取?

我们通过其源码继续探索:

Message.java:

public static Message obtain() {

synchronized (sPoolSync) {

if (sPool != null) {

Message m = sPool;

sPool = m.next;

m.next = null;

m.flags = 0; // clear in-use flag

sPoolSize--;

return m;

}

}

return new Message();

}

Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases.

我们从注释中看到pool这个词,这个就是池,大家应该也听过过线程池,对象池,没错,我们获取的message对象优先在这个message池里获取,如果池里没有再new一个新的Message.

我们先了解一下,这里面的sPool、next、sPoolSize到底是什么东西。

Message.java:

//池里的第一个对象

private static Message sPool;

//对象池的长度

private static int sPoolSize = 0;

//连接下一个message的成员变量

// sometimes we store linked lists of these things

Message next;

在Message这个类中,存在着一个全局变量sPool,sPoolSize则是对象池中的数量,还有一个成员变量next.我们得理清一下sPool跟next到底存在着什么关系。在这先提出一个问题,我们看了那么久的池,怎么没看到类似Map这样的容器呢?Message对象池其实是通过链表的结构组合起来的池。

android message的作用,Android消息机制之Message解析(面试)

Paste_Image.png

上面有三个message,分别为message1、message2、message3

他们的连接关系分别通过其成员变量next进行衔接,举个例子:

message1.next=message2

message2.next=message3

......

以此类推,那么我们了解了message的next有什么作用,那么sPool呢?

我们注意到sPool是全局变量,我们又看回obtain函数中,是怎么样获取的。

Message.java:

public static Message obtain() {

synchronized (sPoolSync) {

if (sPool != null) {

//在池中获取message也是从表头获取,sPool赋值给message,

//同时把其连接的next赋值给sPool(这样,连接起来的message从第二个位置放到表头上了),赋值后设置next为空

Message m = sPool;

sPool = m.next;

m.next = null;

m.flags = 0; // clear in-use flag

sPoolSize--;

return m;

}

}

return new Message();

}

我们看到源码中,先判断sPool为不为空,为空代码这个池的数量为0,不能从池里获取到message.那如果不空,先将sPool赋值给message,再将这个message的下一个next赋值给sPool,赋值完后将message的next设为空,这不就是从表头里获取数据,sPool就是表头的第一个message。如:

message1是表头第一个元素,sPool也是表头,指向message1。当message1从池中取出来时候,message1连接的message2(通过next),成为了表头,同时sPool也指向新的表头,sPoolSize的数量也相应的需要减少。

通过以上例子,我们了解message的结构,也明白了message如何获取,别忘了我们的message除了在池里获取,还能通过创建一个新的实例,那么,新的实例是怎么放进池的,下面开始看看message的回收。

Message.java:

public void recycle() {

//如果还在使用这个消息,不能进行回收--通过flag进行标示

if (isInUse()) {

if (gCheckRecycle) {

throw new IllegalStateException("This message cannot be recycled because it "

+ "is still in use.");

}

return;

}

recycleUnchecked();

}

Recycle()函数是怎样调用的,暂且先不讨论,我们先看看其回收的机制,先判断这个message是否使用状态,再调用recycleUnchecked(),我们重点看看这个函数。

Message.java

void recycleUnchecked() {

//这里才是真正回收message的代码,把message中的状态还原

// Mark the message as in use while it remains in the recycled object pool.

// Clear out all other details.

flags = FLAG_IN_USE;

what = 0;

arg1 = 0;

arg2 = 0;

obj = null;

replyTo = null;

sendingUid = -1;

when = 0;

target = null;

callback = null;

data = null;

//如果message池的数量未超过最大容纳量(默认最大50个message容量),将此message回收,以便后期复用(通过obtain)

//在代码中可知,message在recycle()中进行回收的

//假设池中的message数量为0时,sPool全局变量为null

//当我们把第一个message放进去池的时候,sPool(这个时候还是null)赋值给next,而message本身赋值给全局变量sPool,也就是每次回收的message都会插入表头

//这样一来就形成了链表的结构,也就是我们所说的对象池

synchronized (sPoolSync) {

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

我们看到源码中的最后几行,如果池中现有的数量少于最大容纳量,则可将message放进池中,我们又看到了头疼的next跟sPool,我先举个例子,脑补一下:

1.我有一个message1,我用完了,系统回收这个message1

2.现有的池,表头是message2。

结合以上两个条件再根据源码能得出:

sPool跟message2都指向同一个地址,因为message2是表头,那么message1回收的时候,sPool赋值给了message1的next. 也就是说,message1成了新的表头,同时池的数量sPoolSize相应的增加。

message的回收就是将其放到池的表头,包括获取message也是从表头上获取。

总结:

Android的消息机制都通过message这个载体进行传递消息,如果每次我们都通过new这样的方式获取对象,那么必然会造成内存占用率高,降低性能。而通过对其源码的学习,了解message的缓存回收机制,同时也学习其设计模式,这就是我们所说的享元模式,避免创建过多的对象,提高性能。