Handler(下)那些事儿

继上一篇 Handler(上)那些事儿之后,我们已经了解了ThreadLocal、MessageQueue、Looper三者之间的联系,带着上一遍的疑问,我们来了解一下Handler源码部分。

Handler

Handler 构造方法

在这里插入图片描述
上诉构造方法来自与api28,不过没什么大区别.相比大家在开发中用到做多的应该是无参构造,我们先来看一看源码

  public Handler() {
        this(null, false);
    }
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们可以看到无参构造方法内部其实调用了另一个构造方法.主要就是给mLooper mQueue mCallback mAsynchronous 赋值,分别表示当前线程绑定的Looper,当前线程绑定的Looper内部的MessageQueue消息队列,回调,是否异步.(异步本章节不作讲解)
乘胜追击,我们再看来来其他的构造方法:

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }


我们可以看到内部其实最后都是调用Handler(CallBack,async)/Handler(Looper,CallBack,async)这两个构造方法,该方法主要就是进行mLooper mQueue mCallback mAsynchronous的赋值.
总结就是

  • 若mLooper为空则默认过去当前线程绑定的Looper,不为空则用传入的Looper
  • mQueue则是通过Looper内部的MessageQueue获取的
  • mCallback默认为null
  • mAsynchronous默认为false

所以这就解释了平时在子线程和主线程通信的时候,对于Handler的使用我们就只是通过无参构造方法创建,因为内部会获取当前线程也就是主线程的Looper,所以可以通过Handler去改变UI.

sendMessage/postxxx

通过sendMessage/postxxx方法可以去分发消息,但是Handler给我们提供了很多sendMessage和postxxx方法,有的作用是延时作用,有的时候传一个empty类型的Message,在此都不会一一分析,大家可以自行看看源码,但是所有的方法最后都会去调用sendMessageAtTime我们来看看.

   public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  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);
    }

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

便于理解保留sendMessageAtTime调用前的一个方法,便于理解.

  • sendMessageAtTime(message,uptimeMillis),uptimeMillis表示具体执行的时间
  • 所以可以看到sendMessageDelayed方法最终调用了sendMessageAtTime,传入的第二参数表示时间就是当前时间+加上延时的时间(若有延时则加,无则不加)
  • sendMessageAtTime内部又调用了enqueueMessage,将message添加到队列中,通过调用MessageQueue#enqueueMessage方法.

那么后面的工作就交给了Looper#loop方法,去循环的调用MessageMessage#next方法.

在讲解Looper#loop的时候,提到将MessageMessage#next返回的Message,通过message.target#dispatchMessage方法回调该信息,那么我们来看看dispatchMessage方法.

dispatchMessage

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

其实dispatchMessage主要决定消息回调的先后顺序,

  • 若msg.callback不为空则回调handleCallBack
  • 若mCallBack不为空则回到mCallBack.handleMessage
  • 最后都不会掉则回调Handler的handleMessage

msg.callback其实就是在构建Message的时候传入的Runnable
mCallBack其实就是Handler构造方法中传入的Callback

所以通过dispatchMessage,我们可以知道去获取Message的途径.

使用实例


public class MainActivity extends AppCompatActivity {

    Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                handler = new Handler(Looper.myLooper(), callback);
                Looper.loop();
            }
        }.start();
        findViewById(R.id.btn_add).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.obj = "hello";
                handler.sendMessage(message);
            }
        });
        System.out.println("main " + Thread.currentThread().getName());

    }

    Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            System.out.println("THread " + msg.obj.toString() + Thread.currentThread().getName());
            return false;
        }
    };
}

上诉代码就是一种子线程Handler的使用. 那么有人会问为什么平时主线程Handler用不到Looper#prepare和Looper#loop? 因为Activity在创建的时候就帮我做了.

总结

这么一来整一个Handler的消息机制就都了解了,最后咱们还是要总结归纳整个流程.

  • 首先需要通过Looper#prepare去初始化该线程的MessageQuene
  • 通过创建Handler并获取当前线程的Looper,从而获取Looper的MessageQuene
  • 通过sendMessage,将所要分发的Message添加到MessageQueue中.
  • 由于Looper#loop会去不断的循环调用MessageQueue#next方法去获取可以分发的Message
  • 最后通过Message.target#dispatchMessage去回调Message.

为了便于理解整个过程,我将Handler过程视为一个传送带的工作流程.
在这里插入图片描述
Handler就是工人,负责货物(Message)的组装,将货物(Message)拖放到传送带(MessageQuene)中,传送带(MessageQuene)被动力(Looper)驱使着去传送货物(Message),最终传送到终点再有工人(Handler)去组装.

进阶部分

储存Message是通过什么方式存储的.

  • 存储结构是以单链表的形式存储
  • 存储是依据执行Message的最终实际时间的先后去存储的.

MessageQueue线程唤醒/休眠

我们先来看看MessageQueue的next方法

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            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());
                }
                if (msg != null) {
              (1)   if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                if (mQuitting) {
                    dispose();
                    return null;
                }
                (2)   if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
             if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
        ...
        }

注:
mBlocked:表示线程是否休眠,该变量会在enqueueMessage中用到。
mMessages:表示当前执行的Message。

我们来看看(1)处代码

  1. 判断当前msg.when也就是执行message时间和now(当前时间)进行对比
  2. 若now>msg.when,则返回当前msg,并且mBlocked设置为false,因为当前时间大于可执行时间,所以不需要休眠等待,故线程当前状态为未休眠。
  3. 若now<msg.when则会记录下nextPollTimeoutMillis(距离执行msg的时间差),进行线程休眠。

那么记录下nextPollTimeoutMillis(时间差)有什么用呢? 大家知道looper.loop内部是有一个死循环去获取msg的,并且msg单链的排列又是按照时间排列,所以只有执行完当前msg再回轮到下一个,那么已知下一个msg执行时间和当前时间的差距,系统过让线程休眠,休眠的时间则为执行的时间差,nativePollOnce方法就是让线程休眠nextPollTimeoutMillis的时间,这样就避免的资源的浪费.


我们可以看到(2)处代码

  1. 若当前没有可执行的message或者是执行message的时间未到,没有可执行的任务所以需要将mBlocked赋为true。

大家想一想这一种情况,若当前时间为12点,下一个执行的msgA为12.30,因为有时间差所以线程休眠,那么此时我添加一个msgB他的执行时间为12.15,可是线程还休眠着无法执行msgB,所以需要唤醒线程,将msgB插入到msgA前,然后修改休眠时间进行休眠.那么唤醒这一步骤在enqueueMessage.
那么我们来看看enqueueMessage中线程唤醒的部分

 boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {1if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;2if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;3} else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.4if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

我们来看看3处重要代码。

  1. 若当前msg任务停止,则会抛出异常再回收资源。
  2. 我们可以看到needWake 和mMessages赋予了p,主要就是判断当前的mMessages是否为null,null就表示这没有可执行的Message;当前需要添加的Message的执行时间早于mMessages,需要插入到队列中,然后将当前线程状态赋予needWake。
  3. 这一块表示异步的msg,异步这一块不作讲解,后面会专门开一篇说异步。
  4. 最后根据当前线程是否休眠,来调用netiveWake去唤醒线程。

可以总结一下

  • 若当前无可执行的msg,则线程会去休眠,此时mBlocked=true
  • 若当前执行的msg的时间未到,则会记录下时间差nextPollTimeoutMillis,并调用nativePollOnce方法让线程休眠,此时mBlocked=true.
  • 若在线程休眠的时候,插入新的msg,则会去唤醒线程,重新排列msg的顺序,去执行重新排列后下一个该执行的msg.

通过上诉总结,我们可以回答面试中的一种类似的题目.
1.为什么sendMessage加了延迟,线程不会堵塞?
2.因为当前可执行msg时间未到,系统进行休眠,那么插入新的msg如何操作?
这些类似的问题都可以从休眠堵塞,以及唤醒这一块解释即可.

为什么在Activity中的Handler直接new,而子线程中会报错且提示需要looper.prepare?

5240        Looper.prepareMainLooper();
5241        //创建ActivityThread 对象
5242        ActivityThread thread = new ActivityThread();
5243        thread.attach(false);
5244
5245        if (sMainThreadHandler == null) {
5246            sMainThreadHandler = thread.getHandler();
5247        }
5248
5249        if (false) {
5250            Looper.myLooper().setMessageLogging(new
5251                    LogPrinter(Log.DEBUG, "ActivityThread"));
5252        }
5253
5254        Looper.loop();

上述代码在ActivityThread.java#main方法中,main方法是整个APP初始化的入口,可以看到它已经帮我们做了Looper.prepare 和 Looper.loop,主要是在主线程中,handler直接new即可,不需要再Looper.prepare 和 Looper.loop

在消息机制中,那么多方法内进行死循环,为什么系统不会卡死?

这个问题得要从ActivityThread.java开始说起,整一个APP的初始化在ActivityThread的main方法执行,在一个线程的生命在代码执行完后结束,那么如果执行完了这不就意味着这个线程就会消失,那么在main方法中不就意味着APP结束?所以就需要一个死循环或者是像MessageQueue中的线程休眠唤醒这样的流程,来保存整个main线程长久的生命周期。从而在MessageQueue的中对线程休眠和唤醒的流程。当然整一个app也不止一个消息机制,会有很多机制,所以会通过创建线程去执行。

下集预告

OkHttp的源码解读

weixin073智慧旅游平台开发微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
python017基于Python贫困生资助管理系统带vue前后端分离毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值