关于android:一篇文章扒掉桥梁Handler的底裤

3次阅读

共计 7527 个字符,预计需要花费 19 分钟才能阅读完成。


Android 跨过程要把握的是 Binder, 而同一过程中最重要的应该就是 Handler 音讯通信机制了。我这么说,大家不晓得是否认同,如果认同,还心愿能给一个关注哈。

什么是 Handler?

Handler 次要用于异步音讯的解决:当收回一个音讯之后,首先进入一个音讯队列,发送音讯的 [函数] 即刻返回,而另外一个局部在音讯队列中逐个将音讯取出,而后对音讯进行解决,也就是发送音讯和接管音讯不是同步的解决。这种机制通常用来解决绝对耗时比拟长的操作。

Handler 特点

  1. 传递 Message。用于承受子线程发送的数据, 并用此数据配合主线程更新 UI。

在 Android 中,对于 UI 的操作通常须要放在主线程中进行操作。如果在子线程中有对于 UI 的操作,那么就须要把数据音讯作为一个 Message 对象发送到音讯队列中,而后,由 Handler 中的 handlerMessage 办法解决传过来的数据信息,并操作 UI。当然,Handler 对象是在主线程中初始化的,因为它须要绑定在主线程的音讯队列中。

类 sendMessage(Message msg)办法实现发送音讯的操作。在初始化 Handler 对象时重写的 handleMessage 办法来接管 Message 并进行相干操作。

  1. 传递 Runnable 对象。用于通过 Handler 绑定的音讯队列,安顿不同操作的执行程序。

Handler 对象在进行初始化的时候,会默认的主动绑定音讯队列。利用类 post 办法,能够将 Runnable 对象发送到音讯队列中,依照队列的机制按程序执行不同的 Runnable 对象中的 run 办法。

Handler 怎么用?

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        testSendMessage();}

    public void testSendMessage() {Handler handler = new MyHandler(this);
         Message message = Message.obtain();
         message.obj = "test handler send message";
         handler.sendMessage(message);
    }
    
    // 注 1:为什么要用动态外部???static class MyHandler extends Handler {
        WeakReference<AppCompatActivity> activityWeakReference; // 注 2:为何要用弱援用???public MyHandler(AppCompatActivity activity) {activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {super.handleMessage(msg);
            Log.d(TAG, (String) msg.obj);
        }
    }
}

Handler 源码怎么读?

从应用形式的场景,咱们一步一步的探索外面是怎么实现的,还有下面的标注的两点,在前面我都会介绍的,各位客官听我缓缓道来。首先,看下 四大金刚 关系图,文字表述再多,不如一张图来的间接。


通过上图就能够简略看出 Handler、MessageQueue、Message、Looper 这四者是怎么样相互持有对方的,大略能够理解音讯的传递。

上面咱们先来一张时序图,看下音讯是怎么一步步发送进去的。


此刻,应该要开车了。后方高能!!!

  1. 进入的是 Handler.sendMessage 办法

    public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);
    }
  2. 接下来持续调用 Handler.sendMessageDelayed 办法

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}
     return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
  3. 接着走 Handler.sendMessageAtTime 办法,这外面就要用到 MessageQueue 对象了,此处阐明一下,这个 mQueue 是在哪里获取到的,是在 Handler 构造方法里。此处贴图,从图中能够看出mLooper=Looper.myLooper() mQueue=mLooper.mQueue Handler 中的 MessageQueue 是 Looper 中持有的 MessageQueue 对象。


注 1 为啥要用动态外部类 ---->如果咱们应用 Handler 类,没有用 static 关键字润饰的话,则会输入 Log: The following Handler class should be static or leaks might occur: 会提醒你可能会引起内存透露。因而在注 1 处我用了 static 润饰。

好,这里就说这么多,接着开车:

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);
}
  1. 接着时序图上的流程走,此时要进入到 MessageQueue.enqueueMessage 办法中,该办法就是将 msg 对象存入到 MessageQueue 队列中, 留神此处,将该 handler 对象赋值给了 msg.target, 这个前面会用到的,很要害。
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); //3,行将进入 MessageQueue.enqueueMessage 办法。}
  1. 接着来看 MessageQueue.enqueueMessage 办法, 该办法就是依照工夫的程序插入到 Message 这个链表构造的数据对象中去。
boolean enqueueMessage(Message msg, long when) {if (msg.target == null) { //4. 前面阐明,这个也就是四大金刚图里的 msg.target 所持有的 Handler 对象。throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ...
        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 {// 链表的插入操作,不太熟悉的能够看看数据结构。(此处是依据工夫来排序的)
            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); // 画重点,此处唤醒期待的 next 办法。}
    }
    return true;
}

此时,一条音讯就相当于入队了。MessageQueue 从名称来看是队列,实际上,应用的还是 Message.next 指针来进行操作的,也即是链表的操作。音讯的入队实现,前面将会介绍该音讯是怎么发送进来的。

  1. Loop.loop 办法,敲重点。省略了局部代码,只关注外围代码。这里用到了死循环,不停的获取 Message 对象,获取到之后间接调用 Message.target 变量所持有的 Handler 对象,而后调用 Handler.dispatchMessage 办法,这样就实现了音讯的散发。
public static void loop() {final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {Message msg = queue.next(); // might block   //7. 通过 MessageQueue.next()办法获取 Message 对象。...
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();}
}

7-8. MessageQueue.next() 办法获取 Message 对象。

Message next() {
    ...
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) { // 死循环
        if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);   // 5: 防止了阻塞的关键点,开释资源,处于期待。疑点:处于期待,必定须要一个货色来唤醒它。下面第 5 步剖析 enqueueMessage 的时候有行代码 if (needWake) {nativeWake(mPtr); // 画重点,此处唤醒期待的 next 办法。}。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) { //****** 此条件能够先不看,因为通过 Handler 发送的音讯 target 都会持有 Handler,该逻辑不会触发。音讯同步屏障的时候会优先触发该逻辑。// 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) { // 查找以后的 msg 对象。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;
            }
         ...
        nextPollTimeoutMillis = 0;
    }
}
  1. Handler.dispatchMessage 办法,此处有判断,如果在 Activity 中应用 view.post 办法调用的时候,就会走到 handleCallback 回调中。通过 sendMessagexxx 函数发送音讯的就会走到 handleMessage 回调中去。
/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);
    } else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
        }
        handleMessage(msg);
    }
}

该办法会会将 msg 对象发送到客户端定义 Handler 的中央,重写的 handleMessage 办法。至此,Handler 发送音讯的流程大抵介绍实现。

总结

Handler 发送音讯的时候,在 Handler.enqueueMessage 办法中,将该 Handler 对象增加到 Message 中的 target 属性中,这样就实现了 Message 持有 Handler 的操作,为最初 Message.target.dispatchMessage 做了保障。而后将该 Message 对象放入到 MessageQueue 中的 Message.next 中去,实现了音讯链表的增加;而这个 MessageQueue 是 Looper 中所持有的对象,这样就能够通过 Looper 类通过对 MessageQueue.next()—->Message.next()—>Message.target.dispatchMessage(msg)实现了音讯的散发。

知识点补充

  1. Looper 对象是怎么 new 进去的?

    上图看出是在应用程序过程的 ActivityThread 类中的 main() 函数中调用了 Looper.prepareMainLooper() 办法,就 new 进去了主线程中的 Looper.

    上图也看出,这个 Looper.prepareMainLooper()办法是零碎调用的,开发者不能再次调用了,否则会抛出异样。

    prepare 这个办法真正的 new Looper 了。接着来看看 Looper 的构造函数


此处创立了 MessageQueue, Handler 中的 MessageQueue 就是这块创立的。

  1. 为什么将 Looper 保留在 ThreadLocal 中?

ThreadLocal: 线程的变量正本,每个线程隔离. 我的了解就是,ThreadLocal 外部应用了以后线程为 Key, 须要存储的对象为 Value,通过字典保存起来的,这样客户端在获取的时候,以后线程就只会获取一份保留的 Value. 回到 Looper 中,就能够晓得一个线程里按理说就会只有一个 Looper。

  1. Message 为什么举荐应用 obtain() 形式获取 Message 对象,而不举荐应用 new Message()?

这里波及到池的技术的利用: Message 中保护了一个音讯池,音讯应用完就会回收。缩小对象创立和销毁的开销;java 当中的线程池也是用到了该思维。

  1. 同步屏障:

同步屏障机制的作用,是让这个绘制音讯得以越过其余的音讯,优先被执行。零碎中 UI 绘制会应用到同步屏障,开发中根本用不到。外围代码:先设置一个 target=null 的音讯,插入到音讯链表的头部。

而后在 MessageQueue.next 中 优先查找同步屏障中的音讯 asyncHronous 设置为 true 的异步音讯。

  1. Handler 为什么会导致内存透露以及解决方案?

Handler 导致内存透露个别产生在发送提早音讯的时候,当 Activity 敞开之后,提早音讯还没收回,那么主线程中的 MessageQueue 就会持有这个音讯的援用,而这个音讯是持有 Handler 的援用,而 handler 作为匿名外部类持有了 Activity 的援用,所以就有了以下的一条援用链。
解决:1. 应用动态外部类,如果要调用 Activity 中的办法,就能够在动态外部类中设置一个
WeakReference activityWeakReference; 援用。

2. 在 Activity 销毁的时候,即 onDestory()办法中调用 handler.removeCallbacks,移除 runnable。

结尾

OK,本次的 Android 进阶技术之 Handler 到此就全副写完了,心愿喜爱的敌人不要悭吝你的赞,你的评论,点赞,珍藏就是对我最大的反对,记得关注我哦,咱们文章每日都会更新,感激大家的观看。

正文完
 0