Handler源码分析

这是我参与更文挑战的第4天,活动详情查看: 更文挑战html

本文基于 Android 9.0.0的源代码,来分析Handler的用法java

framework/base/core/java/andorid/os/
  - Handler.java
  - Looper.java
  - Message.java
  - MessageQueue.java
复制代码

Handler做用

  • 任务调度:即经过 post()send() 等方法来指定某个任务在某个时间执行
  • 线程切换:执行耗时的操做,好比网络请求,IO操做等,须要在子线程中运行,否则会阻塞主线程。 而执行完网络请求等耗时操做后一般须要更新UI,若是在子线程中更新UI,那么程序会崩溃。由于Android的UI是线程不安全的。 在Android中使用Rxjava,还要配合RxAndroid来使用,RxAndroid 内部就使用 Handler 来实现线程切换。

常见错误

常见的子线程中更新UI,复现代码,更具体见 Android子线程和更新UI问题android

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

复制代码

运行异常信息git

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#checkThreadgithub

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,参考ThreadLocal 源码分析

想想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,相比于 Looper HandlerMessageQueue 就显得相对复杂一些。由于内部用到了 JNI 编程。初始化、销毁和入队等事件都用到了 native 的方法。能够在 android_os_MessageQueue 查看其源码的定义。更多参考MessageQueue 的实例化

咱们接着看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。(注:摘自从源码去理解Handler)

而后最终调用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方法是个死循环,可是为何不会卡死主线程呢,参考

Android中为何主线程不会由于Looper.loop()里的死循环卡死?

Handler后传篇一: 为何Looper中的Loop()方法不能致使主线程卡死?

深刻理解 MessageQueue

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

image.png

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

再看下关键的Message msg = queue.next() 深刻分析参见MessageQueue中Message消息的执行以及 MessageQueue 的消息管理

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 里的应用很是普遍,好比: AsyncTaskHandlerThreadMessenger、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吗?

Looper Handler 不须要再一个线程中,默认的状况下会从ThreadLocal 中取当前线程对应的 Looper,但咱们能够经过显式地指定一个 Looper 的方式来建立 Handler. 好比,当咱们想要在子线程中发送消息到主线程中,那么咱们能够

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

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

  • Handler的post()方法

  • View的post()方法

  • Activity的runOnUiThread()方法

如何理解Handler的异步

参见Handler后传篇二: 该如何理解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 代码块去进行同步。

资料:Android 中的 Handler 的 Native 层研究

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中的消息机制

Android点将台:烽火狼烟[-Handler-]

Handler 都没搞懂,拿什么去跳槽啊?

Android 高级面试-1:Handler 相关

Android 消息机制:Handler、MessageQueue 和 Looper

Android 消息机制详解(Android P)

相关文章
相关标签/搜索