Callback
:一个接口,内部有一个handleMessage
方法,当实例化Handler
时可以使用Callback
接口,避免自己去实现Handler
子类
async
:是否是异步消息,true
表示是异步消息,false
表示是同步消息,默认是同步消息。
异步消息相对于同步消息的而言的,表示消息不会受到中断或者事件的影响其全局顺序。异步消息是不受到
MessageQueue.enqueueSyncBarrier(long)
的同步障碍影响。
Handler
构造器完成了mQueue
、mCallBack
和mAsynchronous
的赋值,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
调用了MessageQueue
的enqueueMessage
的方法,这个方法很重要,这是Hander
与MessageQueue
关联的环节
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)
这行代码底层实现的就是MessageQueue
的enqueueMessage
入列操作,接下来看MessageQueue
的入列操作。
补充:MessageQueue
本质上是一个单链表结构,翻译过来就是一个消息队列,用于存放Handler
发送过来的消息,入列操作实际上就是链表的插入操作,我们可以通过源码来验证它。
Message
Message
是上述例子中所说的消息,当我们在子线程完成耗时操作,发送消息更新UI
。这里的消息指的就是Message
对象,Message
属性有
- what 用户定义的Message的标识符用以分辨消息的内容。
当我们在主线程执行
handleMessage
方法时,需要判断接收的是哪个消息以便执行,这个what
就是用来区分不同消息的属性。
arg1
和arg2
保存几个整形的数值
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
,当spool
为null
时,调用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层代码的相关初始化
MessageQueue
的enqueueMessage
入列操作
//入队操作
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
,进而执行MessageQueue
的enqueueMessage
方法后,怎么实现在MessageQueue
队列中插入消息。
MessageQueue
翻译成队列,实际上它实现的是一个单链表结构,那为什么不使用数组结构?
单链表的插入删除效率比数组高,而数组的查找效率比单链表高,
MessageQueue
就是利用了单链表插入删除效率高的优点,当我们使用Handler
发送消息的时候,实际上执行的是一个入列操作,而Looper
在处理收到的消息的时候,MessageQueue
实际上执行的是一个出列操作。Handler
机制中频繁使用的是MessageQueue
的入列出列操作,所以使用单链表结构来实现。
MessageQueue
的入列操作,实际上就是单链表的插入操作,当然,不仅仅是单链表插入一个数据这么简单,这里还需要执行MessaeQueue
这个消息队列一些特有的方法。
用一句话概括,enqueueMessage
方法就是遍历链表,按照when
属性的大小将插入的数据进行排序。
既然是单链表插入,那么插入消息msg
的条件是什么?或者说根据什么来确定插入的位置?
Message
中when
属性的作用:用于存储发送消息的时间点,以毫秒为单位
-
当消息队列中没有消息,将消息队列的头结点指针指向
msg
-
当消息
Message
的属性when
==0(表示立即执行),或者当前头节点的when
<此时插入消息的when
时,将消息msg
作为头结点插入在原有的消息前 -
不符合以上两点,遍历链表,找到符合
when
<当前遍历到的链表元素的when
的位置,插入在当前遍历到的当前链表元素的前面 -
若都不符合,插入在链表末尾
Looper
Hander
使用sendMessage
发送消息,sendMessage
又调用了MessageQueue
的enqueueMessage
方法,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
实现关联?
使用Handler
,Handler
的构造函数中,Handler
里面MessageQueue
就是Looper
的MessageQueue
,Handler
默认采用的是当前线程TLS
中的Looper对象,只要执行了Looper.prepare()
方法,就可以获取到有效的Looper
对象,同时又执行了 mQueue = mLooper.mQueue
语句,把当前Handler
的中MessageQueue
对象mQueue
的引用指向了Looper
对象mLooper
中MessageQueue
对象mQueue
的引用,即当前Hander
中,MessageQueue
对象就是Looper
中MessageQueue
中的对象。
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
又在消息队列中取出消息,分发给主线程处理。在这个过程中,主线程里面的MessageQueue
和Looper
是怎么实现关联的?看看Looper
的looper
方法
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到消息对象池中
分析第四步,进入MessageQueue
的next
方法,这是整个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-TT8ITo1Q-1715159067246)]
[外链图片转存中…(img-LEOQofDz-1715159067247)]
[外链图片转存中…(img-tsGevc0r-1715159067248)]
[外链图片转存中…(img-XeGuBFrs-1715159067248)]
有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!