1、前言

在 Android 开发中,Handler 的机制和运行原理这方面的常识能够说是每个人都需求了解的。这不仅是由于 Handler 是 Android 运用的基石之一,也由于 Handler 全体设计上也是十分优秀的。接下来我就整理总结一下常见的 Handler 相关常识点。

2、根本运用(GPT)

  1. 创立Handler目标:要运用Handler,首要需求创立一个Handler目标。Handler能够在UI线程或其他线程中创立,但一般在UI线程中创立,以便将音讯发送到UI线程。

    Handler handler = new Handler();
    
  2. 发送音讯:要将音讯发送到Handler,能够运用Handler的post办法或sendMessage办法。一般,您将运用post办法履行一个Runnable任务。

    handler.post(new Runnable() {
        @Override
        public void run() {
            // 在UI线程履行的任务
            // 能够更新UI元素
        }
    });
    

    或许运用sendMessage办法:

    Message message = handler.obtainMessage();
    message.what = MY_MESSAGE_CODE;
    handler.sendMessage(message);
    
  3. 处理音讯:在Handler地点的线程中,能够覆盖handleMessage办法来处理音讯。一般,您需求继承Handler类并重写handleMessage办法。

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MY_MESSAGE_CODE:
                    // 处理音讯
                    break;
                // 能够处理更多不同音讯类型
            }
        }
    }
    
  4. 相关Handler与Looper:Handler需求与Looper(音讯循环)相关,以便能够在音讯行列中接纳和处理音讯。一般,UI线程现已有一个与之相关的Looper,所以在UI线程中创立Handler不需求额外装备。但如果您在其他线程中创立Handler,需求先创立一个Looper。

    Looper.prepare(); // 创立一个新的Looper
    Handler handler = new Handler(); // 相关Handler与新的Looper
    Looper.loop(); // 开始音讯循环,有必要调用以使Looper活动
    
  5. 从后台线程向UI线程发送音讯:一般情况下,Handler最常用于在后台线程履行任务后更新UI线程。例如,如果您在后台线程中进行网络请求,请求完成后,能够运用Handler将成果传递给UI线程以更新UI元素。

    new Thread(new Runnable() {
        @Override
        public void run() {
            // 后台线程履行任务
            // ...
            // 任务完成后,运用Handler将成果传递给UI线程
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 更新UI
                }
            });
        }
    }).start();
    

这些是Android Handler的根本用法。Handler是Android中处理异步任务和多线程通信的重要工具,能够保证UI更新等操作在UI线程中履行,从而防止运用程序溃散或出现不稳定行为。

3、流程整理

从 2 中能够看出 Handler 有两种发送信息的办法。第一种是发送 Message;第二种是直接 post runnable。咱们分别看下两种办法的源码处理。

3.1 获取 Message 的办法

发送 Message 首要需求获取一个 Message,当然能够直接 new 一个目标出来,可是也能够经过 Message.obtain() 办法来获取一个音讯池里边的音讯目标。

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

可见这种办法能够削减内存分配和废物收回的开支,由于它防止了频频创立和销毁 Message 目标,而是重复运用已有目标。这在Android中的音讯处理机制中非常有用,由于一般会有很多的音讯目标需求创立和处理,如Handler中的音讯行列。因此,Message.obtain 办法的运用办法类似于享元模式,经过同享可复用的目标来削减体系资源的耗费,进步性能。这有助于更有效地办理Android运用程序中的音讯处理。

3.2 压入音讯行列

这儿边接着往下看,会经过 Message.enqueueMessage 将这个音讯压入音讯行列中。这儿就要说下这个 Looper 的获取办法了。检查代码能够看到 Looper 是从 ThreadLocal 里边获取到的。ThreadLocal 保证了在每个线程内只要一个 Looper 目标。到这儿音讯现已进入音讯行列中了。

final MessageQueue mQueue;
public Handler(@Nullable 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;
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

接下来就是取音讯的过程,取音讯的办法是在 Looper.java 的 loop 办法中,如果是在子线程运用的情况下需求自己手动发动 Looper.loop 办法开启轮询。主线程中则是由体系在 ActivityThread.java 的 main 办法里边为咱们开启了轮询。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

接着看 loop 办法,这儿边是一个死循环,会一直从音讯行列中获取音讯。获取到了后会履行 msg.target.dispatchMessage(msg); 办法。能够看到在 android30 里边现已体系现已集成了检测耗时音讯的机制(logSlowDelivery 相关代码)。

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // Make sure the observer won't change while processing a transaction.
        final Observer observer = sObserver;
        final long traceTag = me.mTraceTag;
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
            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);
            }
        }
        if (logSlowDelivery) {
            if (slowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    Slog.w(TAG, "Drained");
                    slowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                                msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    slowDeliveryDetected = true;
                }
            }
        }
        if (logSlowDispatch) {
            showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        msg.recycleUnchecked();
    }
}

这儿边 target 在 enqueueMessage 现已设置成了发送 handler。所以履行逻辑会回到 handler 的 dispatchMessage 办法里边。

3.3 音讯履行

这儿边能够先看一下 post runnable 办法。其实仍是发送的 callback 是 runnable 的 Message。所以处理流程都是一致的。

// Handler.java   
public void dispatchMessage(@NonNull Message msg) {
    // 1、如果 msg 存在 callback 则直接履行,post 办法都会走到这儿
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 2、mCallback 不为空则进入这儿处理,这个 mCallback 能够经过构造办法传入
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 3、最后会走到自身 handleMessage 办法,这个办法能够经过继承重写
        handleMessage(msg);
    }
}
public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

走到这儿,能够发现咱们的 Handler 机制在 Java 层现已完全整理一遍了。下面持续看下 native 层的部分。这儿就引入了一个经典问题:那就是主线程 loop 办法是死循环,体系为什么不会卡死呢?
从源码能够看到 loop 办法里边调用了音讯行列的 next 办法,这儿边会调用持续调用 native 的 nativePollOnce(ptr, nextPollTimeoutMillis); 办法。这儿边会经过 epoll 机制,当等候音讯的时候,会开释体系资源,当被唤醒时再持续操作。唤醒操作 nativeWake(mPtr); 。

// 0 当即回来,2000 等候 2s,-1 永久休眠
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
// 底层经过 epoll 的办法监听读端,会进入等候,等候写入端有数据写入
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 刺进音讯的时候,会调用 wake 办法,会写入了 1,唤醒
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));

3.4 音讯屏障

音讯屏障的典型用例是在UI线程中履行UI更新,以保证UI更新的操作依照它们被提交的次序履行。例如,如果在后台线程中进行了多次UI更新,并将这些更新音讯发送到UI线程的音讯行列中,能够运用 sendMessageAtFrontOfQueue 办法来保证这些UI更新依照它们被发送的次序履行,从而防止UI显示的不一致性。经过 sendMessageAtFrontOfQueue 办法会将时间设置为 0,在进入音讯行列的过程中,会直接刺进到行列头部,所以能够保证履行优先级较高。

3.5 IdleHandler

IdleHandler是Android中的一个回调接口,它用于在主线程闲暇时履行任务。当主线程没有处理音讯时(即处于闲暇状况),IdleHandler中的回调办法将被触发,答应您履行一些耗时较长的任务,而不会影响到UI的响应性。这在某些情况下非常有用,例如在后台预加载数据或履行其他非UI相关的工作。

// 创立一个IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        // 在主线程闲暇时履行的任务
        // 能够履行一些耗时操作,不会堵塞UI线程
        return false; // 回来true表明持续监听,false表明不再监听
    }
};
// 注册IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);

这块处理逻辑是在 MessageQueue 的 next 办法内部。

4、总结

到这儿根本整理了 Handler 的一些运用和原理,尽管各种结构和 Kotlin 都能够很便利的履行切换线程的操作了,可是这些原理性的东西仍是值得咱们学习并了解的。