Handler源码理解分析

作者: 请叫我张懂 | 来源:发表于2017-12-18 09:25 被阅读49次

准备

在理解Handler之前,需要对于链表和数组的有一定的理解:

链表和数组都是用于存储数据的集合,所以他们的用图是相同的。那么他们两者有什么区别呢?换一种话来讲,就是什么时候使用数据,什么时候使用链表的问题了?

数组:有索引做下标,访问元素快,但是在插入操作的时候就较慢。在插入元素的时候指定插入的位置,可能需要移动其他元素的位置,才有位置供新元素进行插入。如下图所示,当需要在元素"B"前插入一个元素"E"时,我们需要找到元素"B"的 index 并将 index 上包括 1 之后的元素向后移动才能腾出位置给"E"。

数组.png

链表:与数组相反,对于插入操作的时候较快,但是在访问元素时较慢。如下图所示,当需要在元素"B"前插入一个元素"D"时,我们需要找到元素"B"和记录下它前一个元素"A",然后将A.next指向元素"D",最后将D.next指向元素"B",即可完成,无需数组一样的元素移动。

链表.png

正文

安卓开发中不能在子线程中更新UI的操作(不绝对,详情View绘制流程-未完成)。所以我们在子线程中需要使用Handler来发送Message来让主线程更新UI。

Handler使用.png

在上图中的代码主要是实现,在3S后将界面中的text文本更新为"handleMessage"。那么这主要是如何实现的呢?为什么会从子线程切换到主线程的呢?让我们看看 sendMessage() 发生了什么。

mHandler.sendMessage(msg); 

我们点进方法中可以看到 sendMessage()的流程如下(参数忽略):

sendMessage() --> sendMessageDelayed() --> sendMessageAtTime()--> enqueueMessage()
Handler.java部分代码
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    
    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);
    }
  • 在sendMessage的时候,实际就是调用sendMessageDelayed只是delayMillis = 0 ;
  • 在sendMessageAtTime时, SystemClock.uptimeMillis() + delayMillis 为当前系统时间 + 延时(注意);
  • 在sendMessageAtTime中出现了MessageQueue进行存储Message;
  • msg.target是Handler对象

既然出现了MessageQueue那么我们看看MessageQueue.enqueueMessage()里面发生了什么:

MessageQueue.java部分代码(方法中删除了干扰理解的代码)
Message mMessages;

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) {
            ...
            
            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
            //添加第一个message,或message的执行时间为0或或者小于表头的时间(可以看出mMessage为表头)
                msg.next = p;
                mMessages = msg;
            } else {
                ...
                
                //死循环,找出message执行时间对应的位置再跳出
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    ...
                    
                }
                //插入元素
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
           ...
                
        }
        return true;
    }
  • msg.when为之前传入的 SystemClock.uptimeMillis() + delayMillis
  • mMessages为表头
  • msg.next为Message对象

从上面可以看出,这就是文章开头的链表插入的操作。由于这里频繁的执行插入操作,并且插入的位置除了开头和结尾,还有中间。如果使用数组来存储的话,将会损耗较多的性能。

下面用图进行说明(在子线程中要发送三条Message,观察它们的存储情况)
MessageQueue_1.png
  1. 在插入message1时,表头为null,所以 mMessages = message1;
MessageQueue_2.png

2.在插入message2时, p!= null、when != 0 、 when > p.when, 进入else。开始死循环,当prev = messsage1,p = null,跳出死循环

    message2.next = null;   message1.next = message2;
MessageQueue_3.png

3.在插入message3时,p!= null、when != 0、when > p.when,进入else。开始死循环,当prev = message1, p = message2时,message3.when < message2.when,跳出死循环

     message3.next = message2;   message1.next = message3;
MessageQueue_4.png

以上分析了Message存储消息队列的流程。那么消息是怎么被取出来在主线程执行的呢?什么时候执行
public void handleMessage(Message msg);的呢?

那么就要引入另一个对象了Looper。不知道有多少人试过在,子线程中new Handler()对象。试过的人都知道会出现如下的错误:

错误代码
      new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler = new Handler();
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Message message1 = Message.obtain();
                message1.obj = "handleMessage";
                mHandler.sendMessage(message1);
            }
        }).start();
报错信息
Looper_erro.png
正确代码
      new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        if (null != msg.obj)
                            Log.i("tag", msg.obj.toString());
                    }
                };
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message1 = Message.obtain();
                message1.obj = "handleMessage1";
                mHandler.sendMessage(message1);

                Message message2 = Message.obtain();
                message2.obj = "handleMessage2";
                handler.sendMessage(message2);
                Looper.loop();
            }
        }).start();

为什么要这样子写呢?我们看看Handler的构造方法:

Handler.java部分代码
    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 that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }

相信大家一定注意到了下面的代码,抛出的异常与控制台里的异常是一样的。且注意 mQueue = mLooper.mQueue;

  mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }

那Looper.mLooper()返回的值为什么为空呢?让我们再进一步深入

Looper.java部分源码
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    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));
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    
     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;
        ...
        //死循环
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            try {
                msg.target.dispatchMessage(msg);
                ...
            } finally {
               ...
            }
            ...
        }
    }

  • msg.target对应的是Handler对象
  • ThreadLocal最后会解释

mLooper() 方法中看出Looper对象是从ThreadLocal中得到的。它是通过调用prepare()方法实例化且保存的。认真的应该可以看出不仅在子线程调用了 Looper.prepare(); ,而且在子线程结尾处调用了 Looper.loop(); 如果这里没有该方法是无法让新建的Handler收到消息的,而且为什么它要放在线程的最后呢?
让我们看看在loop() 里执行了什么。拿到当前线程的 Looper 继而拿到 Looper 中的 MessageQueue。然后开始进入死循环, 当MessageQueue取出的Message不为空时,就调用 Handler的dispatchMessage()
。如果不放在最后,就会一直在死循环中,无法执行loop()之后的代码。

所以回到Handler的dispatchMessage()方法:
   public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在这里调用了 handleMessage()。不过是Message.CallBack为空的时候才调用类本身的handleMessage();

到目前为止是不是还有个疑问? 为什么在主线程中的new Handler()的时候不用Looper.prepare(),looper.loop();

ActivityThread.java部分源码
 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
        
        // --------------------------------------
        Looper.prepareMainLooper();
        // --------------------------------------


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

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

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        
        // --------------------------------------
        Looper.loop();
        // --------------------------------------

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
prepareMainLooper()方法
   public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

想必看到这两个方法就懂了吧!

所以为什么能在子线程中调用Handler来更新UI呢?
  1. 在子线程中添加Message到消息队列。
  2. 通过主线程的Looper来进行回调Handler中的dispatchMessage()方法。
  3. dispatchMessage()中调用handleMessage()方法。
fianl.png

插曲

ThreadLocal:提供线程内的局部变量,这种局部变量仅仅在线程的生命周期。每个线程中的变量不影响到其他线程。结合目前的情况就是 1 个线程对应 1个 Looper 对应 1 个MessageQueue

ThreadLocal.java部分代码
    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();
    }

相关文章

网友评论

    本文标题:Handler源码理解分析

    本文链接:https://www.haomeiwen.com/subject/hdpywxtx.html