关于Handler,你应该知道的

Handler、Looper、Thread、Message、MessageQueue的关系

每一个线程对象,都有且只有一个Looper对象与之关联。线程的Looper对象创建后,会自动创建一个MessageQueue作为该线程的消息队列。也就是说一个Thread对象对应一个Looper对象,对应一个MessageQueue。
创建Handler时,如果没有指定与之关联的Looper对象,那就默认和创建Handler的线程的Looper对象相关联。这里需要提到两点:

  • 通常情况下,一个线程不会自动创建它关联的Looper对象。这意味着我们在子线程中创建Handler对象时,要先确定该线程的Looper对象是否已创建。如果Looper对象没有创建,则需调用Looper.prepare()来为线程创建对应的Looper对象。或者直接关联主线程的Looper对象。
  • 在Android系统中,UI主线程ActivityThread默认会自动创建关联的Looper对象。
    因为可以在一个线程里创建多个Handler,也就是说Looper和Handler是一对多的关系。一个Handler对象只能关联一个Looper对象。

MessageQueue用来存放Handler发送的Message对象。MessageQueue由Looper管理。从MessagaQueue取出Message对象,分发给对应的Handler对象去处理。Looper通过Message对象的target属性,找到处理该消息的Handler对象。

Message 和 Runnable

当我们需要Handler发送一个Message时,不建议直接通过new Message()创建一个新的Message对象。更推荐使用Message.obtain()来获取Message对象。因为Message类中定义了一个Message对象池,它是一个单向链表。

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对象。如此,可以避免创建过多的对象而产生GC问题。
Handler不仅可以发送Message对象,还可以发送Runnable到消息队列。
一般发送Message对象是用于在线程间传递数据。而发送Runnable则是用于执行定时任务。从Handler类的源码可以看出,发送Runnable,还是需要将其包装成Message对象。

public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

Callback 和 handleMessage

细心的朋友会发现,Handler有这样一个构造函数,它需要一个Callback类型的参数,这个Callback是个接口,它也定义了一个handleMessage方法。

public Handler(Callback callback) {
        this(callback, false);
    }
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

我们知道,Handler类本身也有一个handleMessage方法。那么他们之间的区别是什么呢

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

区别就在于,调用dispatchMessage方法处理消息时,是否需要执行Callback.handleMessage()方法。如果执行Callback.handleMessage(),则Handler的handleMessage()方法不会执行。

Handler.post(Runnable)的用法

直接看代码

public class MainActivity extends Activity{
      public Handler handler = new Handler();
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println("main thread id " + Thread.currentThread().getId());
        test_handlerpost();
      }
      public void test_handlerpost() {
        new Thread() {
            @Override
            public void run() {
                try {
                    System.out.println("run thread id " + Thread.currentThread().getId());
                    sleep(1000);
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("Runnable thread id " + Thread.currentThread().getId());
                        }
                    };
                    handler.post(runnable);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }.start();

    }
}

这里需要注意的是,Runnable对象的run方法是在哪个线程里执行的。从下图的执行结果来看,Handler.post(Runnable)方法的Runnable任务是在创建Handler的线程里执行的。
image.png

Handler.postDelayed()的原理

这也是一个容易被忽视,但是很容易体现一个开发者对源码熟悉度和知识深度的问题,面试时通过这个问题,可以考察出你对MessageQueue管理Message的相关机制的理解。
首先简单说明postDelayed方法,它的作用是让Handler执行指定的Runnable任务,但是要延迟指定的时间之后再执行。

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

它有两个参数,Runnable r是需要Handler执行的任务,long delayMillis是指延迟多长时间执行,时间单位是毫秒。从源码可以看出,它内部调用了sendMessageDelayed方法

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

sendMessageDelayed方法内部调用了sendMessageAtTime,但是这里需要注意sendMessageAtTime的第二个参数SystemClock.uptimeMillis() + delayMillis,SystemClock.uptimeMillis() 获取的是系统从开机启动到现在所经过的时间,虽然这是一个相对时间,而不是当前时刻的绝对时间,但是Handler运行机制这个场景下,可以把它理解为一个表示当前时间的时间点。
关于Android中的时间和SystemClock相关,有兴趣的同学可以出门左转Android中的时间

接着看sendMessageAtTime方法

public boolean sendMessageAtTime(@NonNull 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);
    }

先判断MessageQueue是否为空,否则调用enqueueMessage方法,将Message对象添加到MessageQueue。但是这里的参数uptimeMillis,我们从上面的分析已经知道,指的是某一个时间点。那么这里,需要思考的是,enqueueMessage的过程,是立刻直接将Message对象添加到MessageQueue中,还是在指定的时间点再将Message对象添加到MessageQueue中呢?这就是postDelayed方法原理考察的重点。
我们接着看enqueueMessage方法的实现

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

首先根据mAsynchronous来判断是否调用Message对象的setAsynchronous方法。setAsynchronous传入true时,是指将这个Message对象设置为异步消息,否则就是普通消息。
关于普通消息、异步消息,有兴趣的同学可以看看Handler机制——同步屏障。
简单提一下就是,通常的Message对象的target都是指向处理该消息的Handler对象,这样Looper才知道应该将该消息分发给谁。而如果Message对象的target属性为null,意味着它是一个同步屏障(SyncBarrier),它也会被插入到MessageQueue中,作用是让排在它后面的异步消息(即msg.isAsynchronous()==true)才能被返回处理,普通消息不能被返回处理,直到同步屏障被移除。但是这套机制,我们日常开发中基本用不上,而且可以看到相关的方法大多是私有的。
接着看上面的代码,最后调用了queue.enqueueMessage方法

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

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

这个方法里,首先将传入的时间点(SystemClock.uptimeMillis() + delayMillis)赋值给msg对象的when属性。MessageQueue实际上是一个单向链表,接下来就是根据when的值,和当前队列中的Message对象,按时间先后,逐一比较以确定msg对象应该插入到什么位置。最后根据needWake判断是否需要唤醒队列所在的线程。
这就是Message对象进入MessageQueue的过程,简单总结就是,MessageQueue根据Message对象when属性的值,来确定其在队列中的排序。然后依次处理。

接下来看,Message对象从MessageQueue的取出的过程,主要是看MessageQueue的next方法。

Message next() {
        ...
        int nextPollTimeoutMillis = 0;
        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;
                }
            }
        ...
        }
}

next方法的源码很长,这里只列出了和取出Message对象的逻辑相关的部分。
首先调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与object.wait(),只不过是使用了Native的方法对这个线程精确时间的唤醒。第一次开始循环时nextPollTimeoutMillis为0,所以实际上没有发生阻塞。
先将待返回msg对象指向队列的第一个元素,然后判断msg != null && msg.target == null,这就是上面提到过的同步屏障相关的处理,如果msg不是异步消息就查找下一个,直到找到最近的异步消息,把它赋值给msg对象。
接着通过msg的when属性值和当前时间,如果当前时间点和msg.when相等那就直接返回msg。如果当前时间小于msg.when,就将两者的差值赋值给nextPollTimeoutMillis。
然后重新开始for循环,因为此时nextPollTimeoutMillis的值已经大于0,所以再次执行nativePollOnce会发生阻塞。直到msg.when所指的时间点。这也是在Looper.loop方法中,官方注释MessageQueue的next方法可能会阻塞的原因。
Looper.loop

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
    ...
}

关于MessageQueue的nativePollOnce方法和nativeWake方法
# Android 中 MessageQueue 的 nativePollOnce

以上是MessageQueue管理Message插入和取出的基本过程。但是日常开发中,你可能遇到过这样的情况:先通过PostDelayed设置了一个延迟30秒执行的任务RunnableA,消息队列会开始阻塞,紧接着立刻post另一个任务RunnableB,我们会发现RunnableB并不是在延时30秒后RunnableA执行完之后在执行。这就和MessageQueue的enqueueMessage方法最后,根据needWake来判断是否需要唤醒队列所在的线程有关。
回过头去看enqueueMessage方法,会发现needWake值和mBlocked相关性很高。而mBlocked的值又在next方法中设置过,如果next方法内部有阻塞(没有消息或者当前待取出的消息时间点未到),会把mBlocked的值设为true,在下一个Message进队列时会判断这个Message的位置,如果在队首就会将needWake赋值为mBlocked,也就是true,所以最后调用了nativeWake方法,来唤醒线程。
具体到RunnableA和RunnableB的场景:

  • 调用postDelayed方法,设置了一个延时30秒执行的任务RunnableA后,MessageQueue的next方法会调用nativePollOnce方法阻塞,Looper.loop方法阻塞。
  • 紧接着调用post方法设置任务RunnableB,消息进队,判断RunnableA对应的Message时间还没到,正在阻塞。把RunnableB插入到消息队列头部(RunnableA前面),此时在enqueueMessage方法最后会调用nativeWake方法唤醒线程。
  • MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper。
  • Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩29秒)继续调用nativePollOnce()阻塞。
  • 直到阻塞时间到或者下一次有Message进队。

postDelayed方法的实现原理总结
MessageQueue会根据postDelayed方法中设置的延时时间和方法的调用时间点相加之后得出的相对时间点,来将Message对象按时间排序到链表中,链表头部delay的时间短,尾部delay的时间长,因此保证delayed时间最长的不会阻塞住delay时间短的。每次有新的Message调用enqueueMessage方法进入队列时,会和链表头部的Message对象,比较两者when属性指向的时间点,如果新进来的Message,when属性所指向的时间点距离当前时间更近,MessageQueue就会将其放在链表头部。当MessageQueue的next方法获取这个Message对象时,先判断其when属性的时间是否需要delay,如果需要就block,等待时间到来唤醒执行或者新的Message对象插入到它前面,唤醒线程,重新判断。
所以handler.postDelayed方法并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。使用后者的方式,我认为是集中式的统一管理了所有message,而如果像前者的话,有多少个delay message,则需要起多少个定时器。而后者由于有了排序,而且保存了每个message的执行时间,因此只需一个定时器按顺序next即可。

Handler引发的内存泄漏

我们刚接触Handler时,一般的用法是类似这样的:

public class MainActivity extends Activity {
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Thread mThread = new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendMessage(new Message());
            }
        };
        mThread.start();
    }
}

如此,在子线程中执行耗时的操作,需要较长的时间才能执行完。
在java中,非静态内部类会默认持有外部类的强引用。这样的结果就是,在子线程执行耗时操作时,如果外部的Activity出现页面退出或关闭,那么原本应该被回收的Activity对象,会由于Handler对象还持有外部Activity对象的强引用,而导致Activity对象不能被回收,从而导致内存泄露。
这个问题的解决方案是:
1.将Handler类声明为静态类。如果处理消息需要用到外部Activity的引用,可以设为弱引用。

private static class StaticHandler extends Handler{
        WeakReference activity;
        public StaticHandler(Activity activity){
            this.activity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (activity.get()!=null){
                
            }
        }
    }

本文参考
你真的懂Handler.postDelayed()的原理吗?
Android 中 MessageQueue 的 nativePollOnce
Handler机制——同步屏障
Android中的时间

你可能感兴趣的:(关于Handler,你应该知道的)