Handler 的工作原理

参考资料
gityuan

一、Handler原理

Handler 是 Android 中线程间通信的组件。在异步线程中使用前需要先调用 Looper.prepare 为当前线程准备 Looper 和 Looper 持有的消息队列,然后通过创建的 Handler 对象像这个消息队列里插入消息,调用 Looper.loop 方法启动 loop 循环处理消息。

创建 Looper 对象

当调用 Looper.perpare 函数时,在 Java 层会为当前线程创建一个 Looper 以及 MessageQueue 对象。创建 MessageQueue 时,同时还会调用 nativeInit 函数去创建 native 层的 MessageQueue 和 Looper 对象。Native 层的 Looper 对象会去创建一个 EventFd 对象。它是消息循环的核心。

   MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

创建 Handler 对象

  1. 首先判断是否为当前线程准备了 Looper 对象。如果没有会抛出异常
  2. 通过调用 Looper 的 prepare 方法准备当前线程环境的 Looper 对象
  3. 获取 Looper 中维护的消息队列
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
 }
 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));
    }
   public Handler(Callback callback, boolean async) {
        ....
        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;
    }

2.发送消息

  1. 发送消息本质上是像 Looper 所持有的 MessageQueue 队列中插入消息
  2. 在将消息入队前对样会把自身的引用赋值给这个 Message,在取出消息时就可以方便的知道消息是由哪一个 Handler 发送的
 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ......
        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);
}

MessageQueue 插入消息时会对消息进行排序

  1. 如果当前队列没有消息,新消息的延时为0,或者新消息的延时时间要比当前消队列第一个的延时时间短,都会将这个新的消息作为第一个消息优先被取出
  2. 否则就将消息消息链接在之前的消息后面
  3. 如果之前的 Looper 在等待状态会唤起 Looper

唤醒消息是通过调用 nativeWake 函数,它会调用 native looper 对象的 wake 函数向 EventFD 写入一个数字,唤醒被 epoll_wait 阻塞的代码

 boolean enqueueMessage(Message msg, long when) {
        ......
        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;
            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;
                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;
            }

            // 向 EventFD 中写入一个数字,唤醒 epoll_wait 阻塞的地方
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

JNI 层

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

核心就是向 mWakeEventFd 写入了一个数字 write(mWakeEventFd.get()

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
                             mWakeEventFd.get(), nWrite, strerror(errno));
        }
    }
}

3. Looper 取出消息

  1. Looper 通过调用 MessageQueue 的 next 函数取出消息
  2. 通过消息中持有的 Handler 引用触发对应 Handler 的 dispatchMessage 执行对应的任务
public static void loop() {
      final MessageQueue queue = me.mQueue;
         for (;;) {
            // 如果没有消息要发送会让出 CPU
            Message msg = queue.next(); 
            .....
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         }
}

MessageQueue 的 next 方法

  1. 第一次进入时,会判断队列中是否有消息,如果没有消息,会将延时时间设置成 -1,再次循环时会调用 nativePollOnce 函数进入无限的休眠期。nativePollOnce 函数本质上是调用 native Looper 对象的 pollOnce 函数通过调用 epoll_wait 阻塞在当前的位置。
  2. 如果当前有消息,会取出消息计算它的发送时间,如果消息的时间比当前时间小就立即发送,如果消息时间比当前的时间大就计算延时的时间,进入休眠。等待休眠时间到以后再发送
  Message 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) {
                    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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

  }

native 的 Looper

  .....
  struct epoll_event eventItems[EPOLL_MAX_EVENTS];
  int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
  .....

4.处理消息结果

Handler 的 handleMessage 方法

  1. 首先判断 Message 是否指定了 callback
  2. 如果没有就判断是否有全局的 callback
  3. 如果没有全局的 callback 就调用重写后的 handleMessage 方法。
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

二、常见问题

1. Handler 延时消息的原理

Handler 消息的延时是处理延时,任何发送的消息都是第一时间入队的,在发送消息时会和当前时间做比较,如果需要延时就会计算延时时间,调用 epoll_wait 阻塞住,等待延时时间达到后唤醒当前的线程发送消息

2. IdleHandler原理

IdleHandler 可以在当前线程的消息队列空闲时做一些事情。它的原理是向 IdleHandler 的队列中插入了一个消息。当 MessageQueue 去查找队里中的消息时,如果队列中没有消息或者消息还没有达到发送的时间,就会执行 IdleHandler 队列中的消息

      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        .......

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
  1. IdleHandler 可以做一些延时初始化的任务。
  2. 当一个 View 频繁接受消息并刷新时,可以使用 IdleHandler,让任务队里先去处理消息,等到线程空闲时使用 IdleHandler 去更新最新的数据。

3. 子线程和主线程 Looper 的区别

子线程的 Looper 可以退出而主线程的是不可以的。

**4. 应用线程进入 looper 循环为什么没有 ANR **

ANR 是 Android 中的一种机制,它是在应用没有按时完成 AMS 指定的任务才触发的。组件在创建时会向 AMS 申请开始计时,当完成创建后会通知 AMS 取消计时。

进入 looper 循环后,AMS 会在 looper 线程中通过主线程的 Handler 发送消息给主线程去执行任务。所以即使进入 looper 循环, AMS 仍然可以和主线程交互。这也是为什么 ApplicationThread 接到任务后还需要发送 Handler 消息给 ActivityThread 去执行任务。

如果主线程中有其他任务导致 AMS 的任务被延时,或者 AMS 的任务本身很耗时才会触发 ANR

5.消息屏障

消息屏障是 Handler 优先执行异步消息的一种机制。在 android 中 choreographer 类在刷新 view 时使用到了

在子线程中setText可能成功么

在setTextView的时候会调用requestLayout会做线程检查,如果不是的主线程会抛出异常,但是不是绝对的,如果invalid 比 requestLayout执行快,那么不会抛出异常。

创建Handler时传入Callback有什么影响

Handler在取消息时会优先查找是否设置了callback(通过msg设置callback,或者通过Handler构造方法直接传入),性能会更好

image.png

为什么post一个runnable原理(如何让handleMessage快速执行)

使用handler直接post一个runnable对象,这个runnbale对象会被包装成一个message对象,这个runable会作为message的callback方法,结合上面来看如果message有callback那么会不经过判断执行执行handleMessage方法。

一直死循环不会造成cpu浪费么

在没有消息的时候,会阻塞在nativePollOnce方法上,让出cpu资源,进入休眠状态,当有新的任务进入时会重新唤醒cpu

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

推荐阅读更多精彩内容