}
else if (oldThr > 0)
//通过threshold设置新数组容量
newCap = oldThr;
else {
…
}
if (newThr == 0) {
…
}
threshold = newThr;
@SuppressWarnings({“rawtypes”,“unchecked”})
//通过threshold设置table的初始容量
Node
table = newTab;
…
return newTab;
}
通过以上操作,不论初始化HashMap的时候,传入的容量是多少,都能保证HashMap的容量是2x。
Handler源码分析
===========
一直在纠结一个事,因为自己不爱看大段的文字。
自己写总结的时候到底要不要贴上部分源码。
后来硬着头皮加上了,因为源码里很多东西比自己写的清楚。
RTFSC
Handler Message MessageQueue Looper ThreadLocal
Handler机制的完整流程
Message#obtain()
Handler#
Handler#send/post
MQ#enqueueMessage() *消息的排序
Looper#prepareMainLooper()
Looper#prepare()
ThreadLocal机制
Looper#loop()
MQ#next() *延迟消息的处理
Handler#dispatchMessage()
Message#obtain()
message中的变量自己去看源码,target,callback,when
从handler或者是message的源码中都可以看到,生成Message的最终方法都是调用obtain。
ps:如果你非要用Message的构造方法,那么看清楚他的注释,构造方法上面的注释写的也很清楚,
/**
*/
public Message() {
}
下面来分析一波obtain()方法:
任意线程都可以创建message,所以为了维护好内部的messge池,加锁
字面上看是个池子,但是从定义上看,是一个Message。为什么还要说成一个message池呢?因为Message内部有个next变量,Message做成了一个链表的形式。这个池子怎么存储message呢?稍后分析源码。
通过读obtain()的源码,结合链表的知识,很容易理解Message中Spool的原理。
public static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
/**
Return a new Message instance from the global pool. Allows us to
avoid allocating new objects in many cases.
*/
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();
}
通过查看调用链,我们能够看到在MQ中enqueueMessage调用了recycle(),而recyle中也是通过链表的形式对sPool进行维护。源码简单易懂
下面来看下sPool是怎么维护的。
在recycleUnchecked()同样也是加了锁的。然后就是用链表的形式维护这个池子,size++
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
…
}
return;
}
recycleUnchecked();
}
/**
Recycles a Message that may be in-use.
Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
…
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
Handler
Handler类的源码总共不超过1000行,并且大部分都是注释,所以我们看该类源码的时候,更多的是看他的注释。静下心来看源码
构造方法
callback对象
dispatchMessage
Handler发送消息(send/post)
Handler发送消息的方式分为两种:
1.post
2.send
不论是post还是send(其他方法)方式,最终都会调用到sendMessageAtTime/sendMessageAtFrontOfQueue。执行equeueMessage,最终调用MQ#enqueueMessage(),加入到MQ中。
1. post方式
以post方式发送消息,参数基本上都是Runnable(Runnable到底是什么,这个要搞懂)。post方式发送的的消息,都会调用getPostMessage(),将runnable封装到Message的callbak中,调用send的相关方法发送出去。
ps:个人简单、误导性的科普Runnable,就是封装了一段代码,哪个线程执行这个runnable,就是那个线程。
2. send方式
以send方式发送消息,在众多的重载方法中,有一类比较容易引起歧义的方法,sendEmptyMessageXxx(),这类方法并不是说没有用到message,只是在使用的时候不需要传递,方法内部帮我们包装了一个Message。另一个需要关注的点是: xxxDelayed() xxxAtTime()
1.xxxDelayed()
借助xx翻译,得知 delayed:延迟的,定时的,推迟 的意思,也就是说,借助这个方法我们能做到将消息延迟发送。e.g:延迟三秒让View消失。ps:在我年幼无知的时候,总是搞懵这个方法,不会用。
在这个方法的参数中,我们看到如果传入的是毫秒值,那么会在delayMillis的基础上与SystemClock.uptimeMillis()
做个加法。然后执行sendMessageAtTime()。
SystemClock.uptimeMillis() 与 System.currentTimeMillis()
的区别自己去研究。
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
2.xxxAtTime()
在这个方法就更简单易懂了,执行的具体时间需要使用者自己去计算。
在Handler内的equeueMessage中,第一行的msg.target = this;
,将handler自身赋值到msg.target,标记了这个msg从哪来,这个要注意后面会用到。
MQ#enqueueMessage()
这个方法那是相当的关键
在此之前,我们一直鼓捣一个参数delayMillis/uptimeMillis,在这个方法里参数名变为了when,标明这个message何时执行,也是MQ对Message排序存储的依据。MQ是按照when的时间排序的,并且第一个Message最先执行。
在省去了众多目前不关心的代码后,加上仅存的一点数据结构的知识,得到msg在MQ中的存储形式。
mMessages
位于队列第一位置的msg,新加入到msg会跟他比较,然后找到合适的位置加入到队列中。
ps:记得在一次面试中,面试官问到延迟消息的实现思路,我照着源码说了一下。但是被问到:**每次新加入消息,都要循环队列,找到合适的位置插入消息,那么怎么保证执行效率。**我不知道他这么问是想考我优化这个东西的思路,还是他觉得我说错了。就犹豫了一下,没有怼回去。
boolean enqueueMessage(Message msg, long when) {
…
…
synchronized (this) {
…
…
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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;
}
…
…
}
return true;
}
以上几步,我们只是将要执行的msg加入到了队列中。接下来分析下什么时候执行msg。
再接再厉,马上就看到暑光了。
Looper#prepareMainLooper()
借助十几年英语学习积累下来的词汇量,加上我出色的看源码能力。看懂了这个方法的注释及Android系统在哪里执行了此方法。
面试被问到怎么在子线程创建Looper?
仔细看注释。Initialize the current thread as a looper…See also: {@link #prepare()}
这个方法,作为开发人员不需要调用它,但是作为一个高级技工还是要多少了解一点的,系统在三个位置调用了此方法,但是我只关心了AndroidThread这个类,AndroidThread是个啥,自己去看吧。
/**
Initialize the current thread as a looper, marking it as an
application’s main looper. The main looper for your application
is created by the Android environment, so you should never need
to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
Looper#prepare()
面试的时候经常被问到一个线程可以有多个looper吗?
看源码注释就得到了答案。
throw new RuntimeException("Only one Looper may be created per thread");
怎么保证每个线程只有一个looper呢?这里用到了ThreadLocal。
在自己创建的子线程中,如果想创建Looper,那么只需要调用Looper.prepare(),就会为当前线程创建一个looper了。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocal机制
ThreadLocal是个什么东西呢,他是个复杂的机制,毕竟从JAVA1.2就加入了机制,保证了每个线程自己的变量…
本人简单的、带有误导性的科普是:
类似一个Map,key是当前线程id,value就是你要保存的值
一定要自己深入了解该机制
Looper#loop()
这个方法也很关键,消息能够执行,起了很大作用。虽然个人感觉能看的代码很少,但是都很精炼啊。
获取looper,得到MQ
循环MQ得到可执行的msg
通过msg自身,去到他该去的地方msg.target.dispatchMessage(msg);
recycleUnchecked(),维护Message池
ps:曾经年少的我一度认为Looper就是主线程,完全因为这个loop()方法,当时看到在AndroidThread#main()中执行了Looper.loop(),而学过JAVA的都知道main()里面,如果没有耗时、子线程等其他操作,基本上执行到最后一行,就结束了。
但是为什么APP起来了,main()里面那么几行代码执行结束后,没有死掉呢。就是因为loop()里面有个for(;,当MQ中没有msg,那么会一直循环下去。
现在想来,还是太年轻了。这个只是一方面原因,其他线程也会调用Looper.prepare(),为自己创建looper,然后执行Looper.loop(),循环自己的MQ。
发现还是要多了解,多学习。
MQ#next()
这个方法负责把队列中的msg取出来,给到looper去执行。
这个方法也是一个for(;,当取到第一个msg的时候,如果没有到他该执行的时间,那么就等着,一直等,死等。得到可以执行的msg后,给到Looper。里面还有些native的方法,大家自己去看next()源码吧。
Handler#dispatchMessage()
在Looper#loop()中MQ#next()得到了msg,有这么一行msg.target.dispatchMessage(msg);
,在之前讲到了这个target是发送msg的那个handler(多个handler的情况下区分)。根据不同情况,对msg进行分发。如果有callback对象(post方式发送消息,或者new Handler(runnable)),就去执行Runnable.run()。其他情况回调到handleMessage(),在创建handler的地方处理这个msg。
/**
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
以上就是本人对Handler的总结。
写了这么多,已经累瘫在办公桌前,啥都不想干了。这可能是在高考语文结束后,想的最多的一次文字。
最后啰嗦一句
RTFSC(Read The Fucking Source Code)
为什么android设计只能UI线程更新UI
解决多线程并发的问题。多个线程更新UI可能发生并发问题,如果在多个线程中加锁,会导致程序页面有可能非常卡顿
提高界面更新的性能问题
架构设计的简单,因为android中封装了所有更新UI的操作,在开发中只需要在非UI中发送一个消息,就可以更新UI,对于开发人员来说节省了不少时间.
相关面试题
子线程Looper和Handler
延迟消息怎么处理
ThreadLocal作用
自己实现Handler机制
for (; 与while(true) 区别
看了些文章,自己动手试了试,.class文件。一毛钱的区别都没有。
有人说根据编译器不同会有差别,在我目前的能力认知范围内没差别。
Message next() {
…
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
…
}
}
在view绘制的时候,post到MQ的消息是不会被执行的,优先执行绘制时候的异步消息。
7.IdleHandler的实现原理
开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。
这是我在这行工作10几年积累的一些资料,如果还想继续在这行业走下去的,或者现在打算跳槽,可以**私信【学习】**我愿意把资料免费分享给大家。
或者直接点击下面链接领取
Android学习PDF+架构视频+面试文档+源码笔记
开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。
这是我在这行工作10几年积累的一些资料,如果还想继续在这行业走下去的,或者现在打算跳槽,可以**私信【学习】**我愿意把资料免费分享给大家。
或者直接点击下面链接领取
Android学习PDF+架构视频+面试文档+源码笔记
[外链图片转存中…(img-ltdWB4Tq-1643953937269)]
[外链图片转存中…(img-hxYVBFUo-1643953937270)]
[外链图片转存中…(img-eM8IDbXk-1643953937271)]
[外链图片转存中…(img-97T7cFP4-1643953937271)]