Android面试刨根问底之常用源码篇(一),下血本买的

}

else if (oldThr > 0)

//通过threshold设置新数组容量

newCap = oldThr;

else {

}

if (newThr == 0) {

}

threshold = newThr;

@SuppressWarnings({“rawtypes”,“unchecked”})

//通过threshold设置table的初始容量

Node[] newTab = (Node[])new Node[newCap];

table = newTab;

return newTab;

}

通过以上操作,不论初始化HashMap的时候,传入的容量是多少,都能保证HashMap的容量是2x。

Handler源码分析

===========

一直在纠结一个事,因为自己不爱看大段的文字。

自己写总结的时候到底要不要贴上部分源码。

后来硬着头皮加上了,因为源码里很多东西比自己写的清楚。

RTFSC

相关概念

Handler Message MessageQueue Looper ThreadLocal

Handler机制的完整流程

  1. Message#obtain()

  2. Handler#

  3. Handler#send/post

  4. MQ#enqueueMessage() *消息的排序

  5. Looper#prepareMainLooper()

  6. Looper#prepare()

  7. ThreadLocal机制

  8. Looper#loop()

  9. MQ#next() *延迟消息的处理

  10. Handler#dispatchMessage()

Message#obtain()

message中的变量自己去看源码,target,callback,when

从handler或者是message的源码中都可以看到,生成Message的最终方法都是调用obtain。

ps:如果你非要用Message的构造方法,那么看清楚他的注释,构造方法上面的注释写的也很清楚,

/**

  • Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).

*/

public Message() {

}

下面来分析一波obtain()方法:

  1. 为什么上来就是一个同步?

任意线程都可以创建message,所以为了维护好内部的messge池,加锁

  1. 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;

/**

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

这个方法也很关键,消息能够执行,起了很大作用。虽然个人感觉能看的代码很少,但是都很精炼啊。

  1. 获取looper,得到MQ

  2. 循环MQ得到可执行的msg

  3. 通过msg自身,去到他该去的地方msg.target.dispatchMessage(msg);

  4. 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。

/**

  • Handle system messages here.

*/

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

  1. 解决多线程并发的问题。多个线程更新UI可能发生并发问题,如果在多个线程中加锁,会导致程序页面有可能非常卡顿

  2. 提高界面更新的性能问题

  3. 架构设计的简单,因为android中封装了所有更新UI的操作,在开发中只需要在非UI中发送一个消息,就可以更新UI,对于开发人员来说节省了不少时间.

相关面试题

  1. 子线程Looper和Handler

  2. 延迟消息怎么处理

  3. ThreadLocal作用

  4. 自己实现Handler机制

  5. for (; 与while(true) 区别

看了些文章,自己动手试了试,.class文件。一毛钱的区别都没有。

有人说根据编译器不同会有差别,在我目前的能力认知范围内没差别。

  1. 同步消息屏障

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核心笔记

Android面试刨根问底之常用源码篇(一),下血本买的_第1张图片

  • 几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题

Android面试刨根问底之常用源码篇(一),下血本买的_第2张图片

Android面试刨根问底之常用源码篇(一),下血本买的_第3张图片

  • PDF和思维脑图,包含知识脉络 + 诸多细节

Android面试刨根问底之常用源码篇(一),下血本买的_第4张图片

  • 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进阶系统学习视频

你可能感兴趣的:(程序员,架构,移动开发,android)