关于handler,看这篇就够了

4,044 阅读12分钟

带着问题看这篇文章,你会有更多的收获。

  • 为什么需要使用handler?什么场景下使用? 不用行不行?
  • 主线程真的不能更新UI吗?
  • Handler是如何使用的?
  • Headler使用过程中会出现那些问题?
  • IdleHandler是什么?什么场景下使用?
  • 你听过同步消息屏障吗?有什么用?
  • HandlerThread是什么?他的实现原理是什么?有什么用?
  • IntentService是什么?有什么用?

一、Handler是什么,有什么用?

答:Android中的消息通信机制,用于 子线程与主线程间的通讯,实现了一种 非堵塞的消息传递机制,把子线程中的 UI更新信息 发送给主线程(UI线程),以此完成UI更新操作。Android中主线程不能进行耗时操作,所有的耗时操作都需要在子线程中进行,耗时操作完成后如果需要更新ui,就需要把信息传给主线程更新ui。

二、子线程真的不能更新UI吗?

我们举个例子看下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setMessage("演示在子线程更新ui");
                AlertDialog alertDialog = builder.create();
                alertDialog.show();
                Looper.loop();
            }
        }).start();
    }

运行后:

XP5HRJ}4KH26H@9$0ZH~8)G.png

可以看到,对话框正常弹出,而我们平时在 子线程中更新UI 的报错信息是这样的:

报错原因提示:只有创建这个View的线程才能更新这个view,因为我们的view基本上都是在mainThread中即默认线程中初始化的,所以此处会报错。所以子线程更新UI也行,但是只能更新子线程创建的View。

那么Android机制这么设定不让子线程具备更新主线程的能力呢?这是因为多个线程同时对同一个UI控件进行更新,容易发生不可控的错误,即线程安全问题。那么怎么解决这种线程安全问题?最简单的处理方式——加锁,不是加一个,是每层都要加锁(用户代码→GUI顶层→GUI底层...)但这样也意味着更多的 耗时,从而导致UI更新效率降低,界面卡顿等。

我们再看另一个在子线程更新UI的场景

public class MainActivity extends AppCompatActivity {
private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv);
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText(Thread.currentThread().getName());
            }
        }).start();

    }
}

上面这段代码 直接在子线程中更新了UI,却没有报错:

TU{FNT{5V2LI(PXB0)0}X.png

上一步我们得到的结论是只有创建view的线程才具备修改这个view的权利,而此处的现象与我们得到的结论似乎不相符,那我们看下系统源码看下什么原因导致的。

  1. ViewRootImponCreate() 调用时还没创建,在onResume()ActivityThread.handleResumeActivity()*执行后才创建;
  2. 调用 View.requestLayout() ,最终调到 ViewRootImpl.requestLayout() ,走到**checkThread()**报错; 所以我们猜想可能是由于方法执行顺序导致子线程可以修改UI。我们来验证下:
public class MainActivity extends AppCompatActivity {
private TextView textView;
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv);
        Log.d(TAG, "onCreate: ");
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "run: 在子线程修改UI");
                textView.setText(Thread.currentThread().getName());
            }
        }).start();

    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }
}

运行结果如下:

2022-03-13 11:31:03.370 5964-5964/com.firstcode.myapplication D/MainActivity: onCreate: 
2022-03-13 11:31:03.373 5964-5998/com.firstcode.myapplication D/MainActivity: run: 在子线程修改UI
2022-03-13 11:31:03.435 5964-5964/com.firstcode.myapplication D/MainActivity: onResume: 

在子线程代码块中加上休眠模拟耗时操作:

new Thread(new Runnable() {
    @Override
    public void run() {
       
        try {
            Thread.sleep(300);
             Log.d(TAG, "run: 在子线程修改UI");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        textView.setText(Thread.currentThread().getName());
    }
}).start();

程序崩溃,报错日志如下:

  android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at android.view.View.requestLayout(View.java:23093)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3146)

执行日志如下:

2022-03-13 11:39:09.674 6306-6306/com.firstcode.myapplication D/MainActivity: onCreate: 
2022-03-13 11:39:09.684 6306-6306/com.firstcode.myapplication D/MainActivity: onResume: 
2022-03-13 11:39:09.979 6306-6338/com.firstcode.myapplication D/MainActivity: run: 在子线程修改UI

所以这里的子线程可以更新UI是因为方法执行顺序导致,先更新了UI再执行检查逻辑,取了个巧,而并不是真的可以在子线程中更新UI。

三、Handler是如何使用的?

3.1在默认线程(主线程中使用)

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

//发送消息
Message message = Message.obtain();

handler.sendMessage(message);
//或者
handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});

使用方式比较简单:

  1. 创建Handler对象;
  2. 发送消息就OK了

3.2 俩条线程间相互发送消息(子线程向主线程发送消息)

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private static final String TAG = "MainActivity";
    android.os.Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //这里接受并处理消息
            Log.d(TAG, "handleMessage: " + msg.what);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    //子线程向主线程发送
                    Message message1 = Message.obtain();
                    message1.what = 1;
                    handler.sendMessage(message1);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }.start();
        
    }
    
}
2022-03-13 12:07:29.511 6843-6843/com.firstcode.myapplication D/MainActivity: handleMessage: 1

3.3 俩条线程间相互发送消息(主线程向子线程发送消息)

其实俩条线程间相互发送消息,方式都是一样的,只要记住一点就好了:拿到对方的handler发送消息,比如A要向B发送消息,在A中拿到B的handler,调用handler.sendMessage()就好了。


public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private static final String TAG = "MainActivity";
    android.os.Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        //这里接受并处理消息
                        Log.d(TAG, "handleMessage: " + msg.what);
                    }
                };
                Looper.loop();
            }
        }.start();

        try {
            Thread.sleep(200);//为了让handler先初始化完,否则会报空指针异常
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Message message1 = Message.obtain();
        message1.what = 2;
        handler.sendMessage(message1);

    }

}

2022-03-13 12:13:48.756 7305-7337/com.firstcode.myapplication D/MainActivity: handleMessage: 2

向子线程中发送信息稍微有点不同的是,需要先调用Looper的相关方法,至于为什么,看完原理就知道了。

3.4 为何建议调用 Message.obtain() 函数来获取一个Message实例

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();
    }

从源码,可以看到obtain()的逻辑:加锁判断Message池是否为空

  • ① 不为空,取一枚Message对象,正在使用标记置为0,池容量-1,返回此对象;
  • ② 为空,新建一个Message对象返回;

此处复用Message,可避免**避免重复创建实例对象 ,达到节约内存的目的,而且不难看出Message实际上是无头结点的单链表**。

上述获取消息池的逻辑:

定位到下述代码,还可以知道:池的容量为50

然后问题来了,Message信息什么时候加到池中?

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

public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

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 = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。 recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)

四、Handler底层原理解析

终于来到稍微有点技术含量的环节,在观摩源码了解原理前,先说下几个涉及到的类。

4.1 涉及到的几个类

  • handler :1.负责发送消息到MessageQueue;2.对消息进行处理
  • Message :消息的载体
  • Looper :内部维护MessageQueue,以轮训的方式不断从MQ中获取消息,发送给handler处理
  • MessageQueue :消息队列,在Looper中,负责存储消息
  • ThreadLocal : 一个线程有一个ThreadLocal,存储Looper,绑定线程和Looper,使得每一个线程的消息队列都是线程安全的

4.2 整体流程

image.png

4.3 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));
}

Looper会借助 ThreadLocal 来实现与当前线程的绑定功能

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

在这个流程里,做了几件事

  1. 创建「Looper」;
  2. 创建完Looper后,在Looper的构造方法中创建了用于管理消息的「MessageQueue」对象
  3. 通过ThreadLocal 来实现与当前线程的绑定

另外值得注意的是,在Looper中 prepareMainLooper方法中,执行的是prepare(false)方法,而我们自己创建的线程中执行prepare()方法时,默认执行prepare(true),这有什么区别呢?


public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

跟踪MessageQueue的源码:

void quit(boolean safe) {
    if (!mQuitAllowed) { //用户无法主动停止主线程消息队列
        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);
    }
}

可以看到,如果是子线程我们可以主动停止消息队列,但是主线程我们是没有这个权限的。

4.4 Looper.loop()

经过上一步,创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。

public static void loop() {
    final Looper me = myLooper();//获取当前线程Looper实例
    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

    msg.target.dispatchMessage(msg);//将消息分发给handler处理
    }

主要做了几件事:

  • 获取当前线程Looper实例;
  • 获取消息队列
  • 开启一个死循环,不断从队列中获取消息
  • 将消息分发给handler处理

myLooper() 函数:

@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

ThreadLocal → 线程局部变量 → JDK提供的用于解决线程安全的工具类。 作用为每个线程提供一个独立的变量副本 → 以解决并发访问的冲突问题

ThreadLocal.class:

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//从当前线程获取ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}


ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

可以看到:每个Thread内部都维护了一个ThreadLocalMap,这个map的key是ThreadLocal ,value是set的那个值。get的时候,线程都是从自己的变量中取值,所以不存在线程安全问题。

总结下:

  1. Looper.prepare() 时会将当前的looper保存到ThreadLocal中。
  2. 每一个线程都有一个ThreadLocalMap,key是当前线程,value是上面保存的Looper。所以通过looper.mylooper会获取当当前线程的looper对象,并且是唯一的

主线程和子线程的Looper对象实例相互隔离的!!!

另外,线程为key也保证了每个线程只有一个Looper,而创建Looper对象时又会创建MessageQueue对象,所以间接保证每个线程最多只能有一个MessageQueue。

知道这个以后,有个问题就解惑了:

为什么子线程中不能直接 new Handler(),而主线程可以?

因为主线程与子线程不共享同一个Looper实例,主线程的Looper在启动时就通过 prepareMainLooper() 完成了初始化,而子线程还需要调用 Looper.prepare() 和 Looper.loop()开启轮询,否则会报错,不信,可以试试:

直接就奔溃了~

加上试试?

没有报错,程序正常运行。

对了,既然说Handler用于子线程和主线程通信,试试在主线程中给子线程的Handler发送信息,修改一波代码:

运行,直接报错:

原因:多线程并发的问题,当主线程执行到sendEnptyMessage时,子线程的Handler还没有创建。 一个简单的解决方法是:主线程延时给子线程发消息,修改后的代码示例如下:

运行结果如下:

可以,不过其实Android已经给我们封装好了一个轻量级的异步类 HandlerThread

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

Handler可以通过sendMessage()和 post() 发送消息,这两个最后调用的其实都是 sendMessageDelayed()完成的:

Handler.java:

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

第二个参数:当前系统时间+延时时间,这个会影响「调度顺序

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);
}

获取当前线程Looper中的MessageQueue队列,判空,空打印异常,否则返回 enqueueMessage()

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);
}

这里的 mAsynchronous异步消息的标志,如果Handler构造方法不传入这个参数,默认false: 这里涉及到了一个「同步屏障」的东西,等等再讲,跟:MessageQueue -> enqueueMessage

MessageQueue.java

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) {
            // 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;
}

如果你了解数据结构中的单链表的话,这些都很简单。

4.6 Looper是怎么处理队列的消息的?

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

// Looper.loop()
final Looper me = myLooper();           // 获得当前线程的Looper实例
final MessageQueue queue = me.mQueue;   // 获取消息队列
for (;;) {                              // 死循环
        Message msg = queue.next();     // 取出队列中的消息
        msg.target.dispatchMessage(msg); // 将消息分发给Handler
}

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

这里的关键其实就是:nextPollTimeoutMillis,决定了堵塞与否,以及堵塞的时间,三种情况:

等于0时,不堵塞,立即返回,Looper第一次处理消息,有一个消息处理完 ;

大于0时,最长堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);

等于-1时,无消息时,会一直堵塞;

此处是通过Linux的**[epoll机制](https://juejin.cn/post/6896495861954510861#heading-6)**来堵塞,原因是需要处理 native侧 的事件。

没有消息时堵塞并进入休眠释放CPU资源,有消息时再唤醒线程。

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

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

五、IdleHandler是什么?

评论区有小伙子说:把idleHandler加上就完整了,那就安排下吧~

MessageQueue 类中有一个 static 的接口 IdleHanlder

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

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

使用方法也很简单,代码示例如下:

输出结果如下

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

定义了添加和删除IdleHandler的函数:

next() 函数中用到了 mIdleHandlers 列表:

六、一些其他问题

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

答:上面说了,Looper通过**queue.next()**获取消息队列消息,当队列为空,会堵塞,

此时主线程也堵塞在这里,好处是:main函数无法退出,APP不会一启动就结束!

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

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

  • 当系统受到因用户操作产生的通知时,会通过 Binder 方式跨进程通知 ApplicationThread;
  • 它通过Handler机制,往 ActivityThreadMessageQueue 中插入消息,唤醒了主线程;
  • queue.next() 能拿到消息了,然后 dispatchMessage 完成事件分发;

Tips:ActivityThread 中的内部类H中有具体实现

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

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

上面说了,如果直接在Activity中初始化一个Handler对象,会报如下错误:

原因是

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

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

代码示例如下

private static class MyHandler extends Handler {
    //创建一个弱引用持有外部类的对象
    private final WeakReference<MainActivity> content;

    private MyHandler(MainActivity content) {
        this.content = new WeakReference<MainActivity>(content);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity= content.get();
        if (activity != null) {
            switch (msg.what) {
                case 0: {
                    activity.notifyUI();
                }
            }
        }
    }
}

另外,还有一种情况可能会引起内存泄漏:延时消息,Activity关闭消息还没处理完,可以在Activity的onDestroy()中调用:handler.removeCallbacksAndMessages(null) 移除Message/Runnable。

3.同步屏障机制

通过上面的学习,我们知道用Handler发送的Message后,MessageQueueenqueueMessage()按照 时间戳升序 将消息插入到队列中,而Looper则按照顺序,每次取出一枚Message进行分发,一个处理完到下一个。

这时候,问题来了:有一个紧急的Message需要优先处理怎么破?你可能或说**直接sendMessage()**不就可以了,不用等待立马执行,看上去说得过去,不过可能有这样一个情况:

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

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

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

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

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

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

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

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