闲聊Android中的Handler机制

Callback:一个接口,内部有一个handleMessage方法,当实例化Handler时可以使用Callback接口,避免自己去实现Handler子类

async:是否是异步消息,true表示是异步消息,false表示是同步消息,默认是同步消息。

异步消息相对于同步消息的而言的,表示消息不会受到中断或者事件的影响其全局顺序。异步消息是不受到MessageQueue.enqueueSyncBarrier(long)的同步障碍影响。

Handler构造器完成了mQueuemCallBackmAsynchronous的赋值,mQueue是通过mLooper获取的,一个线程只有一个Looper,而mQueue又是通过Looper获取的,就样就实现了一个线程只有一个MessageQueue

Handler发消息给MessageQueue

从发消息开始,进入sendMessage方法内部,由sendMessage->…->enqueueMessage

public final boolean sendMessage(@NonNull Message msg) {

return sendMessageDelayed(msg, 0);

}

进入enqueueMessage

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,

long uptimeMillis) {

msg.target = this;

msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

enqueueMessage调用了MessageQueueenqueueMessage的方法,这个方法很重要,这是HanderMessageQueue关联的环节

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,

long uptimeMillis) {

msg.target = this;

msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis);

}

return queue.enqueueMessage(msg, uptimeMillis);

现在来看看这个queue是什么,依次向上查找,发现了这一行代码

MessageQueue queue = mQueue;

mQueue则是Handler这个类中的一个成员变量

final MessageQueue mQueue;

Handler发送消息的方法sendMessage最终执行的是queue.enqueueMessage(msg, uptimeMillis),而queue是一个MessageQueue对象,所以可以这么理解,Handler发送消息,实际上就是执行MessageQueue的一个入队操作enqueueMessage

对应结合上面的例子,子线程使用Handler发送了一个Message对象, handler.sendMessage(msg)这行代码底层实现的就是MessageQueueenqueueMessage入列操作,接下来看MessageQueue的入列操作。

补充:MessageQueue本质上是一个单链表结构,翻译过来就是一个消息队列,用于存放Handler发送过来的消息,入列操作实际上就是链表的插入操作,我们可以通过源码来验证它。

Message

Message是上述例子中所说的消息,当我们在子线程完成耗时操作,发送消息更新UI。这里的消息指的就是Message对象,Message属性有

  • what 用户定义的Message的标识符用以分辨消息的内容。

当我们在主线程执行handleMessage方法时,需要判断接收的是哪个消息以便执行,这个what就是用来区分不同消息的属性。

  • arg1arg2 保存几个整形的数值
  • obj 用来保存序列化的对象
  • when 用于存储发送消息的时间点,以毫秒为单元
Message对象的创建

在上述例子中,有这么一段代码 Message msg = Message.obtain(),它的作用是获取一个Message实例,

为什么不是通过new获取呢?

为了复用对象,可避免**避免重复创建实例对象**,达到节约内存的目的。

public static Message obtain() {

// 保证线程安全

synchronized (sPoolSync) {

if (sPool != null) {

Message m = sPool;

sPool = m.next;

m.next = null;

// flags为移除使用标志

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

sPoolSize–;

return m;

}

}

return new Message();

}

spool是一个Message对象,默认是null,当spoolnull时,调用obtain方法会重新new一个Message对象,既然官方不推荐使用new获取对象,那么大部分情况下spool是不为空的。

消息是怎么被放入消息对象池的?

查看recycleUnchecked方法

void recycleUnchecked() {

// 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;

//拿到同步锁,以避免线程不安全

synchronized (sPoolSync) {

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

可以看出spool在这里面也有赋值,当一个Message被回收时,会把这个Message对象作为消息对象池中下一个被复用的对象,并且将消息对象池的数量+1。也就是说,当系统回收消息的时候,会优先把他放入消息池,等待下一次复用。

MessageQueue

MessageQueue对象的创建

MessageQueue的构造函数

MessageQueue(boolean quitAllowed) {

mQuitAllowed = quitAllowed;

mPtr = nativeInit();

}

MessageQueue只是有一个构造函数,该构造函数是包内可见的,其内部就两行代码,分别是设置了MessageQueue是否可以退出和native层代码的相关初始化

MessageQueueenqueueMessage入列操作

//入队操作

boolean enqueueMessage(Message msg, long when) {

//判断target变量是否为null,能为null的只有障栅,而障栅入队则是通过postSyncBarrier()方法入队,所以msg的target一定有值,为null抛出异常

if (msg.target == null) {

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

}

//加入同步锁

synchronized (this) {

//判断msg的标志位,因为此时的msg应该是要入队,意味着msg的标志位应该显示还未被使用。如果显示已使用,明显有问题,直接抛异常。

if (msg.isInUse()) {

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

}

//判断消息队列是否正在被关闭,如果正在被关闭,则入队失败,回收消息

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的标记为,msg标记位显示已使用

msg.markInUse();

//设置msg的when

msg.when = when;

Message p = mMessages;

boolean needWake;

//p==null表示链表头部元素为null

//when==0表示立即执行

//when<p.when表示msg的执行时间早于链表中头部元素的时间

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

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

//设置msg成消息队列中链表头部元素

msg.next = p;

mMessages = msg;

needWake = mBlocked;

//如果上面三个条件都不满足则说明要把msg插入到中间的位置,不需要插入到头部

} 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表示是否唤醒消息队列,如果头部元素不是障栅或者异步消息而且还是插入中间的位置,不需要唤醒消息队列

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

Message prev;

//进入死循环

for (;😉 {

//p指向的是mMessage,prev=p,在下一次循环时,prev指向了第一个message,以此类推

prev = p;

//移向下一个元素

p = p.next;

//p==null说明没有下一个元素,消息队列到头了,跳出循环

//when<p.when则说明当前入队的这个message的执行时间是小于队列中这个任务的执行时间的,

// 也就是说入队的这个message比队列中的message先执行,说明这个位置刚好是适合这个messgae的,跳出循环

//如果下面的两个条件都不满足,则说明这个位置还不是放置这个需要入队的message,则继续跟链表中后面的元素,】

// 也就是继续跟消息队列中的下一个消息进行对比,直到满足条件或者到达队列的末尾。

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

break;

}

//因为没有满足条件,说明队列中还有消息,不需要唤醒。

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

needWake = false;

}

}

//将入队的这个消息的next指向循环中获取到的应该排在这个消息之后的message。

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

//将masg前面的message.next指向了msg

prev.next = msg;

}

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

//如果需要唤醒,则唤醒

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

源码的详细注释已经在上面给出来了,现在我们只分析enqueueMessage所做的主要功能,就是Handler在发送消息,执行sendMessage,进而执行MessageQueueenqueueMessage方法后,怎么实现在MessageQueue队列中插入消息。

MessageQueue翻译成队列,实际上它实现的是一个单链表结构,那为什么不使用数组结构?

单链表的插入删除效率比数组高,而数组的查找效率比单链表高,MessageQueue就是利用了单链表插入删除效率高的优点,当我们使用Handler发送消息的时候,实际上执行的是一个入列操作,而Looper在处理收到的消息的时候,MessageQueue实际上执行的是一个出列操作。Handler机制中频繁使用的是MessageQueue的入列出列操作,所以使用单链表结构来实现。

MessageQueue的入列操作,实际上就是单链表的插入操作,当然,不仅仅是单链表插入一个数据这么简单,这里还需要执行MessaeQueue这个消息队列一些特有的方法。

用一句话概括,enqueueMessage方法就是遍历链表,按照when属性的大小将插入的数据进行排序。

既然是单链表插入,那么插入消息msg的条件是什么?或者说根据什么来确定插入的位置?

Messagewhen属性的作用:用于存储发送消息的时间点,以毫秒为单位

  1. 当消息队列中没有消息,将消息队列的头结点指针指向msg

  2. 当消息Message的属性when==0(表示立即执行),或者当前头节点的when<此时插入消息的when时,将消息msg作为头结点插入在原有的消息前

  3. 不符合以上两点,遍历链表,找到符合when<当前遍历到的链表元素的when的位置,插入在当前遍历到的当前链表元素的前面

  4. 若都不符合,插入在链表末尾

Looper

Hander使用sendMessage发送消息,sendMessage又调用了MessageQueueenqueueMessage方法,enqueueMessage插入数据的原理就是链表的插入,在开头先说的结论中,是Looper取出消息交给Handler处理的,关于Looper就会有下面的问题,

Looper对象是怎么获取的 ?

查看Looper的构造器,里面保存了一个MessageQueue对象和一个Thread对象,实现了Looper与线程的关联。Looper是线程私有的,每个线程都有一个Looper对象。

并且构造器它是私有的,即Looper对象不能直接创建

//Looper这个类的对象不能直接创建,必须通过Looper来的两个静态方法prepare()/prepareMainLooper()来间接创建

private Looper(boolean quitAllowed) {

//创建MessageQueue对象

mQueue = new MessageQueue(quitAllowed); //实现了Looper与MessageQueue的关联

//记录当前线程

mThread = Thread.currentThread(); //实现了Thread与Looper的关联

}

**那么Looper对象是怎么创建的?**查看prepare()方法

public static void prepare() {

prepare(true);

}

实际调用的是prepare(true),查看prepare(true)

/**

  • @param quitAllowed 表示Looper是否允许退出,true表示允许退出,fasle表示不允许退出

*/

private static void prepare(boolean quitAllowed) {

//每个线程只允许执行一次该方法,第二次执行的线程的TLS已有数据,则会抛出异常

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

//创建Looper对象,并且保存到当前线程的TLS区域

sThreadLocal.set(new Looper(quitAllowed));

}

通过源码可以看出,Looper对象的创建就是执行了 sThreadLocal.set(new Looper(quitAllowed))这个语句,所以回到上面可以知道,Looper.prepare()可以创建Looper对象。并且保存在当前线程的TLS区域,确保不同的线程拥有一个不同的Looper对象,

上面分析中确保了不同线程有不同的Looper对象,那么怎么保证同一个线程只能有一个Looper对象?

代码中执行

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

判断当前线程中是否已有Looper对象,如果有,还企图新建Looper对象的话,就会抛出一个"Only one Looper may be created per thread"异常,表示一个线程只能拥有一个Looper对象,这样就可以保证在正常运行的情况下,一个线程只有一个Looper对象。

主线程也是通过这种方法获取Looper对象吗?

Android主线程ActivityThread在启动时,会在入口方法main中通过Looper.prepareMainLooper()来创建主线程的Looper对象以及MessageQueue,这也是我们可以在主线程没使用Looper.prepare而能使用Handler传递消息的原因。

public static void main(String[] args) {

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);

Process.setArgV0(“”);

Looper.prepareMainLooper();//通过此方法创建Looper对象

// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.

// It will be in the format “seq=114”

long startSeq = 0;

if (args != null) {

for (int i = args.length - 1; i >= 0; --i) {

if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {

startSeq = Long.parseLong(

args[i].substring(PROC_START_SEQ_IDENT.length()));

}

}

}

ActivityThread thread = new ActivityThread();

thread.attach(false, startSeq);

}

ActivityThread通过调用Looper.MainLooper()来创建Looper对象,查看Looper.MainLooper()

@Deprecated

public static void prepareMainLooper() {

//设置不允许退出的Looper

prepare(false);

synchronized (Looper.class) {

//将当前的Looper保存为looper,每个线程只允许执行一次

if (sMainLooper != null) {

throw new IllegalStateException(“The main Looper has already been prepared.”);

}

sMainLooper = myLooper(); //给主线程的looper赋值

}

}

标记为废弃的原因:主线程的Looper是由系统自动创建的,无需用户自行调用。

Looper怎么与MessageQueue实现关联?

使用HandlerHandler的构造函数中,Handler里面MessageQueue就是LooperMessageQueueHandler默认采用的是当前线程TLS中的Looper对象,只要执行了Looper.prepare()方法,就可以获取到有效的Looper对象,同时又执行了 mQueue = mLooper.mQueue语句,把当前Handler的中MessageQueue对象mQueue的引用指向了Looper对象mLooperMessageQueue对象mQueue的引用,即当前Hander中,MessageQueue对象就是LooperMessageQueue中的对象。

public Handler(@Nullable Callback callback, boolean async) {

if (FIND_POTENTIAL_LEAKS) {

final Class<? extends Handler> klass = getClass();

//如果是匿名类、内部类、方法内部类,且没有声明为static,则存在内存泄漏风险,要打印日志

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.prepare(),抛出异常

if (mLooper == null) {

throw new RuntimeException(

"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");

}

//把Looper的Queue赋值给Handle的Queue

mQueue = mLooper.mQueue;

//mCallBack赋值

mCallback = callback;

//mAsynchronous赋值

mAsynchronous = async;

}

Looper是怎么取出消息的?

在子线程使用Handler发送消息,消息插入主线程的MessageQueue消息队列中,Looper又在消息队列中取出消息,分发给主线程处理。在这个过程中,主线程里面的MessageQueueLooper是怎么实现关联的?看看Looperlooper方法

public static void loop() {

//第1步

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

//第2步

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();

//第3步

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

final Printer logging = me.mLogging;

if (logging != null) {

logging.println(">>>>> Dispatching to " + msg.target + " " +

msg.callback + ": " + msg.what);

}

final long traceTag = me.mTraceTag;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {

Trace.traceBegin(traceTag, msg.target.getTraceName(msg));

}

try {

// 第5步

msg.target.dispatchMessage(msg);

} finally {

if (traceTag != 0) {

Trace.traceEnd(traceTag);

}

}

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);

}

// 第6步

msg.recycleUnchecked();

}

}

looper方法内的具体操作有:

第1步 获取Looper对象

第2步 获取MessageQueue消息队列对象

第3步 while()死循环遍历

第4步 通过queue.next()来从MessageQueue的消息队列中获取一个Message msg对象

第5步 通过msg.target. dispatchMessage(msg)来处理消息

第6步 通过msg.recycleUnchecked()方来回收Message到消息对象池中

分析第四步,进入MessageQueuenext方法,这是整个handler机制的核心方法

Message next() {

// 如果消息循环已经退出了。则直接在这里return。因为调用disposed()方法后mPtr=0

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

//记录空闲时处理的IdlerHandler的数量

int pendingIdleHandlerCount = -1; // -1 only during first iteration

// native层用到的变量 ,如果消息尚未到达处理时间,则表示为距离该消息处理事件的总时长,

// 表明Native Looper只需要block到消息需要处理的时间就行了。 所以nextPollTimeoutMillis>0表示还有消息待处理

int nextPollTimeoutMillis = 0;

for (;😉 {

if (nextPollTimeoutMillis != 0) {

//刷新下Binder命令,一般在阻塞前调用

Binder.flushPendingCommands();

}

// 调用native层进行消息标示,nextPollTimeoutMillis 为0立即返回,为-1则阻塞等待。

nativePollOnce(ptr, nextPollTimeoutMillis);

//加上同步锁

synchronized (this) {

// 获取开机到现在的时间

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

// 获取MessageQueue的链表表头的第一个元素

Message msg = mMessages;

// 判断Message是否是障栅,如果是则执行循环,拦截所有同步消息,直到取到第一个异步消息为止

if (msg != null && msg.target == null) {

// 如果能进入这个if,则表面MessageQueue的第一个元素就是障栅(barrier)

// 循环遍历出第一个异步消息,这段代码可以看出障栅会拦截所有同步消息

do {

prevMsg = msg;

msg = msg.next;

//如果msgnull或者msg是异步消息则退出循环,msgnull则意味着已经循环结束

} while (msg != null && !msg.isAsynchronous());

}

// 判断是否有可执行的Message

if (msg != null) {

// 判断该Mesage是否到了被执行的时间。

if (now < msg.when) {

// 当Message还没有到被执行时间的时候,记录下一次要执行的Message的时间点

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else {

// Message的被执行时间已到

// 从队列中取出该Message,并重新构建原来队列的链接

// 此时说明说有消息,所以不能阻塞

mBlocked = false;

// 如果还有上一个元素

if (prevMsg != null) {

//上一个元素的next(越过自己)直接指向下一个元素

prevMsg.next = msg.next;

} else {

//如果没有上一个元素,则说明是消息队列中的头元素,直接让第二个元素变成头元素

mMessages = msg.next;

}

// 因为要取出msg,所以msg的next不能指向链表的任何元素,所以next要置为null

msg.next = null;

if (DEBUG) Log.v(TAG, "Returning message: " + msg);

// 标记该Message为正处于使用状态,然后返回Message

msg.markInUse();

return msg;

}

} else {

// 没有任何可执行的Message,重置时间

nextPollTimeoutMillis = -1;

}

// 关闭消息队列,返回null,通知Looper停止循环

if (mQuitting) {

dispose();

return null;

}

// 当第一次循环的时候才会在空闲的时候去执行IdleHandler,从代码可以看出所谓的空闲状态

// 指的就是当队列中没有任何可执行的Message,这里的可执行有两要求,

// 即该Message不会被障栅拦截,且Message.when到达了执行时间点

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
msg.next = null;

if (DEBUG) Log.v(TAG, "Returning message: " + msg);

// 标记该Message为正处于使用状态,然后返回Message

msg.markInUse();

return msg;

}

} else {

// 没有任何可执行的Message,重置时间

nextPollTimeoutMillis = -1;

}

// 关闭消息队列,返回null,通知Looper停止循环

if (mQuitting) {

dispose();

return null;

}

// 当第一次循环的时候才会在空闲的时候去执行IdleHandler,从代码可以看出所谓的空闲状态

// 指的就是当队列中没有任何可执行的Message,这里的可执行有两要求,

// 即该Message不会被障栅拦截,且Message.when到达了执行时间点

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:

[外链图片转存中…(img-pkk6vI7z-1715421844969)]
[外链图片转存中…(img-YufCMULd-1715421844970)]
[外链图片转存中…(img-ZLiF9DG0-1715421844970)]
[外链图片转存中…(img-ErQ72ITS-1715421844971)]

有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值