Toast源码分析

Toast的使用
Toast.makeText(MainActivity.this,"-----",Toast.LENGTH_LONG).show();


public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
    return makeText(context, null, text, duration);
}

public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
    if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
        Toast result = new Toast(context, looper);
        result.mText = text;
        result.mDuration = duration;
        return result;
    } else {
        Toast result = new Toast(context, looper);
        View v = ToastPresenter.getTextToastView(context, text);
        result.mNextView = v;
        result.mDuration = duration;
        return result;
    }
}

在makeText就是创建一个Toast实例。

下面我们来看一下show方法。

public void show() {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            checkState(mNextView != null || mText != null, "You must either set a text or a view");
        } else {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
        }

        INotificationManager service = getService();//1
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;//2
        tn.mNextView = mNextView;
        final int displayId = mContext.getDisplayId();

        try {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                if (mNextView != null) {
                    // It's a custom toast
                    service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                } else {
                    // It's a text toast
                    ITransientNotificationCallback callback =
                            new CallbackBinder(mCallbacks, mHandler);
                    service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                }
            } else {
                service.enqueueToast(pkg, mToken, tn, mDuration, displayId);//3
            }
        } catch (RemoteException e) {
            // Empty
        }
    }

注释1通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,

注释2TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub,ITransientNotification.Stub是出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已。该内部类主要的作用是控制Toast的show和hide。

private static class TN extends ITransientNotification.Stub{
    TN(Context context, String packageName, Binder token, List callbacks,
                @Nullable Looper looper) {
            IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface(
                    ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
            mPresenter = new ToastPresenter(context, accessibilityManager, getService(),
                    packageName);
            mParams = mPresenter.getLayoutParams();
            mPackageName = packageName;
            mToken = token;
            mCallbacks = callbacks;

            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            break;
                        }
                        case CANCEL: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            try {
                                getService().cancelToast(mPackageName, mToken);
                            } catch (RemoteException e) {
                            }
                            break;
                        }
                    }
                }
            };
        }
    
}

注释3把TN对象和一些参数传递到远程NotificationManagerService中去,调用enqueueToast().

 public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            
            final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));//4
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index;
                    if (!isSystemToast) {
                        index = indexOfToastPackageLocked(pkg);//5
                    } else {
                        index = indexOfToastLocked(pkg, callback);//6
                    }
                    //index>0,表明队列中已经存在,则调用hide(),然后进行更新
                    if (index >= 0) {
                        record = mToastQueue.get(index);
                        record.update(duration);
                        try {
                            record.callback.hide();
                        } catch (RemoteException e) {
                        }
                        record.update(callback);
                    } else {
                    //表示队列中没有,则创建一个WindowToken和ToastRecord,然后加入到队列中。
                        Binder token = new Binder();
                        mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                    }
                    keepProcessAliveIfNeededLocked(callingPid);//7
                  
                    if (index == 0) {
                        showNextToastLocked();//8
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

注释4通过isSystemToast判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast。如果是系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。

注释5和6则是根据是否为系统Toast分别去判断队列中根据报名判断队列中是否存在。

注释7则是将当前的Toast所在的进程设置为前台进程,这也是为什么activity销毁后Toast仍会显示。

注释8 index=0 表示Toast就是当前的Toast,

@GuardedBy("mToastQueue")
    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
                record.callback.show(record.token);//9
                scheduleDurationReachedLocked(record);//10
                return;
            } catch (RemoteException e) {
                
            }
        }
    }

注释9是record.callback中的callback是ITransientNotification类型,这说明就是传经来的TN实例。也就是调用tn重点额show()。

public void show(IBinder windowToken) {
    mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}

mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        
            };
        }
        
public void handleShow(IBinder windowToken) {
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
                        mHorizontalMargin, mVerticalMargin,
                        new CallbackBinder(getCallbacks(), mHandler));
            }
        }

经过一系列的调用,最后调用mPresenter的show()方法。

public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
            int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
            @Nullable ITransientNotificationCallback callback) {
    
        mView = view;
        mToken = token;

        adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                horizontalMargin, verticalMargin);//11
        if (mView.getParent() != null) {
            mWindowManager.removeView(mView);
        }
        try {
            mWindowManager.addView(mView, mParams);//12
        } catch (WindowManager.BadTokenException e) {
           
        }
    
    }

注释11是调用adjustLayoutParams(),主要作用是将将参数赋值到WindowManager.LayoutParams。

private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
            int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
            float verticalMargin) {
        Configuration config = mResources.getConfiguration();
        int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
        params.gravity = absGravity;
        if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
            params.horizontalWeight = 1.0f;
        }
        if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
            params.verticalWeight = 1.0f;
        }
        params.x = xOffset;
        params.y = yOffset;
        params.horizontalMargin = horizontalMargin;
        params.verticalMargin = verticalMargin;
        params.packageName = mContext.getPackageName();
        params.hideTimeoutMilliseconds =
                (duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
        params.token = windowToken;
    }

注释12则将view添加到window上。

这样Toast就显示出来了,再回到注释10.

private void scheduleDurationReachedLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;//13
        mHandler.sendMessageDelayed(m, delay);
    }

在makeText中有一个默认的时间段,

@Duration
    public int getDuration() {
        return mDuration;
    }
    
@IntDef(prefix = { "LENGTH_" }, value = {
            LENGTH_SHORT,
            LENGTH_LONG
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Duration {}

    public static final int LENGTH_SHORT = 0;

    public static final int LENGTH_LONG = 1;

从这里可知,LENGTH_SHORT = 0,表明会立马消失,而事实是湖等一下消失,实际的操作就是在注释10这里。LONG_DELAY为3.5秒,SHORT_DELAY为2秒。

然后会发一个延时消息,延时的时间就是消失的时间。

这个mHandler是WorkerHandler的实例。

 protected class WorkerHandler extends Handler
    {
        public WorkerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_DURATION_REACHED:
                    handleDurationReached((ToastRecord)msg.obj);
                    break;
                case MESSAGE_FINISH_TOKEN_TIMEOUT:
                    handleKillTokenTimeout((IBinder)msg.obj);
                    break;
                case MESSAGE_SAVE_POLICY_FILE:
                    handleSavePolicyFile();
                    break;
                case MESSAGE_SEND_RANKING_UPDATE:
                    handleSendRankingUpdate();
                    break;
                case MESSAGE_LISTENER_HINTS_CHANGED:
                    handleListenerHintsChanged(msg.arg1);
                    break;
                case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
                    handleListenerInterruptionFilterChanged(msg.arg1);
                    break;
            }
        }
        
        private void handleDurationReached(ToastRecord record)
    {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }
void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            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
        }

        ToastRecord lastToast = mToastQueue.remove(index);

        mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
                DEFAULT_DISPLAY);
        // We passed 'false' for 'removeWindows' so that the client has time to stop
        // rendering (as hide above is a one-way message), otherwise we could crash
        // a client which was actively using a surface made from the token. However
        // we need to schedule a timeout to make sure the token is eventually killed
        // one way or another.
        scheduleKillTokenTimeout(lastToast.token);

        keepProcessAliveIfNeededLocked(record.pid);
        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();
        }
    }

最终会调用cancelToastLocked,进而调用record.callback.hide()。然后将toast从队列中移除,token从WMS中移除。

总结

1、Toast 调用 show 方法的时候 ,实际上是将自己纳入到 NotificationManager 的 Toast 管理中去,期间传递了一个本地的 TN 类型或者是 ITransientNotification.Stub 的 Binder 对象

2、NotificationManager 收到 Toast 的显示请求后,将生成一个 Binder 对象,将它作为一个窗口的 token 添加到 WMS 对象,并且类型是 TOAST

3、NotificationManager 将这个窗口 token 通过 ITransientNotification 的 show 方法传递给远程的 TN 对象,并且抛出一个超时监听消息 scheduleTimeoutLocked

4、TN 对象收到消息以后将往 Handler 对象中 post 显示消息,然后调用显示处理函数将 Toast 中的 View 添加到了 WMS 管理中, Toast 窗口显示

5、NotificationManager 的 WorkerHandler 收到 MESSAGE_TIMEOUT 消息, NotificationManager 远程调用进程隐藏 Toast 窗口,然后将窗口 token 从 WMS 中删除

1610696436(1).png

你可能感兴趣的:(Toast源码分析)