Andriod-消息机制Handler


学而不思则罔,思而不学则殆

参考

大牛的博客,非常全面

Handler问题三连

1.Handler是什么

答:Android Framework 架构中的一个 基础组件,用于子线程与主线程间的通讯,实现了一种非堵塞的消息传递机制

2.Handler有什么用

答:把子线程中的 UI更新信息传递 给主线程(UI线程),以此完成UI更新操作。

个人理解:子线程通知主线程更新UI;或者主线程有什么耗时操作扔到子线程

3.为什么要用Handler,不用行不行

答:不行,Handler是android在设计之初就封装的 一套消息创建、传递、处理机制

Android要求:

在主线程(UI)线程中更新UI

是要求,建议,不是规定,你不听,硬要:

在子线程中更新UI,也是可以的!!!

    fun updateUI(view: View) {
        Thread(Runnable {
            Looper.prepare()
            val dialog = AlertDialog.Builder(this@BActivity)
                    .apply {
                        setTitle("子线程更新UI")
                        setIcon(R.drawable.ic_launcher_background)
                    }
                    .create()
            dialog.show()
            Looper.loop()
        }).start()
    }

却可以弹窗Dialog
在这里插入图片描述
当我们用这个方法尝试在子线程更新UI的时候:
测试一:

    fun updateUI2(view: View) {
        Thread(Runnable {
            //dialog.show()
            textView.text = "测试子线程更新UI"
        }).start()
    }

会报如下错误:

2020-08-09 09:11:16.493 15802-15863/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.handlerstudy, PID: 15802
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

异常翻译:只有创建这个view的线程才能操作这个view;
引起原因:在子线程中更新了主线程创建的UI;
也就是说:子线程更新UI也行,但是只能更新自己创建的View;
换句话说:Android的UI更新(GUI)被设计成了单线程;

测试二:
子线程没有执行 Looper.prepare()的方法,再来弹出Dialog,却报错了。

    fun updateUI3(view: View) {
        Thread(Runnable {
            val dialog = AlertDialog.Builder(this@BActivity)
                    .apply {
                        setTitle("子线程更新UI")
                        setIcon(R.drawable.ic_launcher_background)
                    }
                    .create()
            dialog.show()
        }).start()
    }

错误log:

2020-08-09 09:13:40.224 16004-16036/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.handlerstudy, PID: 16004
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:227)
        at android.os.Handler.<init>(Handler.java:129)
        at android.app.Dialog.<init>(Dialog.java:133)
        at android.app.AlertDialog.<init>(AlertDialog.java:204)
        at android.app.AlertDialog$Builder.create(AlertDialog.java:1105)
        at com.example.handlerstudy.BActivity$updateUI3$1.run(BActivity.kt:60)
        at java.lang.Thread.run(Thread.java:923)

你可能会问,为啥不设计成多线程?

答:多个线程同时对同一个UI控件进行更新,容易发生不可控的错误!

那么怎么解决这种线程安全问题?

答:最简单的 加锁,不是加一个,是每层都要加锁(用户代码→GUI顶层→GUI底层…)这样也意味着更多的 耗时,UI更新效率降低;如果每层共用同一把锁的话,其实就是单线程。

所以,结论是:

Android没有采用「线程锁」,而是采用「单线程消息队列机制」,实现了一个「伪锁」

再来看一个网上很常见的例子:

class BActivity : BaseActivity() {
    private lateinit var textView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)
        textView = findViewById<TextView>(R.id.title)
        textView.text = "测试子线程更新UI"
        Thread(Runnable {
            textView.text = "测试子线程更新UI ${Thread.currentThread().name}"
        }).start()
    }
    ...
}

在OnCreate的时候子线程更新UI,却没有报错:
在这里插入图片描述
这是要搞我吗?但如果在子线程中加线程休眠模拟耗时操作的话:

class BActivity : BaseActivity() {
    private lateinit var textView: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_b)
        textView = findViewById<TextView>(R.id.title)
        textView.text = "测试子线程更新UI"
        Thread(Runnable {
            Thread.sleep(100)
            textView.text = "测试子线程更新UI ${Thread.currentThread().name}"
        }).start()
    }
    ...
}

程序就崩溃了,崩溃日志如下:

2020-08-09 10:40:01.813 17155-17217/com.example.handlerstudy E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.example.handlerstudy, PID: 17155
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8743)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1597)
        at android.view.View.requestLayout(View.java:25372)
        at android.view.View.requestLayout(View.java:25372)
        at android.view.View.requestLayout(View.java:25372)
        at android.view.View.requestLayout(View.java:25372)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
        at android.view.View.requestLayout(View.java:25372)
        at android.widget.TextView.checkForRelayout(TextView.java:9737)
        at android.widget.TextView.setText(TextView.java:6311)
        at android.widget.TextView.setText(TextView.java:6139)
        at android.widget.TextView.setText(TextView.java:6091)
        at com.example.handlerstudy.BActivity$onCreate$1.run(BActivity.kt:19)
        at java.lang.Thread.run(Thread.java:923)

这是要搞我啊?
那为什么添加延时过后就崩溃了呢?
那我们先打印日志看看:
不加延时:

2020-08-09 10:47:45.109 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onCreate
2020-08-09 10:47:45.110 17582-17641/com.example.handlerstudy D/zhangyu.BActivity: 子线程更新UI
2020-08-09 10:47:45.115 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onStart
2020-08-09 10:47:45.116 17582-17582/com.example.handlerstudy D/zhangyu.BActivity: onResume

添加延时:

2020-08-09 10:48:17.611 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onCreate
2020-08-09 10:48:17.620 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onStart
2020-08-09 10:48:17.620 17687-17687/com.example.handlerstudy D/zhangyu.BActivity: onResume
2020-08-09 10:48:17.713 17687-17745/com.example.handlerstudy D/zhangyu.BActivity: 子线程更新UI

前面说了 Android的UI更新被设计成单线程,这里妥妥滴会报错,但却发生在延迟执行后?限于篇幅,这里就不去跟源码了,直接说原因:

ViewRootImp 在 onCreate() 时还没创建;在 onResume()时,ActivityThread 的 handleResumeActivity() 执行后才创建;
调用 requestLayout(),走到 checkThread() 时就报错了。

Handler怎么用

1.sendMessage() + handleMessage()

代码示例如下:

在这里插入图片描述
其中黄色部分与如下警告;

This Handler class should be static or leaks might occur (anonymous android.os.Handler)

Handler不是静态类可能引起「内存泄露」,原因以及正确写法等下再讲。
另外,建议调用 Message.obtain() 函数来获取一个Message实例,为啥?点进源码:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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池是否为空
① 不为空,取一枚Message对象,正在使用标记置为0,池容量-1,返回;
② 为空,新建一个Message对象,返回;

该Message 是一个链表结构,每次获取对象的时候是从头部获取。
然后问题来了,Message信息什么时候加到池中?

答:当Message 被Looper分发完后,会调用 recycleUnchecked()函数,回收没有在使用的Message对象。

源码如下:

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        //清除属性值和引用-预防泄露
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        //加入到头部,头插法
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool; //当前节点的下一个节点指向头结点
                sPool = this; //头结点指向当前节点
                sPoolSize++;
            }
        }
    }

标志设置为FLAG_IN_USE,表示正在使用,相关属性重置,加锁,判断消息池是否满,
未满,「单链表头插法」把消息插入到表头。(获取和插入都发生在表头,像不像 栈~)

2.post(runnable)

    fun ues2(view: View) {
        Thread {
//            mHandler.post(Runnable {
//                textView.text = "用法2"
//            })
            mHandler.post {
                textView.text = "用法2" //设置UI
            }
        }.start()
    }

实际调用的是sendMessageDelayed方法

    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    //获取Message对象
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

3.附:其他两个种在子线程中更新UI的方法

runOnUiThread

    fun ues4(view: View) {
        Thread {
            runOnUiThread {
                textView.text = "通过runOnUiThread在子线程更新UI"
            }
        }.start()
    }

View.post()和View.postDelayed()

    fun ues3(view: View) {
        Thread {
            textView.post {
                textView.text = "通过View.post在子线程更新UI"
            }

            textView.postDelayed({
                textView.text = "通过View.postDelayed在子线程更新UI"
            }, 1000)
        }.start()
    }

Handler底层原理解析

1.涉及到的几个类

在这里插入图片描述

UI线程的Looper

我们在主线程可以直接使用Handler,是因为主线程在创建的时候已经帮我们初始化了Looper,所以我们可以直接使用。请查看源码,移除干扰项:

    //ActivityThread 
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper(); //创建主线程Looper
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        ...
        Looper.loop(); //开启消息轮询,死循环,正常不会执行后续代码
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

定位到:Looper → prepareMainLooper函数

    public static void prepareMainLooper() {
        prepare(false); //新建Looper,设置Looper不可关闭
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

定位到:Looper → prepare函数

    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)); //新建Looper对象,保存到THreadLocal中
    }

定位到:Looper → Looper构造函数

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

定位到:MessageQueue→ quit构造函数:
另外这里的 mQuitAllowed 变量,直译「退出允许」,具体作用是?跟下 MessageQueue:

    void quit(boolean safe) {
        if (!mQuitAllowed) { //MainLooper不允许退出
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

3.消息队列的运行

前戏过后,创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。
定位到:Looper → loop函数
别的不多看,先看一下主逻辑

   public static void loop() {
        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 (;;) {
            Message msg = queue.next(); // might block
            ...处理消息...
        }
    }

loop方法是一个无限循环,开启消息轮询逻辑

4.HandlerThread

HandlerThread = 继承Thread + 封装Looper

    //HandlerThread.java
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();   //创建Looper
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared(); //可以着这里初始化Hander
        Looper.loop(); //轮询
        mTid = -1;
    }

继承Thread,getLooper()加锁死循环wait()堵塞线程;
run()加锁等待Looper对象创建成功,notifyAll()唤醒线程;
唤醒后,getLooper返回由run()中生成的Looper对象;

虽然HanderThread再开发中可以方便我们的做一些线程切换的工作。但是如果HandlerThread被滥用的话,会导致消息处理不过来,让用户感觉点击没有效果。所以实际开发中需要对子线程处理的消息进行分级,创建不同的HanderThread处理不同优先级的消息。跟UI相关的耗时操作或者数据存储的优先级比一般纯后台的,不及时的消息(比如:埋点上报等)高。不要让低优先级的消息阻塞高优先级的消息。

5.当我们用Handler发送一个消息发生了什么?

刚讲到 ActivityThread 在 main函数中调用 Looper.prepareMainLooper
完成主线程 Looper初始化,然后调用 Looper.loop() 开启消息循环 等待接收消息。
上面说了,Handler可以通过sendMessage()和 post() 发送消息,
上面也说了,源码中,这两个最后调用的其实都是 sendMessageDelayed()完成的:

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

第二个参数:当前系统时间(从手机开机时开始统计,更改手机时间不会影响这个时间)+延时时间,这个会影响「调度顺序」,跟 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);
    }
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this; //target 保存Hander,所以需要注意内部类Hander造成的内存泄漏
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) { //设置屏障消息
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这里的 mAsynchronous 是 异步消息的标志,如果Handler构造方法不传入这个参数,默认false:
这里涉及到了一个「同步屏障」的东西,等等再讲,跟:MessageQueue -> 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; //这是msg的执行时间
            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;
    }

6.Looper是怎么拣队列的消息的?

MessageQueue里有Message了,接着就该由Looper来分拣了,定位到:Looper → loop函数

   public static void loop() {
        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 (;;) {
            Message msg = queue.next(); // 取出队列中的消息
            ...处理消息...
        }
    }

queue.next() 从队列拿出消息,定位到:MessageQueue -> next函数:

在这里插入图片描述
这里的关键其实就是:nextPollTimeoutMillis,决定了堵塞与否,以及堵塞的时间,三种情况:

等于0时,不堵塞,立即返回,Looper第一次处理消息,有一个消息处理完;
大于0时,最长堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);
等于-1时,无消息时,会一直堵塞;

Tips:此处用到了Linux的pipe/epoll机制:没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程;

7.分发给Handler的消息是怎么处理的?

通过MessageQueue的queue.next()拣出消息后,调用msg.target.dispatchMessage(msg)
把消息分发给对应的Handler,跟到:Handler -> dispatchMessage

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) { //有callback 说明是post方法发送的Runnable,直接run()
            handleCallback(msg); 
        } else {
            if (mCallback != null) { //Handerl初始化传入的Callback
                if (mCallback.handleMessage(msg)) {//返回true,拦截handleMessage
                    return;
                }
            }
            handleMessage(msg); //处理事件
        }
    }

8.IdleHandler是什么?

在 MessageQueue 类中有一个 static 的接口 IdleHanlder

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    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();
    }

翻译下注释:当线程将要进入堵塞,以等待更多消息时,会回调这个接口;
简单点说:当MessageQueue中无可处理的Message时回调;
作用:UI线程处理完所有View事务后,回调一些额外的操作,且不会堵塞主进程;

接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里,
返回值是true的话,执行完此方法后还会保留这个IdleHandler,否则删除。
使用代码如下;

            mHandler.looper.queue.addIdleHandler {
                Log.d("zhangyuCActivity", "空闲消息1")
                false
            }

            mHandler.looper.queue.addIdleHandler {
                Log.d("zhangyuCActivity", "空闲消息2")
                true
            }

            mHandler.looper.queue.addIdleHandler {
                Log.d("zhangyuCActivity", "空闲消息3")
                false
            }
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息1
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2
2020-08-09 16:19:46.603 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息3
2020-08-09 16:19:46.624 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2
2020-08-09 16:19:46.651 21174-21174/com.example.handlerstudy D/zhangyuCActivity: 空闲消息2

看下源码,了解下具体的原理:MessageQueue,定义了一个IdleHandler的列表和数组

在这里插入图片描述
定义了添加和删除IdleHandler的函数:

在这里插入图片描述
在 next() 函数中用到了 mIdleHandlers 列表:
在这里插入图片描述

IdleHandler在Android源码中的应用

在ActivityThread中有三个实现了IdleHandler的内部类

    //ActivityThread.java
    private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
        ...
        }
    }

    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
        ...
        }
    }

    final class PurgeIdler implements MessageQueue.IdleHandler {
        @Override
        public boolean queueIdle() {
        ...
        }
    }

    @UnsupportedAppUsage
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler); //添加GC空闲消息
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void unscheduleGcIdler() {
        if (mGcIdlerScheduled) {
            mGcIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mGcIdler); //移除GC空闲消息
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

    void schedulePurgeIdler() {
        if (!mPurgeIdlerScheduled) {
            mPurgeIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mPurgeIdler);
        }
        mH.removeMessages(H.PURGE_RESOURCES);
    }

    void unschedulePurgeIdler() {
        if (mPurgeIdlerScheduled) {
            mPurgeIdlerScheduled = false;
            Looper.myQueue().removeIdleHandler(mPurgeIdler);
        }
        mH.removeMessages(H.PURGE_RESOURCES);
    }

这些消息会在主线程没有事件处理的时候被处理。还有一些第三方框架在使用比如:LeakCanary,Glide

一些其他问题

1.Looper在主线程中死循环,为啥不会ANR?

答:上面说了,Looper通过queue.next()获取消息队列消息,当队列为空,会堵塞,此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

你可能会问:主线程都堵住了,怎么响应用户操作和回调Activity声明周期相关的方法?

答:application启动时,可不止一个main线程,还有其他两个Binder线程:ApplicationThread 和 ActivityManagerProxy,用来和系统进程进行通信操作,接收系统进程发送的通知。

在这里插入图片描述

当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;
它通过Handler机制,往 ActivityThread 的 MessageQueue 中插入消息,唤醒了主线程;
queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;PS:ActivityThread 中的内部类H中有具体实现

死循环不会ANR,但是 dispatchMessage 中又可能会ANR哦!如果你在此执行一些耗时操作,
导致这个消息一直没处理完,后面又接收到了很多消息,堆积太多,就会引起ANR异常!!!

2.Handler泄露的原因及正确写法

在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC;
比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。而且Message的target又持有handler,Message在队列中

而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。

代码示例如下:

    private static class MyHandler extends Handler {
        private final WeakReference<Activity> activityWeakReference;

        public MyHandler(Activity activity) {
            this.activityWeakReference = new WeakReference<>(activity);
        }

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

同步屏障机制

通过上面的学习,我们知道用Handler发送的Message后,MessageQueue的enqueueMessage()按照 时间戳升序 将消息插入到队列中,而Looper则按照顺序,每次取出一枚Message进行分发,一个处理完到下一个。这时候,问题来了:有一个紧急的Message需要优先处理怎么破?你可能或说直接sendMessage()不就可以了,不用等待立马执行,看上去说得过去,不过可能有这样一个情况:

一个Message分发给Handler后,执行了耗时操作,后面一堆本该到点执行的Message在那里等着,这个时候你sendMessage(),还是得排在这堆Message后,等他们执行完,再到你!

对吧?Handler中加入了「同步屏障」这种机制,来实现「异步消息优先执行」的功能。

添加一个异步消息的方法很简单:

1、Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的;
2、创建Message对象时,直接调用setAsynchronous(true)

一般情况下:同步消息和异步消息没太大差别,但仅限于开启同步屏障之前。可以通过 MessageQueue 的 postSyncBarrier 函数来开启同步屏障:

在这里插入图片描述
行吧,这一步简单的说就是:往消息队列合适的位置插入了同步屏障类型的Message (target属性为null)
接着,在 MessageQueue 执行到 next() 函数时:

在这里插入图片描述
遇到target为null的Message,说明是同步屏障,循环遍历找出一条异步消息,然后处理。在同步屏障没移除前,只会处理异步消息,处理完所有的异步消息后,就会处于堵塞。如果想恢复处理同步消息,需要调用 removeSyncBarrier() 移除同步屏障:
在这里插入图片描述

在API 28的版本中,postSyncBarrier()已被标注hide,但依旧可在系统源码中找到相关应用,比如:
为了更快地响应UI刷新事件,在ViewRootImpl的scheduleTraversals函数中就用到了同步屏障:

    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置屏障消息
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//取消屏障消息
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

自测试屏障消息1

通过反射放屏障消息:

    private void postSyncBarrier() {
        try {
            Method method = messageQueue.getClass().getDeclaredMethod("postSyncBarrier");
            Log.d("zhangyu", "method:" + method);
            method.setAccessible(true);
            token = (int) method.invoke(messageQueue);
            Log.d("zhangyu", "token:" + token);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //5秒后移除屏障消息
                    removeSyncBarrier();
                }
            }
            ).start();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    private void removeSyncBarrier() {
        try {
            Method method = messageQueue.getClass().getDeclaredMethod("removeSyncBarrier", int.class);
            Log.d("zhangyu", "method:" + method);
            method.setAccessible(true);
            method.invoke(messageQueue, token);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

发送屏障消息过后,所有的主线程UI消息都不能执行,处于堵塞状态,只有取消屏障效果过后才能继续主线程消息:

    public void send111(View view) {
        Log.d("zhangyu", "send111");
        postSyncBarrier(); //发送屏障消息
    }

    public void remove111(View view) {
        Log.d("zhangyu", "UI消息一");
    }

    public void show(View view) {
        Log.d("zhangyu", "UI消息二");
    }
}
2020-08-09 17:14:56.485 23196-23196/com.example.handlerstudy D/zhangyu: send111
2020-08-09 17:14:56.486 23196-23196/com.example.handlerstudy D/zhangyu: method:public int android.os.MessageQueue.postSyncBarrier()
2020-08-09 17:14:56.486 23196-23196/com.example.handlerstudy D/zhangyu: token:17
2020-08-09 17:15:01.497 23196-23254/com.example.handlerstudy D/zhangyu: method:public void android.os.MessageQueue.removeSyncBarrier(int)
2020-08-09 17:15:01.498 23196-23196/com.example.handlerstudy D/zhangyu: UI消息一
2020-08-09 17:15:01.502 23196-23196/com.example.handlerstudy D/zhangyu: UI消息二

发送屏障消息过后,有发送消息一和消息二,但是没有执行,而是等屏障消息移除后才被执行。

屏障消息系统应用

在View绘制流程中有用到屏障消息,View绘制之前设置屏障消息,绘制结束后在取消屏障消息,在这中间UI主线程Handler收到的异步消息被阻塞,等待屏障被移除。

    //ViewRootImpl.java
    int mTraversalBarrier;
    
    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //设置屏障消息
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //发送屏障消息
            ...
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); //取消屏障消息
            mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal(); //View绘制流程三大步骤
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

   void doTraversal() {
            ...
            performTraversals();
            ...
    }
    private void performTraversals() {
            ...
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //测量
            ...
            performLayout(lp, mWidth, mHeight);//布局
            ...
            performDraw();//绘制
            ...
    }

PS:这里存在一个问题,当多线程调用scheduleTraversals方法时,会生成多个屏障消息(实际发生概率很小),但是这儿只保存了最新的屏障消息token,后面删除的时候也只删除了最新的屏障消息。导致主线程事件一直被阻塞,不能被响应,造成ANR.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: default-servlet-handler是Spring MVC框架中的一个处理器,用于处理静态资源请求。当请求的URL没有匹配到任何一个Controller时,default-servlet-handler会将请求转发给Servlet容器中的默认Servlet来处理,例如Tomcat中的DefaultServlet。这样可以避免在Controller中处理静态资源请求,提高系统性能。 ### 回答2: default-servlet-handler是Spring MVC框架用于处理静态资源的默认处理器,它是一个处理器映射器和处理器适配器的组合。在Spring MVC中,当发送来自客户端的静态文件请求时,会使用该default-servlet-handler默认处理器来处理这些请求。 Spring MVC的默认处理器使用了Servlet容器的默认Servlet来处理静态资源的请求。这些静态资源可以是图片、CSS文件、JavaScript文件或HTML文件等。当请求的资源在内存中不存在时,default-servlet-handler会把请求转发到Servlet容器里的默认Servlet,由其来处理,并把处理结果返回给客户端。这样做可以减轻Spring MVC本身的负担,提高整个应用程序的性能。 使用default-servlet-handler,我们可以不必为所有静态资源创建控制器,以提高应用程序启动和运行速度。这就是说,当我们的控制器无法处理一个请求时,Spring MVC会检查是否有默认的Servlet来处理这个请求,并把这个请求转给该Servlet。所以,我们可以通过在web.xml文件中注册Servlet来实现对不同路径的映射和处理。 另外需要注意的是,default-servlet-handler在XML配置文件中的配置代码如下: ```xml <mvc:default-servlet-handler/> ``` 在注解配置中的代码如下: ```java @Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } } ``` 以上就是default-servlet-handler的基本使用和配置方法。如果在Spring MVC框架中需要处理静态资源并提高应用程序的性能,这个处理器的使用是非常值得推荐的。 ### 回答3: default-servlet-handler是Spring框架中的一个组件,它是用来处理静态资源(如图片、CSS文件、JavaScript文件等)的一个处理器。在Web应用程序中,通常会有很多静态资源需要提供给客户端访问,如何高效地管理这些资源并提供访问? 在传统的Web应用程序中,通常会将静态资源放在Web服务器的某个目录下,如Tomcat中的【webapps】目录。Web服务器收到客户端请求后,根据URL去服务器中查找对应的文件并返回给客户端。但是,这种方式存在一个问题:每次静态资源的修改都需要重启Web服务器,影响Web应用程序的正常运行。 而Spring框架中提供了default-servlet-handler能够解决这个问题,它会去解析Web应用程序中的资源和请求路径,并将请求转发到Web服务器的默认Servlet来处理。这样,Web服务器就不需要重启就可实时地提供静态资源。 default-servlet-handler的原理是将对静态资源的请求交给Web服务器中的默认Servlet来处理,而不是交给DispatcherServlet来处理。需要注意的是,default-servlet-handler只是用来处理静态资源,如果请求的URL路径与某些动态请求有冲突,则会由DispatcherServlet来处理动态请求。 在配置default-servlet-handler时,需要为该处理器指定一个URL模式,用于匹配静态资源的请求路径。例如: <mvc:default-servlet-handler /> 这样配置就会将所有静态资源请求交给Web服务器默认的Servlet来处理。 综上所述,default-servlet-handler是Spring框架中用于处理静态资源的重要组件,它能够高效地提供静态资源访问的服务,降低Web服务器重启频率,提高Web应用程序的稳定性和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值