万人收藏!关于Android Handler源码解析,看这一篇就够了!

Handler作用

  • 任务调度:即通过 post()send() 等方法来指定某个任务在某个时间执行
  • 线程切换:执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。 而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。因为Android的UI是线程不安全的。 在Android中使用Rxjava,还要配合RxAndroid来使用,RxAndroid 内部就使用 Handler 来实现线程切换。

常见错误

常见的子线程中更新UI,复现代码。

textView = (TextView) findViewById(R.id.txt);
  new Thread(new Runnable() {
    public void run() {
        SystemClock.sleep(3000);//这句不加不会报错,具体分析见上面链接
        textView.setText("from来自子线程"); 
    }
  }).start();

复制代码

运行异常信息

ErrorInfo: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6903)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1050)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.view.View.requestLayout(View.java:19785)
        at android.widget.TextView.checkForRelayout(TextView.java:7368)
        at android.widget.TextView.setText(TextView.java:4480)
        at android.widget.TextView.setText(TextView.java:4337)
        at android.widget.TextView.setText(TextView.java:4312)
复制代码

可以看到错误发生在android.view.ViewRootImpl#checkThread

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
}
复制代码

可见此处会判断mThread是不是等于当前线程 看下mThread到底是啥,在何处赋值的

public ViewRootImpl(Context context, Display display) {
        ...
        mThread = Thread.currentThread();
        ...
}
复制代码

在构造方法中被赋值的,也就是说是创建ViewRootImpl时所在的线程 ViewRootImpl又是在哪里被创建的呢?这里不深入讲了,是在main线程。

基础用法

android.os.Handler handler = new Handler(){//在主线程中获取handler
  @Override
  public void handleMessage(final Message msg) {
    //这里接受并处理消息
  }
};

new Thread(() -> {
     try {
         Thread.sleep(2000);//子线程中执行耗时操作
         //发送消息
         Message message = Message.obtain();
         message.what=1;
         message.obj=new Object();
         handler.sendMessage(message);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
}).start();

new Handler().post(new Runnable() {
    @Override
    public void run() {
        //doSomething
    }
});

复制代码

实例化一个 Handler 重写handleMessage 方法 ,然后在需要的时候调用它的 send 以及 post 系列方法就可以了,非常简单易用,并且支持延时消息。(更多方法可查询 API 文档)

但是我们并没有看到Handler是如何与MessageQueue以及Looper关联起来的,下面我们进入源码分析下

Handler 源码分析

Handler 实例化

从构造函数开始,我们通常从主线程中创建,先看下Handler的构造函数有哪些

  • Handler()

  • Handler(Callback callback)

  • Handler(Looper looper)

  • Handler(Looper looper, Callback callback)

  • Handler(boolean async)

  • Handler(Callback callback, boolean async)

  • Handler(Looper looper, Callback callback, boolean async)

看最后两个构造方法就行,因为前面的几个也是依次调用到后的方法

先看Handler(Callback callback, boolean async)

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;
    }
复制代码

Handler(Looper looper, Callback callback, boolean async)与上面的区别就是Looper是赋值进去的。

Looper 实例化

由上面可以看到调用Looper#myLooper方法获取到Looper对象, 如果mLooper == null的话,会抛出异常

Can't create handler inside thread that has not called Looper.prepare()

这个错误我们应该也见过。实际上我们在实例化 Handler 的时候 会去检查当前线程的 Looper 是否存在,如果不存在则会报异常,也就是说在创建 Handler 之前一定需要先创建 Looper 。 我们平时一般不会遇到这个错,因为我们大多数都是在主线程创建Handler的,而为什么在主线程就不要自己创建Looper,我们待会再看,目前只需要知道如果Looper.myLooper()没有获取到Looper对象的话就会报这个错。

我们跟踪Looper#myLooper方法进去,解决为什么会抛出这个异常。

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

复制代码

只有一行代码,从线程中取出Looper对象,那么我们有理由相信,这个ThreadLocal是通过set方法把Looper对象设置进去的。

想一想ThreadLocal在哪里把Looper对象设置进去了呢。回到刚才想要解决的问题:Can’t create handler inside thread that has not called Looper.prepare() 。那会不会是Looper的prepare方法呢?

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));
}
复制代码

ThreadLocal确实是在Looper#prepare方法里把Looper对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper对象。

所以,要创建Handler,那么Looper.myLooper()就必须非空,上面分析得出要非空,要先调用Looper.prepare()

到了这里,LooperThreadLocal建立起了关联。

MessageQueue 实例化

接着上面继续看下Looper的构造方法

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}
复制代码

每当我们实例化一个 Looper 的时候会调用它的构造方法,并在其中实例化一个 MessageQueue,相比于 LooperHandlerMessageQueue 就显得相对复杂一些。因为内部用到了 JNI 编程。初始化、销毁和入队等事件都用到了 native 的方法。

我们接着看Handle构造函数里的

mQueue = mLooper.mQueue

我们知道消息是存放在MessageQueue消息队列中的,而MessageQueue就是在上面Looper构造函数中new出来的,至此Handler通过LooperMessageQueue也建立起了关联。

总结一下,创建Handler,他的构造函数中会先调用Looper.myLooper()获取Looper,也即是从ThreadLocal中获取,而ThreadLocal中要想获取到,要先调用Looper.prepare() 来set值,那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()吧,也不会抛出异常。其实这是一个特殊情况,我们通常都是在主线程,也就是UI线程中创建handler的。而在主线程中,系统已经为我们创建了一个Looper对象,所以不会抛出异常了,而那些会抛出异常报错的情况,是在子线程中创建的Handler,但是又没有调用Looper.prepare()去创建Looper对象。 继续看,主线程在什么时候创建了Looper对象吧。

ActivityThread的main方法,这个方法是应用程序的入口。

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        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");
    }
复制代码

Looper.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();
        }
    }
复制代码

可以看到第一行还是调用了prepar(false)方法的(false代表不可退出)。所以主线程是已经创建了一个Looper对象的。

Handler的创建过程分析完毕,现在总算搞明白了。

Handler、MessageQueue 和 Looper 之间的关系

最后再总结一下,Handler的创建是依赖于Looper的。而主线程是默认创建了一个Looper对象的。每一个Looper会关联一个线程(ThreadLocal中封装了Looper)。每一个Looper中又会封装一个消息队列。 这样一来,HandlerLooperMessageQueueThread四个角色就关联了起来。 Handler在主线程中创建,是因为要和主线程的消息队列关联起来,那样Handler#handleMessage方法才会在主线程中执行,那么这样在更新UI就是线程安全的了。

Handler 发送消息过程

回想开头我们基础用法里提到 Handler一般是通过一下2个方法发送的

handler.sendMessage(message); handler.post(runnable);

发送过程

我们先从第一个开始分析 handler.sendMessage(message)

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);
}
复制代码

sendMessage会调用sendMessageDelayed方法并将message对象传进去,第二个参数是延时时间,使用sendMessage方法时默认为0的,最后都会调用sendMessageAtTime。 上面分析了,在创建Looper对象的时候,会创建一个MessageQueue,所以只要Looper是正常创建的话,消息队列是不为空的。 那么到最后一行的enqueueMessage方法,源码如下

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
 }
复制代码

handler本身赋值给msg.target

msg.setAsynchronous(true设置message是否是异步的,这是message的一个属性。同一个Thread只有一个Looper,一个MessageQueue,但是可以有很多个Handler,如果Handler初始化的时候async参数是true,那么这个Handler所post的所有的message都会带上异步的属性。可以通过MessageQueue``的postSyncBarrier(long when)来向队列中插入一个同步分割栏,同步分割栏是一个特殊的message,这种message的target=null,就像一个卡子,当他被插入时,会卡住在这之后的所有的同步的message,只会摘取异步的message。当然也可以通过MessageQueue的removeSyncBarrier(int token)来移除这个同步分割栏,token就是postSyncBarrier方法的返回值。但是目前这两个方法都被hide了。所以大家一般用到的都只是普通的Message。

然后最终调用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) {
                //很明显enqueueMessage需要同步,因为存在多个线程往一个Loop线程的MessageQueue中插入消息的场景。 
                //这里其实是将Message根据延时插入到特定的地方,先看下关键点1,mMessages其实代表消息队列的头部,如果mMessages为空,说明还没有消息,如果当前插入的消息不需要延时,或者说延时比mMessages头消息的延时要小,那么当前要插入的消息就需要放在头部
                //至于是否需要唤醒队列,则需要根据当前的Loop线程的状态来判断,后面讲Loop线程的时候再回过头说;
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //再来看下关键点2,这个时候需要将消息插入到队列中间,其实就是找到第一个Delay事件小于当前Message的非空Message,并插入到它的前面,往队列中插入消息时,如果Loop线程在睡眠,是不应该唤醒的,异步消息的处理会更加特殊一些,先不讨论。
                //最后看关键点3,如果需要唤醒Loop线程,通过nativeWake唤醒,以上,就是普通消息的插入。

                // 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;
    }
复制代码

Messagequeue中有一个对象mMessage用于指向当前传进的msg,即最新的消息。而刚才的sendMessageAtTime(Message msg, long uptimeMillis)方法,第二个参数指定了时间,然后在这里按照这个uptimeMillis来进行消息的排序,这样每一个消息都是按照时间的排序关联了起来,排在前面的消息指向了排在后面的消息。

以上是进入消息队列的分析,Handler调用sendMessage方法的最终将message对象传进Messagequeue

取出消息

那么消息是怎么从消息队列出来的呢? 这时我们要回看ActiviryThread的main方法,去寻找点线索。源码在上面已贴出。 发现了倒数第二行的Looper.loop(),简单理解就是消息执行循环操作。 android.os.Looper#loop

public static void loop() {
        //确保MessageQueue准备好
        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 (;;) {
            //for 无限循环,阻塞于消息队列的 next() 方法;
            //不断从队列中读取消息并移除,如果队列为空,阻塞等待
            Message msg = queue.next(); // might block 
            if (msg == null) {//跳出循环,looper退出就是利用了这点
                // No message indicates that the message queue is quitting.
                return;
            }

            ...

            try {
                msg.target.dispatchMessage(msg);
                ...
            } finally {
               ...
            }
            ...
            //清理,回收到缓存池
            msg.recycleUnchecked();
        }
    }
复制代码

loop方法是个死循环,但是为什么不会卡死主线程呢?

loop内容有点复杂,借用一张图来看下

当我们调用 Looper#loop() 方法之后整个 Looper 循环就开始不断地处理消息了。在上图中就是我们用绿色标记的一个循环。当我们在循环中调用 MessageQueue#next()`` 方法来获取下一个消息的时候,会调用 nativePollOnce() 方法,该方法可能会造成线程阻塞和非阻塞,当线程为非阻塞的时候就会从 Native 层回到 Java 层,从 MessageQueuue 中取得一个消息之后给 Looper 进行处理。如果获取的时候造成线程阻塞,那么有两种情况会唤醒阻塞的线程,一个是当一个新的消息被加入到队列中,并且将会早于之前队列的所有消息被触发,那么此时将会重新设置超时时间。如果达到了超时时间同样可以从睡眠状态中返回,也就回到了 Java 层继续处理。所以,Native 层的 Looper 的作用就是通过阻塞消息队列获取消息的过程阻塞 Looper

再看下关键的Message msg = queue.next()

Message next() {
        ...

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //是否需要阻塞等待,第一次一定不阻塞
            // 调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。
            // 在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。
            // 如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。
            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;
                //是否存在barier
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    //存在同步分隔栏,找到后面异步属性的msg
                    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;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //没有可以即刻执行的Message,查看是否存在需要处理的IdleHandler,如果不存在,则返回,阻塞等待,如果存在则执行IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            // 如果目前没有消息,已经处在空闲状态,则执行 idler.queueIdle
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            //处理完IdleHandler ,需要重新判断Message队列 nextPollTimeoutMillis赋值为0
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
复制代码

上面分析过msg.target就是handler,所以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);
        }
    }
复制代码

由于这种方法没有传callback,所以最终调用handleMessage,我们来看下

 /**
  * Subclasses must implement this to receive messages.
  */
  public void handleMessage(Message msg) {
  }
复制代码

看到这里,相信大家应该很熟悉了,这就是我们重写的方法。

我们再看看另一个发送消息的方法 handler.post(runnable)

public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
}
复制代码

接收一个实现了Runable接口的对象,然后将其传进getPostMessage()方法。跟进getPostMessage()方法看看

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
}
复制代码

其实就是将Runable包装成message的callback嘛。 所以,如果我们使用post方法发送消息,在执行dispatchMessage的时候,callback字段是不为空的,那么就会执行handleCallback()方法,而不是执行handleMessage方法了。

private static void handleCallback(Message message) {
        message.callback.run();
}
复制代码

空闲处理者的添加与处理

什么是空闲处理者

通过上面的分析可知 MessageQueue 通过 next 方法通过死循环获取下一个要处理的 Message, 若当前时刻不存在要处理的消息, 下次循环会进行睡眠操作

  • 在没有取到可执行消息 ---> 下次 for 循环进行睡眠 之间的时间间隔, 称之为空闲时间
  • 在空闲时间处理事务的对象, 称之为空闲处理者

空闲处理者的添加

 public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
}

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}
复制代码

通过上述代码可以得到以下的信息

  • 空闲处理者使用 IdleHandler 接口描述
  • 空闲处理者通过 MessageQueue.addIdleHandler() 添加
  • 空闲处理者使用 MessageQueue.mIdleHandlers 维护

空闲消息的处理

public final class MessageQueue {

    // 空闲消息集合
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    // 空闲消息处理者的数组
    private IdleHandler[] mPendingIdleHandlers;

    Message next() {
        ...... 
        for (;;) {
            ......
            synchronized (this) {
                // 省略获取 msg 的代码
                ......
                // 1\. 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 2 若无空闲处理者, 则进行下一次 for 循环
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                ......
                // 3\. 将空闲消息处理者集合转为数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 4\. 处理空闲消息
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];// 获取第 i 给位置的空闲处理者
                mPendingIdleHandlers[i] = null; // 置空
                boolean keep = false;        
                try {
                    // 4.1 处理空闲消息
                    keep = idler.queueIdle(); 
                } catch (Throwable t) {
                    ......
                }
                if (!keep) {
                    synchronized (this) {
                        // 4.2 走到这里表示它是一次性的处理者, 从 mIdleHandlers 移除
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ......
        }
    }
}
复制代码

好的, 可以看到 MessageQueue.next 在获取不到 msg 时, 会进行一些空闲消息的处理

  • 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
  • 若无空闲处理者, 则进行下一次 for 循环
  • 若存在空闲处理者, 则空闲消息处理者集合转为数组 mPendingIdleHandlers
  • for 循环处理空闲消息
    • 调用 IdleHandler.queueIdle 处理空闲消息
      • 返回 true, 下次再 MessageQueue.next 获取不到 msg 的空闲时间会继续处理
      • 返回 false 表示它是一次性的处理者, 从 mIdleHandlers 移除

总结

我们发现不管是使用post方法还是sendMessage方法来发送消息,最终都会调用sendMessageDelayed方法。handler将消息追加到消息队列中的过程都是一样的,然后Looper不断的从MessageQueue中取出消息,并由handler去分发消息,处理消息,这样就构成了完善的Android消息机制体系。

Handler扩展

Handler 虽然简单易用,但是要用好它还是需要注意一点。

由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。

常见内存泄漏

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。

这个泄露是因为Message会持有Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息

示例代码如下:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}
复制代码

并且再在 Activity.onDestroy() 前移除消息,加一层保障:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}
复制代码

这样双重保障,就能完全避免内存泄露了。

注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行。

Handler 里的 Callback 用处

Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?

来看看 Handler.dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
  //这里的 callback 是 Runnable
  if (msg.callback != null) {
    handleCallback(msg);
  } else {
    //如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}
复制代码

可以看到 Handler.Callback优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 HandlerhandleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理

这个就很有意思了,这有什么作用呢?

我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!

场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。

创建 Message 实例的最佳方式

由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。

方法有二:

  1. 通过 Message 的静态方法 Message.obtain(); 获取;
  2. 通过 Handler 的公有方法 handler.obtainMessage();

妙用 Looper 机制

我们可以利用 Looper 的机制来帮助我们做一些事情:

  • Runnable post 到主线程执行

    Activity.runOnUiThread(Runnable)

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
    复制代码
    

    View.post(Runnable)

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            //直接通过handler发送Post消息
            return attachInfo.mHandler.post(action);
        }
        //先加入队列,等attachInfo被赋值时,会通过handler发送消息.
        getRunQueue().post(action);
        return true;
    }
    复制代码
    
  • 利用 Looper 判断当前线程是否是主线程

    public final class MainThread {
        private MainThread() {
        }
    
        private static final Handler HANDLER = new Handler(Looper.getMainLooper());
    
        public static void run(@NonNull Runnable runnable) {
                if (isMainThread()) {
                    runnable.run();
                }else{
                    HANDLER.post(runnable);
                }
        }
    
        public static boolean isMainThread() {
            return Looper.myLooper() == Looper.getMainLooper();
        }
    }
    复制代码
    

Looper 和 Handler 一定要处于一个线程吗?子线程中可以用 MainLooper 去创建 Handler吗?

LooperHandler 不需要再一个线程中,默认的情况下会从ThreadLocal 中取当前线程对应的 Looper,但我们可以通过显式地指定一个 Looper 的方式来创建 Handler. 比如,当我们想要在子线程中发送消息到主线程中,那么我们可以

Handler handler = new Handler(Looper.getMainLooper());
复制代码

子线程中进行UI操作的方法

  • Handler的post()方法

  • View的post()方法

  • Activity的runOnUiThread()方法

如何理解Handler的异步

MessageQueue.next() 会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢? MessageQueue.next() 方法内部的原理?

调用 MessageQueue.next() 方法的时候会调用 Native 层的 nativePollOnce() 方法进行精准时间的阻塞。在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。如果没有从 Native 层得到消息,那么这个方法就不会返回。此时主线程会释放 CPU 资源进入休眠状态。

当我们加入消息的时候,会调用 MessageQueue.enqueueMessage() 方法,添加完 Message 后,如果消息队列被阻塞,则会调用 Native 层的 nativeWake() 方法去唤醒。它通过向管道中写入一个消息,结束上述阻塞,触发上面提到的 nativePollOnce() 方法返回,好让加入的 Message 得到分发处理。

MessageQueue.enqueueMessage() 使用 synchronized 代码块去进行同步。

Looper 的退出方法?

quit() 和 quitSafely() 有什么区别 子线程中创建了 Looper,在使用完毕后,终止消息循环的方法? quit() 和 quitSafely() 的本质是什么?

quit()quitSafely() 的本质就是让消息队列的 next() 返回 null,以此来退出Looper.loop()quit() 调用后直接终止 Looper,不在处理任何 Message,所有尝试把 Message 放进消息队列的操作都会失败,比如 Handler.sendMessage() 会返回 false,但是存在不安全性,因为有可能有 Message 还在消息队列中没来的及处理就终止Looper了。 quitSafely() 调用后会在所有消息都处理后再终止 Looper,所有尝试把 Message 放进消息队列的操作也都会失败。

知识点汇总

由前文可得出一些知识点,汇总一下,方便记忆。

  1. Handler 的背后有 LooperMessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理
  2. 在创建 Handler 之前一定需要先创建Looper
  3. Looper 有退出的功能,但是主线程的 Looper 不允许退出
  4. 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出
  5. Runnable 被封装进了 Message,可以说是一个特殊的 Message
  6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成Looper所在的线程,并不是创建 Handler 的线程
  7. 使用内部类的方式使用Handler可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类

由于篇幅原因,上文一些知识点,有些没有详细深入的去讲,甚至省略了。想进一步深入学习的同学可以详见我整理编写的《Android Framework精编内核解析》,可以在评论区留言或私信我获取!



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

推荐阅读更多精彩内容