写给Rikka自己的Handler源码说明书,重要概念一网打尽

关于C++层的源码可以参考下这一篇:Android 中 MessageQueue 的 nativePollOnce

函数中传入了 nextPollTimeoutMillis,有点像 Thread.sleep(mills)。这个nextPollTimeoutMillis会在延时任务中起到作用。

这里也就回答了上节末尾的问题,为什么开死循环不会特别占用CPU的资源,是因为在没有消息的时候主线程已经挂起,有消息时才会唤醒。

Part2 返回一个消息

// MessageQueue.java

synchronized (this) {

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages; // 1

if (msg != null) {

if (now < msg.when) { // 2

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else {

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next; // 3

}

msg.next = null;

msg.markInUse();

return msg;

}

} else {

nextPollTimeoutMillis = -1;

}

这段代码比较好理解,不过我们要先知道 MessageQueue里面维护的是一个 Message链表

注释1:拿到当前的MessagemMessage。

注释2:判断 msg是否是延时任务,如果是的话则不处理,并更新 nextPollTimeoutMillis 的时间

注释3:如果 msg不是延时任务,则把 mMessage指向当前 msg在链表中的下一个。然后return当前的msg。

Part3 IdleHandler

synchronized (this) {

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size(); // 1

}

if (pendingIdleHandlerCount <= 0) { // 2

mBlocked = true;

continue;

}

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; // 3

}

mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

}

// 4

for (int i = 0; i < pendingIdleHandlerCount; i++) {

final IdleHandler idler = mPendingIdleHandlers[i];

mPendingIdleHandlers[i] = null;

boolean keep = false;

try {

keep = idler.queueIdle(); // 5

} catch (Throwable t) {

Log.wtf(TAG, “IdleHandler threw exception”, t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

pendingIdleHandlerCount = 0; // 6

nextPollTimeoutMillis = 0; // 7

}

}

这段代码涉及到了 IdleHandler,它是MessagQueue的内部类,我们先来看看他是做啥的:

// MessagQueue.java

private final ArrayList mIdleHandlers = new ArrayList();

public static interface IdleHandler {

boolean queueIdle();

}

public void addIdleHandler(@NonNull IdleHandler handler) {

if (handler == null) {

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

}

synchronized (this) {

mIdleHandlers.add(handler);

}

}

可以发现它是一个接口,通过 addIdleHandler()可以添加实现了 queueIdle()方法的对象进来,保存在 mIdleHandlers的List中。

我们回到part3的代码中来解析一下:

注释1:如果pendingIdleHandlerCount < 0 并且当前没有Message或者是延时任务,则把 mIdleHandlers的大小赋值给pendingIdleHandlerCount 。

注释2:如果赋值过后的pendingIdleHandlerCount <= 0,则说明当前线程没有可以执行的任务,那么continue,在下一个循环中将当前线程挂起。

注释3:如果mPendingIdleHandlers为null,则new一个IdleHandler出来赋值给它,并将其转换成一个 数组。

注释4:遍历 mPendingIdleHandlers

注释5:取出其中的每个 IdleHandler并执行它的 queueIdle()方法。

注释6、注释7:清空 pendingIdleHandlerCount 和 nextPollTimeoutMillis 。因为如果是延时任务,早就已经continue了。另外一个事情就是,如果第一次打开死循环,Message链表是空的,这时候主线程可以做些别的事情(就是 IdleHandler),做完之后,之后的循环就不会再去做这样的操作了(除非我们自己加IdleHandler进来)。

到这里 MessageQueue.next()就解析完了,这里做一个总结,它大概做了三件事情:

  1. 开启死循环

  2. 如果当前没有Message可以处理,则调用 nativePollOnce()挂起主线程,不占用CPU资源。 等到有消息可以处理时唤醒主线程。

  3. 如果存在非延时的消息,则把该消息返回到Looper中。

  4. 如果是循环的第一次,可能会没有消息,这个时候可以 处理IdleHandler的消息,做一些别的事情,这相当于提升了性能,不让主线程一上来就因为没有Message而挂起,然后下个循环又马上被唤醒。

2.5 Message是如何被加入到MessageQueue中的?


在上一节中,我把MessaqeQueue和Messaqe的关系笼统的概括为: MessageQueue中维护了一个Message链表。

仅仅这样理解是不够的,我们需要搞清楚,一个Message是怎么放到 MessageQueue中的。入口方法就是我们常用的 Handler.sendMessage()或者 Handler.sendMessageDelayed()还是 Handler.postDelay(),他们最终都会调用 sendMessageAtTime()

// Handler.java

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w(“Looper”, e.getMessage(), e);

return false;

}

return enqueueMessage(queue, msg, uptimeMillis);

}

这个方法会调用 enqueueMessage(),然后传入当前 Handler的 MessageQueue() mQueue,我们在开篇讲过,mQueue是构造函数中就被创建,它是传入的 Looper的MessageQueue。

// Handler.java

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

msg.target = this; // 1

if (mAsynchronous) {

msg.setAsynchronous(true);

}

return queue.enqueueMessage(msg, uptimeMillis); // 2

}

注释1:把msg.target = this就是说这个消息发送给当前Handler的

注释2:调用 MessageQueue.enqueueMessage(),传入消息体和延时时间。这个动作就是把消息加入到 MQ中了。

来看一下加入过程:

// MessageQueue.java

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) { //1

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

needWake = mBlocked && p.target == null && msg.isAsynchronous();

Message prev;

for (;😉 { // 2

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p;

prev.next = msg;

}

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

注释1:如果当前的Message是非延时任务,或者说比列表最后一个Message的执行时间要快,那么它就插队,插到前面去。

注释2:否则的话,就往列表后边找,找到 末尾或者第一个Message执行时间比当前的要晚的,然后插进去。

这段代码很简单,要么当前任务 插到前面,要么插到后面最早执行的位置。

到这里,一个Message的入队就讲完了。

2.6 关于消息分发 msg.target.dispatchMessage(msg)


回到Looper中,在死循环里,它会调用 msg.target.dispatchMessage(msg), 我们知道,msg.target是发送消息的Handler,那么这里调用了 目标Handler的dispatchMessage()把Message发送出去,来看看这个方法:

// Handler.java

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg); // 1

} else {

if (mCallback != null) { // 2

if (mCallback.handleMessage(msg)) { // 3

return;

}

}

handleMessage(msg); // 4

}

}

private static void handleCallback(Message message) {

message.callback.run();

}

注释1:如果msg.callback不为空,则直接调用这个Callback的 run(),这个情况是我们在Message中传入了一个Callback的方法,它在处理时,会直接调用这个Callback方法。

注释2、注释3:如果在创建 Handler(构造函数中)时带入了 一个 Callback,则直接调用这个 Callback,这个构造函数我们在开篇看过。

注释4:如果都不是上述情况,则调用 handleMessage()

// Handler.java

/**

  • Subclasses must implement this to receive messages.

*/

public void handleMessage(Message msg) {

}

handleMessage() 是一个空方法,英文文档的解释为: 子类必须实现这个方法

这就是为什么我们在自己使用的时候,一定要重写 handleMessage(),然后对发送过来的消息做处理。

当这个消息被调用时,也说明了 Message被分发成功了。

到这里,handler的源码也讲解的差不多了。

3. 在子线程中更新UI

==============================================================================

这里画一个图便于理解:

在这里插入图片描述

这里手写一个简单例子,在子线程通过Handler去更新UI线程:

public class MainActivity extends AppCompatActivity {

private TextView textView;

private static final int COUNT = 0x00001;

private Handler handler = new Handler() { // 1

@Override

public void handleMessage(Message msg) {

if (msg.what == COUNT) {

textView.setText(String.valueOf(msg.obj));

}

super.handleMessage(msg);

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

textView = findViewById(R.id.btn);

new Thread() { // 2

@Override

public void run() {

try {

for (int i = 0; i < 100; i++) {

Thread.sleep(500);

Message msg = new Message();

msg.what = COUNT;

msg.obj = “计数:” + i;

handler.sendMessage(msg); // 3

}

} catch (Exception e) {

e.printStackTrace();

}

}

}.start();

}

}

注释1中:我们在主线程创建了一个Handler,那么其Looper自然就是UI线程的Looper

注释2:开启一个子线程

注释3:在子线程中,持有一个主线程的Handler,然后给这个Handler发送消息。

上面代码非常容易理解,但是可能会出现内存泄漏,所以这里只是做一个子线程更新UI的认知。

4. 主线程更新子线程、子线程更新其他子线程

========================================================================================

开篇我说过,Handler的出现是子线程不能更新UI,所以Handler起到了这个作用。

这一章是来自于我实习面试的时候,面试官在Handler这点上问我:那主线程怎么更新子线程?或者子线程怎么更新其他的子线程?

我当时由于没有细读Handler的源码,所以我粗略的回答:只要持有对方线程的Handler,就可以更新了。

其实我回答也没错,但是这谈不上及格的答案,面试官也不会喜欢,下面我将用代码来演示一遍:

4.1 主线程更新子线程


public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;

private MyThread thread;

class MyThread extends Thread {

private Looper looper;

@Override

public void run() {

Looper.prepare(); // 1

Log.d(TAG, “子线程为->” + Thread.currentThread() + “”);

looper = Looper.myLooper(); // 2

Looper.loop(); // 3

}

}

private Handler mHandler;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Log.d(TAG, “主线程为->” + Thread.currentThread() + “”);

thread = new MyThread();

thread.start();

try {

sleep(2000); // 4

} catch (InterruptedException e) {

e.printStackTrace();

}

mHandler = new Handler(thread.looper) { // 5

@Override

public void handleMessage(Message msg) {

Log.d(TAG, “当前线程为->” + Thread.currentThread() + “”);

}

};

mHandler.sendEmptyMessage(0);

}

}

主线程要更新子线程,所以主线程要持有子线程的Handler,所以需要用子线程来构造一个Handlder

注释1:为子线程创建一个Looper

注释2:获取子线程Looper

注释3:打开子线程Looper的轮询

注释4:主线程睡一下,避免 子线程还没有完全起来的时候就在主线程获取子线程的Looper了

注释5:通过子线程的Looper创建属于子线程的Handler,然后在主线程中使用Handler发送消息。

打印如下:

在这里插入图片描述

说明消息已经从主线程传递到子线程当中去了。

4.2 子线程更新其他子线程


方法同上。下面写两个线程 MyThread1MyThread2,然后让2线程给1线程发消息:

public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;

private MyThread1 thread1;

private MyThread2 thread2;

private Handler thread1Handler;

class MyThread1 extends Thread {

@Override

public void run() {

Log.d(TAG, “子线程1为->” + Thread.currentThread() + “”);

Looper.prepare();

thread1Handler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

Log.d(TAG, “这个消息是从->” + msg.obj + “线程发过来的,在” + Thread.currentThread() + “线程中获取的”);

}

};

Looper.loop();

}

}

class MyThread2 extends Thread {

@Override

public void run() {

Log.d(TAG, “子线程2为->” + Thread.currentThread() + “”);

Message msg = thread1Handler.obtainMessage();

msg.obj = Thread.currentThread();

thread1Handler.sendMessage(msg);

}

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

thread1 = new MyThread1();

thread2 = new MyThread2();

thread1.start();

try {

Thread.sleep(500); // 让Thread1完全起来

} catch (InterruptedException e) {

e.printStackTrace();

}

thread2.start();

}

打印结果如下:

在这里插入图片描述

所以一开始的问题:如果在主线程中更新子线程,在子线程中更新另外的子线程的解答:

(1)要持有目标线程的Handler

(2)因为非UI线程是不会自己开启轮询的,所以要手动在子线程中启动 Looper.prepare()Looper.loop()

another question: HandlerThread

在上面的手动写法中,我们还要写 Thread.sleep保证子线程启动起来,并且我们还要手动在子线程中手写 Looper.prepare()Looper.loop(),这无疑有一些麻烦,Android提供了一个便于在子线程开启Handler、Looper的类,就是 HandlerThread

5. HandlerThread

==================================================================================

HandlerThread顾名思义,就是使用 Handler的线程。它的源码非常简单,来看看:

先来看看其构造函数:

// HandlerThread.java

public class HandlerThread extends Thread {

int mPriority;

int mTid = -1;

Looper mLooper;

private @Nullable Handler mHandler;

public HandlerThread(String name) {

super(name);

mPriority = Process.THREAD_PRIORITY_DEFAULT;

}

public HandlerThread(String name, int priority) {

super(name);

mPriority = priority;

}

}

HandlerThread是继承Thread的,在构造函数中,传入线程名称和优先级。

接下来看看其 run方法:

// HandlerThread.java

protected void onLooperPrepared() {

}

@Override

public void run() {

mTid = Process.myTid();

Looper.prepare();

synchronized (this) {

mLooper = Looper.myLooper();

notifyAll();

}

Process.setThreadPriority(mPriority);

onLooperPrepared();

Looper.loop();

mTid = -1;

}

run方法中,写了 Looper.prepare(),然后锁住对象去获取Looper,通过 Process.setThreadPriority()来设置线程优先级。

然后调用 onLooperPrepared(),这是个空方法,表示我们在自己使用HandlerThread时,可以通过重写这个方法来在Looper准备完成后做一些想做的事情。

如何使用HandlerThread来发送消息呢?HandlerThread实现了下面的方法:

@NonNull

public Handler getThreadHandler() {

if (mHandler == null) {

mHandler = new Handler(getLooper());

}

return mHandler;

}

public Looper getLooper() {

if (!isAlive()) {

return null;

}

synchronized (this) {

while (isAlive() && mLooper == null) {

try {

wait();

} catch (InterruptedException e) {

}

}

}

return mLooper;

}

通过 getThreadHandler()我们可以得到当前线程的Handler,并且它会在 getLooper()中保证Looper是起来的。

所以这也避免了我们手写 wait()、sleep()的麻烦。

这里总结一下HandlerThread:

HandlerThread是实现了Thread的线程,它start后会进行 Looper.prepare()、Looper.loop(),并且在我们想要使用其Handler时,能保证Handler不为空。它适合的场景是:主线程需要更新子线程的数据、子线程更新另一个子线程的数据。被更新的子线程可以使用HandlerThread

6. 关于Message的获取

=================================================================================

写这一章节也是基于面试时比较喜欢问的,Message的获取有两种方法:

  1. 通过 new 方法获取

  2. 通过 Message.obtain()

上述第二种方法就是 Handler中的 obtainMessage()里调用的方法,两种方法是有区别的,哪一种更好?

我们来看看 Message.obtain()

// Message.java

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

}

可以看到 obtain()中会去 sPool中获取一个Message,如果sPool为空,则返回一个new的Message,否则把sPool赋值给m,并将m返回,然后 sPool取其next。

在结合一下下面的方法:

// Message.java

void recycleUnchecked() {

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,原来的sPool放到新的后面。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

这个方法是在一个消息被使用后,对其进行回收:将所有数据置空,然后将其做为sPool,原来的sPool放到新的后面。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-YMo15w6l-1712063043417)]
[外链图片转存中…(img-07v042PB-1712063043417)]
[外链图片转存中…(img-9SLoi93t-1712063043418)]
[外链图片转存中…(img-gAVNXed2-1712063043418)]
[外链图片转存中…(img-OfB67KMz-1712063043418)]
[外链图片转存中…(img-mnJKpefj-1712063043418)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-9qU6NOLk-1712063043419)]

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊

小编将自己6年以来的面试经验和学习笔记都整理成了一个**937页的PDF,**以及我学习进阶过程中看过的一些优质视频教程。

[外链图片转存中…(img-BozZUr6E-1712063043419)]

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值