IPC在Toast中的應(yīng)用

1、Toast概念和問題的引出

Toast 中文名"土司"回溺,應(yīng)該算是 Android 使用頻率很高的一個(gè) widget 了,一般使用它來做一些操作的提示信息,例如我們?cè)谔詫汓c(diǎn)擊收藏寶貝之后底部就會(huì)出現(xiàn)"收藏成功"這樣的提示信息累贤,這種就是 Toast 的功能了。

先拋出一個(gè)問題哦:那就是多個(gè)應(yīng)用或者多個(gè)線程都要在顯示 Toast 的時(shí)候系統(tǒng)是怎么去處理這個(gè)問題的少漆?是串行執(zhí)行還是并行執(zhí)行呢臼膏?

2、如何使用 Toast

Toast 的使用可以說是非常簡(jiǎn)單示损,簡(jiǎn)單到一句話就可以顯示一個(gè) Toast 了渗磅,下面是具體的代碼體現(xiàn)。

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

3检访、構(gòu)造 Toast 對(duì)象

下面就依據(jù)上面的那句代碼分析 Toast 顯示到手機(jī)屏幕上是需要經(jīng)過什么過程始鱼。

主要入口為兩個(gè):

  • public static Toast makeText(Context context, CharSequence text, int duration)
  • public void show()

3.1、makeText 源碼分析

makeText 方法內(nèi)部主要做了這樣幾件事:

  • 創(chuàng)建一個(gè) Toast 對(duì)象脆贵;
  • 加載一個(gè)系統(tǒng)布局医清,這個(gè)布局就是用顯示這個(gè) Toast 的;
  • 設(shè)置需要顯示的文本內(nèi)容丹禀,就是我們?cè)谏厦嬖O(shè)置的 "hello Toast" 和設(shè)置這個(gè) Toast 需要顯示的時(shí)間;

其實(shí)就根據(jù)傳入到 makeText 內(nèi)部的幾個(gè)屬性構(gòu)造出一個(gè) Toast 對(duì)象状勤。

public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
    Toast result = new Toast(context);
    LayoutInflater inflate = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
    TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
    tv.setText(text);
    
    result.mNextView = v;
    result.mDuration = duration;
    return result;
}

3.2鞋怀、Toast 構(gòu)造方法

Toast 的構(gòu)造方法內(nèi)部很簡(jiǎn)單,主要是創(chuàng)建一個(gè) TN 對(duì)象持搜,并且賦值給 mTN密似,這個(gè) TN 對(duì)象非常重要,在下面分析中再慢慢說明它的作用葫盼。因?yàn)?TN 對(duì)象 是在 Toast 構(gòu)造中創(chuàng)建的残腌,因此一個(gè) Toast 對(duì)象的創(chuàng)建就對(duì)應(yīng)創(chuàng)建一個(gè) TN 對(duì)象,這句話也很重要贫导。

public Toast(Context context) {
    mContext = context;
    //創(chuàng)建一個(gè) TN 對(duì)象
    mTN = new TN();
    //設(shè)置顯示的位置
    mTN.mY = context.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.toast_y_offset);
    mTN.mGravity = context.getResources().getInteger(
            com.android.internal.R.integer.config_toastDefaultGravity);
}

4抛猫、TN 類的分析

看 TN 類先看它的聲明,學(xué)過 AIDL 的同學(xué)應(yīng)該都知道孩灯,TN 繼承了 ITransientNotification.Stub 類闺金,說明它是遠(yuǎn)程服務(wù)真正干活的類。這什么意思呢峰档?其實(shí)是這樣的败匹,當(dāng)客戶端需要顯示一個(gè)土司,那么這個(gè)要求是要交給遠(yuǎn)程服務(wù) NotificationManagerService 去統(tǒng)一調(diào)度的【這里是第一次 IPC 過程:用戶進(jìn)程訪問服務(wù)進(jìn)程 NotificationManagerService 】讥巡。

而 NotificationManagerService 調(diào)度完成之后是怎么通知用戶進(jìn)程去 show / hide 土司的呢掀亩?遠(yuǎn)程服務(wù)進(jìn)程通過 mTN 與用戶進(jìn)程(當(dāng)前進(jìn)程)進(jìn)行交互(show,hide等操作)【這里是第二次 IPC 過程:服務(wù)進(jìn)程 NotificationManagerService 訪問 用戶進(jìn)程 TN】。

private static class TN extends ITransientNotification.Stub {
    final Runnable mHide = new Runnable() {
        @Override
        public void run() {
            handleHide();
            // Don't do this in handleHide() because it is also invoked by handleShow()
            mNextView = null;
        }
    };
    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams()
    final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            IBinder token = (IBinder) msg.obj;
            handleShow(token);
        }
    };
    int mGravity;
    int mX, mY;
    float mHorizontalMargin;
    float mVerticalMargin;
    View mView;
    View mNextView;
    int mDuration;
    WindowManager mWM;
    static final long SHORT_DURATION_TIMEOUT = 5000;
    static final long LONG_DURATION_TIMEOUT = 1000;
    TN() {
        // XXX This should be changed to use a Dialog, with a Theme.Toast
        // defined that sets up the layout params appropriately.
        final WindowManager.LayoutParams params = mParams;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.windowAnimations = com.android.internal.R.style.Animation_Toast;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
    }
    /**
     * schedule handleShow into the right thread
     */
    @Override
    public void show(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.obtainMessage(0, windowToken).sendToTarget();
    }
    /**
     * schedule handleHide into the right thread
     */
    @Override
    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.post(mHide);
    }
    public void handleShow(IBinder windowToken) {
        if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                + " mNextView=" + mNextView);
        if (mView != mNextView) {
            // remove the old view if necessary
            handleHide();
            mView = mNextView;
            Context context = mView.getContext().getApplicationContext();
            String packageName = mView.getContext().getOpPackageName();
            if (context == null) {
                context = mView.getContext();
            }
            mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
            // We can resolve the Gravity here by using the Locale for getting
            // the layout direction
            final Configuration config = mView.getContext().getResources().getConfigura
            final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDi
            mParams.gravity = gravity;
            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL)
                mParams.horizontalWeight = 1.0f;
            }
            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                mParams.verticalWeight = 1.0f;
            }
            mParams.x = mX;
            mParams.y = mY;
            mParams.verticalMargin = mVerticalMargin;
            mParams.horizontalMargin = mHorizontalMargin;
            mParams.packageName = packageName;
            mParams.hideTimeoutMilliseconds = mDuration ==
                Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            mParams.token = windowToken;
            if (mView.getParent() != null) {
                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                mWM.removeView(mView);
            }
            if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
            mWM.addView(mView, mParams);
            trySendAccessibilityEvent();
        }
    }
    private void trySendAccessibilityEvent() {
        AccessibilityManager accessibilityManager =
                AccessibilityManager.getInstance(mView.getContext());
        if (!accessibilityManager.isEnabled()) {
            return;
        }
        // treat toasts as notifications since they are used to
        // announce a transient piece of information to the user
        AccessibilityEvent event = AccessibilityEvent.obtain(
                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
        event.setClassName(getClass().getName());
        event.setPackageName(mView.getContext().getPackageName());
        mView.dispatchPopulateAccessibilityEvent(event);
        accessibilityManager.sendAccessibilityEvent(event);
    }        
    public void handleHide() {
        if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
        if (mView != null) {
            // note: checking parent() just to make sure the view has
            // been added...  i have seen cases where we get here when
            // the view isn't yet added, so let's try not to crash.
            if (mView.getParent() != null) {
                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                mWM.removeViewImmediate(mView);
            }
            mView = null;
        }
    }
}

5欢顷、顯示一個(gè)Toast

  • 在 show 方法中首先判斷 mNextView 是否為空槽棍,這個(gè) mNextView 就是加載出來需要展示 Toast 的布局啦,如果連布局都沒有抬驴,那么顯示什么土司炼七,直接就 throw 了。

  • 獲取包名 pkg 怎爵,為什么需要包名呢特石,因?yàn)樵谶h(yuǎn)程服務(wù)它會(huì)拿這個(gè)包名去判斷需要顯示的 Toast 是不是來至同一個(gè)進(jìn)程的(一個(gè)進(jìn)程對(duì)應(yīng)一個(gè)包名),具體的判斷過程待會(huì)再分析鳖链。

  • 給 tn 賦值 mTN ,還記得這個(gè) mTN 不墩莫?他就是在 Toast.makeText 內(nèi)部方法中創(chuàng)建的(也就是在Toast構(gòu)造中創(chuàng)建)芙委,我在前面說過一個(gè) Toast 對(duì)象對(duì)應(yīng)一個(gè) mTN 對(duì)象。

  • 調(diào)用 IPC 通知遠(yuǎn)程服務(wù) NotificationServiceManager 去enqueueToast狂秦,這個(gè)過程非常重要灌侣。我們看到它將一個(gè) tn 作為參數(shù)傳入,這是干嘛用呢裂问?我們先前在介紹 TN 這個(gè)類時(shí)說過侧啼,這個(gè)類是用于進(jìn)程間通訊的牛柒,因?yàn)轱@示和隱藏 Toast 是需要由遠(yuǎn)程的 NMS 去統(tǒng)一調(diào)度的,它內(nèi)部維護(hù)一個(gè)集合存放一個(gè)個(gè) Toast 痊乾,當(dāng)?shù)搅酥付〞r(shí)間可以顯示這個(gè) Toast 了皮壁,那么 NMS 就可以通過 TN 這個(gè)代理類去通知客戶端去真正顯示一個(gè) Toast 。

public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }
    //獲取遠(yuǎn)程服務(wù) INotificationManager
    INotificationManager service = getService();
    //獲取包名 pkg 
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    try {
        service.enqueueToast(pkg, tn, mDuration);
    } catch (RemoteException e) {
        // Empty
    }
}

5.1哪审、INotificationManager.Stub.Proxy.enqueueToast

service.enqueueToast(pkg, tn, mDuration) 這個(gè)方法的調(diào)用實(shí)際上是通過
INotificationManager的代理對(duì)象去執(zhí)行的蛾魄,具體執(zhí)行代碼就在下面:主要就是將數(shù)據(jù)寫入到 Parcel _data 中,然后調(diào)用 mRemote.transact 進(jìn)行 RPC 遠(yuǎn)程調(diào)用操作,之后通過 _reply 獲取返回的數(shù)據(jù)湿滓。

//INotificationManager.Stub.Proxy.enqueueToast 代碼
public void enqueueToast(java.lang.String pkg, android.app.ITransientNotification callback, int duration) throws android.os.RemoteException
{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(pkg);
                //callback就是 tn 對(duì)象滴须,這里需要傳遞給遠(yuǎn)程服務(wù),因此轉(zhuǎn)化為 Bidner 
        _data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
        _data.writeInt(duration);
        mRemote.transact(Stub.TRANSACTION_enqueueToast, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

6叽奥、開始遠(yuǎn)程服務(wù)調(diào)用 Toast

[ NMS 的源碼] (https://android.googlesource.com/platform/frameworks/base/+/f76a50c/services/java/com/android/server/NotificationManagerService.java) 有一個(gè)網(wǎng)站 grepcode 是可以查閱源碼的扔水,有需要可以上這里看看源碼。

在 enqueueToast 方法內(nèi)部主要做了這幾件事:

  • synchronize 加鎖判斷朝氓,這里為什么要加鎖魔市?
  • indexOfToastLocked校驗(yàn)操作;
  • 根據(jù) indexOfToastLocked 的校驗(yàn)結(jié)果進(jìn)行后續(xù)的操作膀篮。
public void enqueueToast(String pkg, ITransientNotification callback, int duration) {

    //pkg 和 callback 的空校驗(yàn) 代碼省略...

    //加鎖判斷
    synchronized (mToastQueue) {
        int callingPid = Binder.getCallingPid();
        long callingId = Binder.clearCallingIdentity();
        try {
            ToastRecord record;
            //查找隊(duì)列中是否由該 ToastRecord
            int index = indexOfToastLocked(pkg, callback);
            // If it's already in the queue, we update it in place, we don't
            // move it to the end of the queue.
            //表示在隊(duì)列中找到對(duì)應(yīng)的 ToastRecord
            if (index >= 0) {
                //更新這個(gè) ToastRecord 的顯示時(shí)間嘹狞,這種情況是在同一個(gè)用用中,使用同一個(gè) Toast 對(duì)象誓竿,在極短時(shí)間內(nèi)多次調(diào)用show方法磅网,就會(huì)出現(xiàn)這個(gè)情況。           
                record = mToastQueue.get(index);
                record.update(duration);
            } else {
                //沒有找到的情況
                // Limit the number of toasts that any given package except the android
                // package can enqueue.  Prevents DOS attacks and deals with leaks.
                if (!"android".equals(pkg)) {//不是系統(tǒng)彈的土司
                    int count = 0;
                    final int N = mToastQueue.size();
                    for (int i=0; i<N; i++) {
                         final ToastRecord r = mToastQueue.get(i);
                         if (r.pkg.equals(pkg)) {
                             count++;
                            //控制 toast 的次數(shù) 50
                             if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                 Slog.e(TAG, "Package has already posted " + count
                                        + " toasts. Not showing more. Package=" + pkg);
                                 return;
                             }
                         }
                    }
                }
                //沒有找到的情況就創(chuàng)建一個(gè) ToastReord 對(duì)象筷屡。
                record = new ToastRecord(callingPid, pkg, callback, duration);
                //添加到隊(duì)列中涧偷。
                mToastQueue.add(record);
                
                index = mToastQueue.size() - 1;
                keepProcessAliveLocked(callingPid);
            }
            // If it's at index 0, it's the current toast.  It doesn't matter if it's
            // new or just been updated.  Call back and tell it to show itself.
            // If the callback fails, this will remove it from the list, so don't
            // assume that it's valid after this.
            if (index == 0) {
                showNextToastLocked();
            }
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
    }
}

6.1、enqueueToast 加鎖判斷

這里要思考一下為什么這里要加鎖毙死?我們知道鎖就是為了怕并發(fā)訪問出現(xiàn)數(shù)據(jù)的不同步問題燎潮,NMS 是系統(tǒng)服務(wù),那么它的對(duì)象就只有一個(gè)扼倘,多個(gè)進(jìn)程(多個(gè) APP) 或者說這多個(gè)線程都可以獲取到這個(gè)服務(wù)确封,并且可以并發(fā)地去調(diào)用該服務(wù)的 enqueueToast 方法,這就像多個(gè)線程操作同一個(gè)對(duì)象的共享變量出現(xiàn)的數(shù)據(jù)不同步問題了再菊,因此這里需要加鎖爪喘。還有就是 enqueueToast 方法是服務(wù)端的方法,它會(huì)在Binder 線程池中去調(diào)用纠拔。

6.2秉剑、indexOfToastLocked的校驗(yàn)操作

檢測(cè)當(dāng)前pag 包名和 callback 是否在 mToastQueue 中已經(jīng)存在?在分析之前首先了解一下 callback 是什么呢稠诲?我們?cè)谙惹皠?chuàng)建 Toast 構(gòu)造提到過侦鹏,一個(gè) Toast 對(duì)象會(huì)對(duì)應(yīng)一個(gè) mTN 對(duì)象诡曙。這個(gè) callback 就是這個(gè) mTN 對(duì)象的代理對(duì)象,為什么略水?因?yàn)樗切枰M(jìn)行進(jìn)程間通訊的价卤,因此需要將 mTN 對(duì)象轉(zhuǎn)化為 Binder 才能傳輸,而在 NMS 中獲取到這個(gè) Binder 之后會(huì)將其轉(zhuǎn)化為 ITransientNotification.Proxy 代理對(duì)象聚请。

該方法會(huì)返回一個(gè)整數(shù)荠雕,-1表示沒有找到,其他值表示找到了驶赏。

private int indexOfToastLocked(String pkg, ITransientNotification callback)
{
    IBinder cbak = callback.asBinder();
    ArrayList<ToastRecord> list = mToastQueue;
    int len = list.size();
    for (int i=0; i<len; i++) {
        ToastRecord r = list.get(i);
         //包名和binder都一樣時(shí)才表示是同一個(gè) ToastRecord
        if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
            return i;
        }
    }
    return -1;
}

6.3炸卑、根據(jù) indexOfToastLocked 的校驗(yàn)結(jié)果進(jìn)行后續(xù)的操作。

1.如果找到了那么返回值不為-1煤傍,那么就直接更新一下 duration 并且不會(huì)去修改該 ToastRecord 為 mToastQueue 中的位置盖文。意思是什么呢?這里舉個(gè)栗子:這里 for 循環(huán) 500 次蚯姆,每次都調(diào)用 toast.show() 方法五续,其運(yùn)行結(jié)果只會(huì)顯示一次,為什么呢龄恋?因?yàn)槲覀冊(cè)谏厦嫣岬竭^疙驾,使用 indexOfToastLocaked 內(nèi)部是根據(jù) pkg 和 callback 進(jìn)行判斷的,而這兩個(gè)值是一樣的郭毕,因此該方法返回的值不為 -1 它碎,因此就算是 show 500 次其 mToastQueue 中也只有一個(gè) ToastRecord 對(duì)象。

Toast toast = Toast.makeText(this, "Toast", Toast.LENGTH_SHORT);
for (int i = 0; i < 500; i++) {
    toast.show();
}

2.如果找到的值為-1表示需要?jiǎng)?chuàng)建一個(gè)新的 ToastRecord 并且添加到 mToastQueue 中显押。因?yàn)橹暗玫降?index 為 -1 扳肛,因此在這里需要重新計(jì)算 index = mToastQueue.size()-1,這個(gè) index 就表示當(dāng)前新創(chuàng)建 ToastRecord 在隊(duì)列中的位置乘碑,當(dāng)這個(gè)位置為 0 時(shí)表示只有隊(duì)列中只有一個(gè) ToastRecord 挖息,直接執(zhí)行 showNextToastLocked 即可。

3.注意這里兽肤,如果沒有知道的情況套腹,并且同一個(gè) pkg 下的 ToastRecord 的數(shù)量超過了 MAX_PACKAGE_NOTIFICATIONS 那么就直接 return 了,官方表示這是為了避免 DOS attacks资铡。

4.這里注意下面這一種情況沉迹,執(zhí)行結(jié)果是 toast 會(huì)執(zhí)行 50 次,多余都被拋棄了害驹,原因看上面第 3 點(diǎn)。但是重點(diǎn)這種寫法跟第 1 點(diǎn)的寫法的區(qū)別就是 Toast 的創(chuàng)建時(shí)機(jī)不一樣蛤育,這里是每循環(huán)一次就創(chuàng)建一個(gè)宛官,也就是 Toast 的構(gòu)造就會(huì)走一次葫松,還記得上面說過 mTN 是在 Toast 構(gòu)造創(chuàng)建的,每次調(diào)用 makeText 都會(huì)創(chuàng)建一個(gè)新的 Toast 對(duì)象底洗,也就是 mTN 也是一個(gè)新的對(duì)象腋么。那么傳遞給 NMS 的 mTN 就不一樣了,那么在進(jìn)行 indexOfToastLocked 就會(huì)返回 -1 因此這里就是這段代碼的區(qū)別亥揖。

for (int i = 0; i < 500; i++) {
    Toast toast = Toast.makeText(this, "Toast", Toast.LENGTH_SHORT).show();
}

7珊擂、遠(yuǎn)程服務(wù)端調(diào)度顯示和隱藏操作

7.1、showNextToastLocked()

這個(gè)方法主要做了一下幾件事:

  • 從隊(duì)列中取出一個(gè) ToastRecord 并且通過 IPC 調(diào)用用戶進(jìn)程的 TN 中的 show 方法真正去顯示一個(gè) Toast 费变。

  • 通過 Handler 發(fā)送一個(gè)延時(shí)任務(wù)摧扇,時(shí)間為 duration ,在這個(gè)時(shí)間過后就取消顯示這個(gè) Toast挚歧。

private void showNextToastLocked() {
    //從隊(duì)列中取出第一個(gè) ToastRecord 對(duì)象
    ToastRecord record = mToastQueue.get(0);
    //while 循環(huán)是怕應(yīng)用進(jìn)程出現(xiàn)異常 RemoteException
    while (record != null) {
        if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
        try {
            //通知應(yīng)用進(jìn)程去執(zhí)行顯示操作扛稽,實(shí)際上會(huì)調(diào)用 TN 的 show() 方法。
            record.callback.show();
            //在指定時(shí)間隱藏這個(gè) Toast
            scheduleTimeoutLocked(record, false);
            return;
        } catch (RemoteException e) {
             //出現(xiàn)異常的情況滑负,那么就從隊(duì)列中移除
            // remove it from the list and let the process die
            int index = mToastQueue.indexOf(record);
            if (index >= 0) {
                mToastQueue.remove(index);
            }
            keepProcessAliveLocked(record.pid);
            //從隊(duì)列中再取出下一個(gè) ToastRecord 去執(zhí)行在张。
            if (mToastQueue.size() > 0) {
                record = mToastQueue.get(0);
            } else {
                //如果沒有就置null,退出循環(huán)
                record = null;
            }
        }
    }
}

7.2矮慕、record.callback.show();

record.callback 就是用戶進(jìn)程傳遞過來服務(wù)進(jìn)程的 ITransientNotification 的代理對(duì)象帮匾,通過調(diào)用該代理對(duì)象的 show() 方法,這個(gè)過程是一個(gè) IPC 的過程痴鳄,這個(gè)最終會(huì)調(diào)用用戶進(jìn)程的 TN 中的 show() 方法瘟斜。

public void show() {
    //通過 handler 去 post 一個(gè)任務(wù)
    mHandler.post(mShow);
}

final Runnable mShow = new Runnable() {
    public void run() {
        handleShow();
    }
};

//通過 WindowManager 將 View 顯示出來
public void handleShow() {
    if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
            + " mNextView=" + mNextView);
    if (mView != mNextView) {
        // remove the old view if necessary
        handleHide();
        mView = mNextView;
        mWM = WindowManagerImpl.getDefault();
        final int gravity = mGravity;
        mParams.gravity = gravity;
        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
            mParams.horizontalWeight = 1.0f;
        }
        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
            mParams.verticalWeight = 1.0f;
        }
        mParams.x = mX;
        mParams.y = mY;
        mParams.verticalMargin = mVerticalMargin;
        mParams.horizontalMargin = mHorizontalMargin;
        if (mView.getParent() != null) {
            if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
            mWM.removeView(mView);
        }
        if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
        mWM.addView(mView, mParams);
        trySendAccessibilityEvent();
    }
}

7.3、 scheduleTimeoutLocked

使用 Handler 發(fā)送一個(gè)延時(shí)消息夏跷,時(shí)間就是 Toast 設(shè)置的 duration 屬性哼转。

private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
{
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
    mHandler.removeCallbacksAndMessages(r);
    mHandler.sendMessageDelayed(m, delay);
}

7.4、 handleTimeout

private final class WorkerHandler extends Handler
{
    @Override
    public void handleMessage(Message msg)
    {
        switch (msg.what)
        {
            case MESSAGE_TIMEOUT:
                handleTimeout((ToastRecord)msg.obj);
                break;
        }
    }
}

private void handleTimeout(ToastRecord record)
{
  
    synchronized (mToastQueue) {
        int index = indexOfToastLocked(record.pkg, record.callback);
        if (index >= 0) {
            cancelToastLocked(index);
        }
    }
}

7.5、cancelToastLocked

當(dāng)通過 Handler 發(fā)送的延時(shí)任務(wù)的時(shí)間到了那么就需要通知應(yīng)用進(jìn)程去 hide 這個(gè) Toast 萍鲸。具體操作還是通過 IPC 過程去真正調(diào)用 TN 的 hide 方法實(shí)現(xiàn)真正的隱藏操作家夺。

當(dāng)操作完成之后判斷隊(duì)列中是否還有 ToastRecord 沒有執(zhí)行,如果有那么就調(diào)用 showNextToastLocked 方法再次的去取出 ToastRecord 去執(zhí)行佣蓉。

private void cancelToastLocked(int index) {
    ToastRecord record = mToastQueue.get(index);
    try {
        //遠(yuǎn)程調(diào)用應(yīng)用進(jìn)程的 TN 的 hide() 方法隱藏 toast
        record.callback.hide();
    } catch (RemoteException e) {
        Slog.w(TAG, "Object died trying to hide notification " + record.callback
                + " in package " + record.pkg);
        // don't worry about this, we're about to remove it from
        // the list anyway
    }
    mToastQueue.remove(index);
    keepProcessAliveLocked(record.pid);
    //如果隊(duì)列中還有數(shù)據(jù)那么就再次調(diào)用 showNextToastLocked 方法流程跟上面是一樣的。
    if (mToastQueue.size() > 0) {
        // Show the next one. If the callback fails, this will remove
        // it from the list, so don't assume that the list hasn't changed
        // after this point.
        showNextToastLocked();
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亲雪,一起剝皮案震驚了整個(gè)濱河市勇凭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌义辕,老刑警劉巖虾标,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異灌砖,居然都是意外死亡璧函,警方通過查閱死者的電腦和手機(jī)傀蚌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蘸吓,“玉大人善炫,你說我怎么就攤上這事】饧蹋” “怎么了箩艺?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宪萄。 經(jīng)常有香客問我艺谆,道長(zhǎng),這世上最難降的妖魔是什么雨膨? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任擂涛,我火速辦了婚禮,結(jié)果婚禮上聊记,老公的妹妹穿的比我還像新娘撒妈。我一直安慰自己,他們只是感情好排监,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布狰右。 她就那樣靜靜地躺著,像睡著了一般舆床。 火紅的嫁衣襯著肌膚如雪棋蚌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天挨队,我揣著相機(jī)與錄音谷暮,去河邊找鬼。 笑死盛垦,一個(gè)胖子當(dāng)著我的面吹牛湿弦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腾夯,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颊埃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蝶俱?” 一聲冷哼從身側(cè)響起班利,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榨呆,沒想到半個(gè)月后罗标,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年馒稍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皿哨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纽谒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出如输,到底是詐尸還是另有隱情鼓黔,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布不见,位于F島的核電站澳化,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏稳吮。R本人自食惡果不足惜缎谷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灶似。 院中可真熱鬧列林,春花似錦、人聲如沸酪惭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽春感。三九已至砌创,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲫懒,已是汗流浹背嫩实。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窥岩,地道東北人甲献。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谦秧,于是被迫代替她去往敵國(guó)和親竟纳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容