Handler消息处理机制,相信做Android的同学都知道,我们先来看下面一段代码:

import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.widget.Toast;public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        new Thread(new Runnable() {            @Override            public void run() {                Message message = new Message();                message.what = 1;                handler.sendMessage(message);            }        }).start();    }    private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case 1:                    Toast.makeText(MainActivity.this, "handle message", Toast.LENGTH_SHORT).show();                    break;            }        }    };}

上面这种方式主要用在子线程中数据处理完之后更新UI。对于这种方式相信大多数刚接触android不久的同学也都知道,也就是初级认识,那么Handler是如何实现线程间通信的呢?相信读过《Android艺术探索》的同学都知道,每个线程都有一个Looper,且每个线程只有一个Looper(为什么只有一个Looper?下面介绍),那么不例外我们主线程也有一个Looper,从上面代码中可以看到Handler是在主线程中创建,而在子线程中通过调用handler.sendMessage();方法将消息发送给主线程,如此实现了线程间通信,也就可以更新UI了。那Handler是消息流程过程是怎样的呢?

Android Handler消息处理机制面试5连问_第1张图片

如上图所示实现消息循环 ,具体源码如下:

/** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in {@link #handleMessage}, * in the thread attached to this handler. *   * @return Returns true if the message was successfully placed in to the  *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting. */public final boolean sendMessage(Message msg){    return sendMessageDelayed(msg, 0);}/** * Enqueue a message into the message queue after all pending messages * before (current time + delayMillis). You will receive it in * {@link #handleMessage}, in the thread attached to this handler. *   * @return Returns true if the message was successfully placed in to the  *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting.  Note that a *         result of true does not mean the message will be processed -- if *         the looper is quit before the delivery time of the message *         occurs then the message will be dropped. */public final boolean sendMessageDelayed(Message msg, long delayMillis){    if (delayMillis < 0) {        delayMillis = 0;    }    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}/** * Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) uptimeMillis. * The time-base is {@link android.os.SystemClock#uptimeMillis}. * Time spent in deep sleep will add an additional delay to execution. * You will receive it in {@link #handleMessage}, in the thread attached * to this handler. *  * @param uptimeMillis The absolute time at which the message should be *         delivered, using the *         {@link android.os.SystemClock#uptimeMillis} time-base. *          * @return Returns true if the message was successfully placed in to the  *         message queue.  Returns false on failure, usually because the *         looper processing the message queue is exiting.  Note that a *         result of true does not mean the message will be processed -- if *         the looper is quit before the delivery time of the message *         occurs then the message will be dropped. */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);}

从源码可知,调用handler.sendMessage()方法最终是调用enqueueMessage()将消息放入消息队列MessageQueue,然后同过Looper.loop()方法取出消息。

/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */public static void loop() {    final Looper me = myLooper();    if (me == null) {        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");    }    final MessageQueue queue = me.mQueue;    // Make sure the identity of this thread is that of the local process,    // and keep track of what that identity token actually is.    Binder.clearCallingIdentity();    final long ident = Binder.clearCallingIdentity();    // Allow overriding a threshold with a system prop. e.g.    // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'    final int thresholdOverride =            SystemProperties.getInt("log.looper."                    + Process.myUid() + "."                    + Thread.currentThread().getName()                    + ".slow", 0);    boolean slowDeliveryDetected = false;    for (;;) {        Message msg = queue.next(); // might block        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }        // This must be in a local variable, in case a UI event sets the logger        final Printer logging = me.mLogging;        if (logging != null) {            logging.println(">>>>> Dispatching to " + msg.target + " " +                    msg.callback + ": " + msg.what);        }        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;        try {            msg.target.dispatchMessage(msg);            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;        } finally {            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();    }}

loop()方法中关键一行代码msg.target.dispatchMessage(msg);将消息分配给handler进行处理,如此完成消息循环,其中msg.target就是Handler。从loop源码中可以看出,loop是一个无限循环,那主线程为什么不会发生ANR呢?

       在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

       读过《Android艺术探索》我们也知道,一个线程只有一个Looper,那为什么呢?看下面源码,从Looper.prepare()入手。

/** Initialize the current thread as a looper.  * This gives you a chance to create handlers that then reference  * this looper, before actually starting the loop. Be sure to call  * {@link #loop()} after calling this method, and end it by calling  * {@link #quit()}.  */public static void prepare() {    prepare(true);}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));}

从源码中可以发现,如果当前线程中已经存在Looper,则会抛出"Only one Looper may be created per thread"的异常。那handler机制如何保证线程消息不被其他线程处理呢?答案是TreadLocal,那ThreadLocal是如何保证数据的独立性的呢?继续看源码。

public void set(T value) {     Thread t = Thread.currentThread();     ThreadLocalMap map = getMap(t);     if (map != null)         map.set(this, value);     else         createMap(t, value); }    public T get() {     Thread t = Thread.currentThread();     ThreadLocalMap map = getMap(t);     if (map != null) {         ThreadLocalMap.Entry e = map.getEntry(this);         if (e != null) {             @SuppressWarnings("unchecked")             T result = (T)e.value;             return result;         }     }     return setInitialValue(); }

关键代码:Thread t = Thread.currentThread();取的是当前线程的ThreadLocal,如此保证数据的独立性。

 

如有不正之处,欢迎指正!谢谢!

更多相关文章

  1. Android应用程序键盘(Keyboard)消息处理机制分析(12)
  2. 在Android中使用Handler和Thread线程执行后台操作
  3. Android消息机制——Handler、Looper、MessageQueue
  4. Android的线程模型
  5. Android 消息提示框:五种Toast详解
  6. Android 消息机制之Message
  7. Android的消息处理机制(深入源码)
  8. Android 多线程AsyncTask详解

随机推荐

  1. 自定义自己的AlertDialog
  2. Android(安卓)的整体布局
  3. Android(安卓)获取屏幕高宽度,密度,通知栏
  4. Android图像处理相关文章
  5. Android集成腾讯bugly-tinker热更新使用
  6. android hessian
  7. Android短彩信源码解析-短信发送流程(二)
  8. Android自定义Dialog(自定义主题、自定义
  9. Android(安卓)自绘输入框
  10. 仿QQ android 实战(学习 android 先来个QQ