天天看點

Toast在子線程調用的問題

Toast我們平時經常使用,但是你是否了解在子線程中要如何使用Toast呢?

Toast的一般姿勢

平時我們經常在主線程中直接使用Toast,代碼看起來會像下面這樣

Toast.makeText(MainActivity.this, "", Toast.LENGTH_SHORT).show();
           

但是如果在子線程調用是不會有toast彈出的

Toast的正确姿勢

如果在子線程調用那麼讓Toast能正常顯示的方式是在它之前和之後調用Looper.prepare()和Looper.loop()

Looper.prepare();
Toast.makeText(MainActivity.this, "", Toast.LENGTH_SHORT).show();
Looper.loop();
           

原因是什麼呢

我們得從源碼角度來分析,看看在Toast show()的時候做了些什麼

public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }

    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;

    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}
           

是以Toast其實是通過NotificationManagerService來實作Toast的展示的,而傳給他的參數裡的 mTn又是什麼呢,

其實它是Toast的一個内部類,它有兩個方法,show()和hide()是用來給NotificationManagerService回調的,可以看看它的代碼

private static class TN extends ITransientNotification.Stub {
    ....
    /**
     * schedule handleShow into the right thread
     */
    @Override
    public void show(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }

    /**
     * schedule handleHide into the right thread
     */
    @Override
    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.obtainMessage(HIDE).sendToTarget();
    }
           

是以可以看出來,Toast通過 NotificationManagerService來統一排程 Toast,而 NotificationManagerService回調 TN 的show()來往對應的線程發消息,

既然是handler實作,那麼來看看它的實作代碼,就在TN的構造方法裡有這麼一段

if (looper == null) {
    // Use Looper.myLooper() if looper is not specified.
    looper = Looper.myLooper();
    if (looper == null) {
        throw new RuntimeException(
                "Can't toast on a thread that has not called Looper.prepare()");
    }
}
mHandler = new Handler(looper, null) {....
           

是以沒有調用prepare()和啟動消息隊列的話,在子線程調用Toast是顯示不出來的。

總結

Toast在主線程的顯示隻需要調用show()就可以,如果想在子線程調用,則需要在子線程啟動Looper,這樣才能有消息隊列來承載Handler收發消息。否則子線程的Toast是不能顯示的

更多Android進階技術,面試資料系統整理分享,職業生涯規劃,産品,思維,行業觀察,談天說地。可以加Android架構師群;701740775。

繼續閱讀