关于Handler的学习

声明:个人笔记,无参考学习价值

1.为什么不能在子线程中创建Handler

 public Handler(@Nullable 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());
            }
        }
//1.在Handler的构造函数里面,会调用Looper.myLooper(),来获取Looper对象,如果Looper对象为null,就会抛出异常
//Handler需绑定 线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行, 先指定Looper对象,从而绑定了 Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
  //Looper对象,是和消息队列绑定的可以在Looper()的构造参数中看到消息队列的构建
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
至此,Handler的构造参数中,指定了Looper对象,从而也就绑定了线程和消息队列
---------------------------------------------------------------------------------------------------------------------
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    }

下面看一看Looper.myLooper(),我们跳转到Looper类中:

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
//返回与当前线程关联的Looper对象,如果正在调用的线程没有关联一个Looper,那么返回null
//拿到Looper对象,就等于绑定了线程
    public static @Nullable Looper myLooper() {
//我们再去看一下sThreadLocal
        return sThreadLocal.get();
    }

除非你已调用了prepare(),否则sThreadLocal.get()将会返回null

 // sThreadLocal.get() will return null unless you've called prepare().    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

来看下prepare()

 public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
       //如果sThreadLocal.get() != null,根据上面的代码可以知道,不为null,说明了已经调用了prepare,那么就不能再次重复调用了,所以抛出异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
//,然后调用这句创建Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
小结: 那么看到这里,就稍微有点眉目了,要调用Handler对象,就必须拿到Looper对象,要拿到Looper对象呢,又必须调用prepare()方法,这也就印证在在Handler构造参数中的这句抛出的异常 "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()":
  mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }

我们没有手动调用prepare().那就是说我们需要在子线程中的代码里手动调用了

Looper.prepare()

2.那么问题来了,我们在主线程中调用Handler也没有去调用Looper.prepare()呀?

首先看一下在主线程中Looper调用的方法:

    /**
     * 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()}
     */
//皇家塑料翻译:
//将当前的线程初始化为一个looper对象,并将它标记为应用的主looper.这个应用的主looper已经在android环境被创建,因此你绝对不需要自己去调用这个函数
    public static void prepareMainLooper() {
        //prepare(false)这一句稍后分析
        prepare(false);
        synchronized (Looper.class) {
//这个判断和prepare()的一样,只能创建一个
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
//这里和prepare()不一样,直接从myLooper()中自信拿到sMainLooper,而且之前的myLooper()我们也看过, return sThreadLocal.get(); 那么也就是说必然会返回一个sMainLooper对象
            sMainLooper = myLooper();
        }
    }

那么我们再把myLooper()源码上的注解塑料翻译一遍,懂得自行跳过:

  /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
返回一个和当前线程关联的looper对象,如果调用的线程没有关联一个Looper返回null
我们知道,一个android程序中默认会有一个主线程
直接看 Return the Looper object associated with the current thread,这个current thread在prepareMainLooper()中,当然是主线程了,
所以它一定为我们做了一些事情,所以这里才会自信的直接调用 sMainLooper = myLooper();
而子线程是我们自己创建的,它并没有关联一个Looper,所以需要手动去Looper.prepare()(禁止套娃,又回到上面去了)

那么去看看主线程中怎么调用的吧 去ActivityThread中,有一个main()方法

    public static void main(String[] args) {
              ....
        //在这里调用了prepareMainLooper(),那么这个方法中就调用了prepare(false),那么sThreadLocal.get()中必然有值,那么myLooper(),就必然有值,那么sMainLooper,就成功赋值拉
        Looper.prepareMainLooper();

       .....

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
          ....

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

好了,再来说一下prepare(false),直接跟进源码 prepare(boolean quitAllowed)

//quitAllowed这个boolean值就传进了Looper的构造参数
 sThreadLocal.set(new Looper(quitAllowed));

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
//传进了MessageQueue的构造参数
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
并成为一个成员变量,供全局调用

mQuitAllowed在MessageQueue中只有一次调用

 void quit(boolean safe) {
//就是这里了, 如果设置了false,那么就抛出这个异常,表示prepareMainLooper()执行后,再执行quit,就会抛出"主线程不允许退出的错误"
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

顺带一看,至于quit(boolean safe),是在Looper的quit()和quitSafely()分别被调用,这两个方法都是退出looper的方法,不过更建议使用quitSafely()安全退出

 public void quit() {
        mQueue.quit(false);
    }
  /**
     * Quits the looper safely.
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
//在looper被要求退出之后,任何企图发送消息到队列的行为都将失败,举例:Handler的sendMessage(Message)将会返回失败
  public void quitSafely() {
        mQueue.quit(true);
    }

3.既然说了Looper.prepare(),那么顺带也把Looper.loop()也说了,毕竟Looper要执行还得用这个方法

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        //拿Looper对象,没有的话就去调用Looper.prepare,就不用多说了
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
    //有looper对象,就取出他的消息队列
        final MessageQueue queue = me.mQueue;
         .........
      //循环,取出消息
        for (;;) {
//------->MessageQueue.next()单独再看
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting. 
                return;
            }

               .................
            try {
//        msg.target,从Message中可以看到是Handler对象,那么后面调用的就是Handler中的dispatchMessage()方法
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
              .......
            //msg进入Message对象回收复用的环节,可见下面第四点中的Message对象创建
            msg.recycleUnchecked();
        }
    }

看下msg.target.dispatchMessage(msg),这里是我们处理消息中比较关心的内容

    /**
     * Handle system messages here.  
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) 
// 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则执行  handleCallback(msg),即回调Runnable对象里复写的run()
     handleCallback(msg);           
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);   //这个handleMessage(msg),就是我们在调用时复写的handleMessage,而处理UI的操作就是在这里完成的,他是一个空方法
        }
    }

然后,说一下MessageQueue中的next()

  * 分析1:queue.next()
  * 定义:属于消息队列类(MessageQueue)中的方法
  * 作用:出队消息,即从 消息队列中 移出该消息
  */
  Message next() {

        ...

        // 该参数用于确定消息队列中是否还有消息
        // 从而决定消息队列应处于出队消息状态 or 等待状态
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

        // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
     
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出了消息
                    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 {

                // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1
                // 下次循环时,消息队列则处于等待状态
                nextPollTimeoutMillis = -1;
            }

            ......
        }
           .....
       }
}//

4.为什么使用Messag.obtain(),而不是new Message()来创建消息对象

首先看源码中Message的构造方法 : 最好的方式是通过调用Message.obtain()来获取一个消息对象

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

有八个不同参数的静态obtain()方法

 /**
     * 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();
    }

可以看到,obtain()时,先判断sPoo中不为null的情况下,从sPool中取出一个Message返回,达到复用的效果,如果sPool为null的情况下就new Message()来创建一个新的

5. 那么来了解一下Message的Pool是怎么实现的

来看一下Message中的这两个方法:
首先recycle()的注释第一句什么意思,返回一个Message实例到全局的pool中

    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {      //return ((flags & FLAG_IN_USE) == FLAG_IN_USE);  isInUser()返回一个判断标识符
            if (gCheckRecycle) {  //这个全局变量赋值为true,还有个判断是5.0系统一下的为false
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");      //这个消息不能被回收,因为他仍然在被使用
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
  // 回收一个可能正在被使用的Message    当处理排队的信息时,在消息队列和Looper内部调用
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
    //以上   flags = FLAG_IN_USE为确保消息不被回收,剩余的都是将消息置空
        synchronized (sPoolSync) {
//如果池里的数量<设置的最大值50,sPool赋值给next,当前的消息对象赋值给sPool,sPoolSize用来记录数值
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

可以看出Message在调用recycle()回收消息的时候,对Message做出存储,当下次调用obtain()时,从sPool中取出复用

6.那么Message的recycle()是什么时候调用的呢,不急,我们先看Handler的sendMessage(),毕竟创建的消息是要先干活的嘛

通过sendMessage(msg) 方法,一路跟进了

 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
      //将当前这个Handler对象赋值给msg.target
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
  //Message携带这三个参数,和uptimeMills被MessageQueue的enqueueMessage执行
//而这个MessagQueue,如果你还记得之前Handler构造参数是在做什么操作,那你就应该知道,它是跟着Looper来的
        return queue.enqueueMessage(msg, uptimeMillis);
    }

接下来,去MessagQueue中去

 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) {
            if (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;
// 判断消息队列里有无消息
                        // a. 若无,则将当前插入的消息 作为队头 & 若此时消息队列处于等待状态,则唤醒
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } 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;
// b. 判断消息队列里有消息,则根据 消息(Message)创建的时间 插入到队列中
                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.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

// 之后,随着Looper对象的无限消息循环
// 不断从消息队列中取出Handler发送的消息 & 分发到对应Handler
// 最终回调Handler.handleMessage()处理消息

注:文中所有图片来源https://www.jianshu.com/p/b4d745c7ff7a

总结

  • 根据操作步骤的源码分析总结
image
  • 工作流程总结

下面,将顺着文章:工作流程再理一次

image
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339