大家好,欢迎来到IT知识分享网。
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()。
到了这里,Looper与ThreadLocal建立起了关联。
MessageQueue 实例化
接着上面继续看下Looper的构造方法
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 复制代码
每当我们实例化一个 Looper 的时候会调用它的构造方法,并在其中实例化一个 MessageQueue,相比于 Looper 和 Handler,MessageQueue 就显得相对复杂一些。因为内部用到了 JNI 编程。初始化、销毁和入队等事件都用到了 native 的方法。
我们接着看Handle构造函数里的
mQueue = mLooper.mQueue
我们知道消息是存放在MessageQueue消息队列中的,而MessageQueue就是在上面Looper构造函数中new出来的,至此Handler通过Looper与MessageQueue也建立起了关联。
总结一下,创建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中又会封装一个消息队列。 这样一来,Handler,Looper,MessageQueue,Thread四个角色就关联了起来。 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),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
这个就很有意思了,这有什么作用呢?
我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
创建 Message 实例的最佳方式
由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。
方法有二:
- 通过 Message 的静态方法 Message.obtain(); 获取;
- 通过 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的异步
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 放进消息队列的操作也都会失败。
知识点汇总
由前文可得出一些知识点,汇总一下,方便记忆。
- Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理
- 在创建 Handler 之前一定需要先创建Looper
- Looper 有退出的功能,但是主线程的 Looper 不允许退出
- 异步线程的 Looper 需要自己调用 Looper.myLooper().quit(); 退出
- Runnable 被封装进了 Message,可以说是一个特殊的 Message
- Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成Looper所在的线程,并不是创建 Handler 的线程
- 使用内部类的方式使用Handler可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类
由于篇幅原因,上文一些知识点,有些没有详细深入的去讲,甚至省略了。想进一步深入学习的同学可以详见我整理编写的《Android Framework精编内核解析》,可以在评论区留言或私信我获取!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/47593.html