Handler “虐我千百遍,待她如初恋,2022年我们程序员该如何进阶和规划

return new Message();

}

[](()Message和Handler的绑定

创建Message的时候已经通过Messa 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》开源 ge.obtain(Handler h)这个构造方法进行了绑定,在Handler中的enqueueMessage()方法中也绑定了,所有发送Message的方法都会调用此方法入队,所以在创建Message的时候是可以不进行绑定的

private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis){

msg.target=this;//进行绑定

if(mAsynchronous){

msg.setAsynchronous(true);

}

return queue.enqueuMessage(msg,uptimeMillis);

}

[](()Handler消息(发送/处理)

Handler发送消息的重载方法有很多

sendMessage(Message msg)

sendMessageDelayed(Message msg, long uptimeMillis)

post(Runnable r)

postDelayed(Runnable r, long uptimeMillis)

sendMessageAtTime(Message msg,long when)

sendEmptyMessage(int what)

sendEmptyMessageDelayed(int what, long uptimeMillis)

sendEmptyMessageAtTime(int what, long when)

主要是sendMessage(Message),调用了sendMessageDelayed继续调用sendMessageAtTime,调用enqueueMessage再继续调用到MessageQueue中的enqueueMessage,将消息保存在了消息队列中,最终由Looper取出,交给Handler的dispatchMessage进行处理

public void dispatchMessage(Message msg){

if(msg.callback!=null){ //callback在message的构造方法中初始化或者使用 handler.post(Runnable)时候才不为空

handlercallback(msg);

}else{

if(mCallback!=null){ //mCallback是一个Callback对象,通过无参的构造方法创建出来的Handler,该属性为null时不会进入到此判断

if(mCallback.handleMessage(msg)){

return ;

}

}

handleMessage(msg);

}

}

private static void handleMessage(Message msg){

msg.callback.run();

}

dispatchMessage方法,我们知道message中callback是一个Runnable对象,如果callback不为空,则直接调用callback的run方法,否则判断mCallback是否为空,mCallback在Handler构造方法中初始化,在主线程中直接通过无参的构造方法new出来的为null,所以会执行后面的handlemessage方法

handlemessage中可以拿到Message对象,根据不同的需求进行处理,到此Handler算是结束了

[](()Handler小结

handler.sendMessage发送消息到消息队列MessageQueue,然后Looper通过loop()函数轮询MessageQueue中的Message,当Message到了可执行的时候开始执行,执行后就会调用message绑定的Handler进行处理

[](()再探Handler

[](()从设计思想看Handler

子线程发送消息,主线程处理消息,构成了线程模型中的经典问题: 生产者-消费者模式(内存共享)

生产者-消费者模式:主要让生产者和消费在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据,消费者从存储空间中取出数据

这样做的好处是什么?

可以保证数据生产消费的顺序(MessageQueue先进先出)不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(Handler),不会相互持有,没有任何耦合

[](()MessageQueue

简单的说:

MessageQueue是一个消息队列,Handler将Message发送到消息队列中,消息队列会按照一定的规则取出要执行的Message

  • 成员变量

// True if the message queue can be quit.

//用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭

private final boolean mQuitAllowed;

@SuppressWarnings(“unused”)

// 该变量用于保存native代码中的MessageQueue的指针

private long mPtr; // used by native code

//在MessageQueue中,所有的Message是以链表的形式组织在一起的,该变量保存了链表的第一个元素,也可以说它就是链表的本身

Message mMessages;

//当Handler线程处于空闲状态的时候(MessageQueue没有其他Message时),可以利用它来处理一些事物,该变量就是用于保存这些空闲时候要处理的事务

private final ArrayList mIdleHandlers = new ArrayList();

// 注册FileDescriptor以及感兴趣的Events,例如文件输入、输出和错误,设置回调函数,最后

// 调用nativeSetFileDescriptorEvent注册到C++层中,

// 当产生相应事件时,由C++层调用Java的DispathEvents,激活相应的回调函数

private SparseArray mFileDescriptorRecords;

// 用于保存将要被执行的IdleHandler

private IdleHandler[] mPendingIdleHandlers;

//标示MessageQueue是否正在关闭。

private boolean mQuitting;

// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.

// 标示 MessageQueue是否阻塞

private boolean mBlocked;

// The next barrier token.

// Barriers are indicated by messages with a null target whose arg1 field carries the token.

// 在MessageQueue里面有一个概念叫做障栅,它用于拦截同步的Message,阻止这些消息被执行,

// 只有异步Message才会放行。障栅本身也是一个Message,只是它的target为null并且arg1用于区分不同的障栅,

// 所以该变量就是用于不断累加生成不同的障栅。

private int mNextBarrierToken;

  • 数据是有序的吗?

你这不是废话吗?肯定有序啊,来来来,看源码

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;

// 一致循环,直到找到尾巴(p == null)

// 或者这个 message 的 when 小于我们当前这个 message 的 when

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;

}

Queue 都是有序的,Set 才是无序的,它的排序的依据是通过when字段,表示一个相对时间,该值是由 MessageQueue#enqueueMessage(Message, Long) 方法设置的

  • when是怎么来的?

Message#when 是一个时间,用于表示 Message 期望被分发的时间,该值是 SystemClock#uptimeMillis() 与 delayMillis 之和

public final boolean sendMessageDelayed(Message msg, long delayMillis)

{

if (delayMillis < 0) {

delayMillis = 0;

}

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

既然是时间,为什么不直接用System.currentTimeMillis() ???

return sendMessageAtTime(msg, System.currentTimeMillis()+ delayMillis);

为什么不用呢?首先我们先了解下这两者的区别

SystemClock.uptimeMillis() 是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调用该方法时相差的毫秒数

System.currentTimeMillis() 代表的是从 1970-01-01 00:00:00 到当前时间的毫秒数,我们可以通过修改系统时间达到修改该值的目的,所以该值是不可靠的值

看完解释,一目了然了吧!

在上面的成员变量中,看到了IdleHandler这个东东,虽然用的少,但也简单的了解下吧

[](()IdleHandler

IdleHandler在处理业务逻辑方面和Handler一样,不过它只会在线程空闲的时候才执行业务逻辑的处理,这些业务经常是哪些不是很紧要或者不可预期的,比如GC

从源码上来看,我们发现这货就是一个接口而已,内部就一个带返回值的方法boolean queueIdle(),在使用的时候只需要实现该接口并加入到MessageQueue中就可以了

public static interface IdleHandler {

boolean queueIdle();

}

  • 使用

MessageQueue messageQueue = Looper.myQueue();

messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {

@Override

public boolean queueIdle() {

// do something.

return false;

}

});

public void addIdleHandler(@NonNull IdleHandler handler) {

//非空判断

if (handler == null) {

throw new NullPointerException(“Can’t add a null IdleHandler”);

}

//加一个同步锁

synchronized (this) {

//调用mIdleHandlers.add(handler)添加

mIdleHandlers.add(handler);

}

}

/**

*从消息队列中移除一个之前添加的IdleHandler。如果该IdleHandler不存在,则什么也不做

*/

public void removeIdleHandler(@NonNull IdleHandler handler) {

synchronized (this) {

mIdleHandlers.remove(handler);

}

}

[](()ThreadLocal(核心成员)

我们知道Handler利用了内存共享的原理,那么ThreadLocal就是最大的功臣

ThreadLocal设计的初衷是提供线程内部的局部变量,在本地线程内随时随地可取,隔离其他线程

  • 类图

  • 结构图

  • set操作

public void set(T value) {

//先拿到保存键值对的ThreadLocalMap对象实例map

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

//

if (map != null)

map.set(this, value);

else

//如果map为空(即第一次调用的时候map值为null),去创建一个ThreadLocalMap对象并赋值给map,并把键值对保存在map

createMap(t, value);

}

getMap实现非常直接,就是直接返回Thread对象的threadLocal字段

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

//Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

简单的说每个线程引用的ThreadLocal副本值都是保存在当前Thread对象里面的。存储结构为ThreadLocalMap类型,ThreadLocalMap保存的类型为ThreadLocal,值为副本值

  • get操作

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}

private T setInitialValue() {

T value = initialValue();

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

return value;

}

和set方法原理一样,拿到当前线程Thread对象实例中保存的ThreadLocalMap对象map,然后从map中读取键为this(即ThreadLocal类实例)对应的值

从上面的图中我们发现 ThreadLocal内部有一个ThreadLocalMap对象,该Map对象里面维护了一个弱引用的Entry实体类

  • ThreadLocalMap(内部类)

从源码中的文档上我们得知:

ThreadLocalMap是一个适用于维护线程本地值的自定义哈希映射(hash map),没有任何操作可以让它超出ThreadLocal这个类的范围。该类是私有的,允许在Thread类中声明字段。为了更好的帮助处理常使用的,hash表条目使用了WeakReferences的键。但是,由于不实用引用队列,所以,只有在表空间不足的情况下,才会保留已经删除的条目

  • 存储结构

ThreadLocalMap中定义了额Entry数据实例table,用于存储Entry

private Entry[] table;

也就是说ThreadLocalMap维护一张哈希表(一个数组),表里存储Entry。既然是哈希表,那肯定会涉及到加载因子,即当表里面存储的对象达到容量的多少百分比的时候需要扩容

具体可参考HashMap原理

[](()线程同步安全

Handler是用于线程间通信的,但是它产生的根本原因并不只是用于UI处理的,而更多的是Handler是整个App通信的框架,从ActivityThread.java类中就已经感受到了,整个APP都是用它进行线程间的协调,既然如此重要,那么它又是如何保证自己的线程安全的呢?

Handler中至关重要的类MessageQueue做了两件事,消息入库enqueueMessage和消息出库next,所以我们就从这个地方入手

我们发现不管是enqueueMessage()还是next()有个锁的存在

synchronized(this)

这个锁说明对调用同一个MessageQueue对象的线程来说,它们都是互斥的

在Handler里面,一个线程对应一个唯一的Looper对象,而Looper中只会有一个唯一的MessageQueue。所以也就是说我们主线程中只有一个MessageQueue对象,所有的子线程向主线程发送消息的时候,主线程一直只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱

在next方法为什么要加锁?我每次从线程里面取消息,而且每次都是队列的头部,为什么还需要加锁?

在next方法中加锁,因为synchronized(this)的范围是所有this正在访问的代码块都受到保护,可以保证enqueueMessage()和next()函数能够实现互斥,这样才能症状的保证在多线程中访问的时候MessageQueue有序进行

[](()同步屏障

举个例子

在马路上各种车都需要遵守交通信息,然后车辆依次通过,但是这个时间救护车正在执行任务的时候,交警就会指挥交通,让各种私家车辆让开一条绿色通道给救护车通过,待到救护车通过后,大家继续依次排队通过。

我们知道Handler通常在没有设置的时候都是同步的,我们知道Handler的MessageQueue消息都是按照时间的先后顺序进行排序的,那么假如同一个时刻中某一个消息需要立即执行怎么办?

这个时候就涉及到同步屏障了,也就是绿色通道

同步屏障:就是阻碍同步消息,只让异步消息通过,那么如何开启同步屏障呢?

要想发送异步消息,那么我们需要满足target==null的条件

  • target是个啥?

首先从源码中看到target是在Message类中定义的

//Message.java

Handler target;

也就是说Message是持有Handler的,而target就是Handler对象

  • 异步消息

我们知道在Handler调用sendMessage()方法最终会进入到Handler的enqueueMessage()让消息入队,但是这样入队的消息都有一个msg.target=this,也就说这样的消息是一个同步消息

那么我们只需要满足target Android开源项目《ali1024.coding.net/public/P7/Android/git》 ==null这个条件就可以达到发送异步消息的目的了

  • 异步方式

1.直接在new Handler的设置

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)


t》** ==null这个条件就可以达到发送异步消息的目的了

  • 异步方式

1.直接在new Handler的设置

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-3QDNsAoV-1650621107543)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值