}
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池,加锁
sPool是个什么东西字面上看是个池子,但是从定义上看,是一个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;
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();
}
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是个啥,自己去看吧。
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+架构视频+面试文档+源码笔记
330页 PDF Android核心笔记
几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题
PDF和思维脑图,包含知识脉络 + 诸多细节
Android进阶系统学习视频
r的实现原理 尾声
开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。
这是我在这行工作10几年积累的一些资料,如果还想继续在这行业走下去的,或者现在打算跳槽,可以**私信【学习】**我愿意把资料免费分享给大家。
或者直接点击下面链接领取
Android学习PDF+架构视频+面试文档+源码笔记
330页 PDF Android核心笔记
[外链图片转存中…(img-ltdWB4Tq-1643953937269)]
几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题
[外链图片转存中…(img-hxYVBFUo-1643953937270)]
[外链图片转存中…(img-eM8IDbXk-1643953937271)]
PDF和思维脑图,包含知识脉络 + 诸多细节
[外链图片转存中…(img-97T7cFP4-1643953937271)]
Android进阶系统学习视频