Toast系列(一):Toast基本工作原理(android 7.0及以前)

Toast是一个独立的顶级窗口,显示时浮在其他窗口之上,不依赖于任何Activity,即使在任何activity未启动的情况下或者当前位于前台的程序是别的app时,依然可以显示。

各个app都可以随心所欲地在屏幕上弹出Toast,为了避免“百花齐放”,必须有第三者来管理,使其顺序显示。这个第三者就是系统服务INotificationManager。INotificationManager会维护一个Toast显示队列,各个程序抛出的显示Toast的请求会被依次加入该队列,然后逐个取出显示后从队列里去除。这个过程实际上属于进程通信,通过AIDL的方式。

我们知道AIDL进程通信的媒介是Binder,Toast定义了一个内部类TN,继承了ITransientNotification.Stub类,作为通信媒介传给服务端,当轮到某个Toast弹出时,服务端通过调用它TN对象的show和hide方法控制Toast窗口的显示和超出显示时长时后的隐藏。

  private static class TN extends ITransientNotification.Stub {

        //定义handler
        final Handler mHandler = new Handler();

        //显示Toast窗口
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };


        //隐藏Toast窗口
        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                mNextView = null;
            }
        };



        //供服务端调用的show方法
        @Override
        public void show() {
            mHandler.post(mShow);
        }


        //供服务端调用的hide方法
        @Override
        public void hide() {
            mHandler.post(mHide);
        }



        public void handleShow() {
            ...
            if (mView != mNextView) {
                ...
                handleHide();             
                ...
                mView = mNextView;
                ...
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                //将Toast的View添加为窗口
                mWM.addView(mView, mParams);
                ...
            }
        }
    
        public void handleHide() {
            ...
            if (mView != null) {
                ...
                if (mView.getParent() != null) {
                    mWM.removeView(mView);
                }            
                ...
                mView = null;
            }
        }
    }

我们可以看到,TN首先定义了一个Handler和两个供Handler发送执行的Runnable对象mShow和mHide。mShow里执行的是显示Toast窗口的代码,mHide里执行隐藏Toast窗口的代码。

TN暴露给服务端调用的show和hide方法直接通过Handler发送执行对应的Runnable对象。之所以采用handler方式是确保UI操作在主线程进行。

另外,TN还封装了Toast的相关信息,如添加到窗口的View,窗口位置信息及其他布局参数等。

private static class TN extends ITransientNotification.Stub {
 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        ...
        int mGravity;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;

       //添加到窗口的View
        View mView;
        View mNextView;
        int mDuration;
        
        WindowManager mWM;
        ...
    }

TN是何时被传给服务端的呢?当Toast调用show方法时,Tn对象会被传递给INotificationManager,INotificationManager通过TN的show和hide方法控制Toast窗口的显示和隐藏。

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

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

        try {
            //将该Toast的TN对象抛给INotificationManager,加入显示队列
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
  }
...

}

Toast的Duration耗尽后,INotificationManager会通过调用TN的hide方法让窗口消失,并将其从显示队列里去除。如果我们用Toast的cancel方法显示取消掉一个Toast会执行什么呢?

public class Toast{
  ...  
  public void cancel() {
        mTN.hide();

        try {
            getService().cancelToast(mContext.getPackageName(), mTN);
        } catch (RemoteException e) {
            // Empty
        }
    }
  ...

}

可以看到,调用cancel后,首先调用TN的hide方法立即隐藏掉Toast窗口,然后调用INotificationManager的cancelToast方法将其从队列中去除。

另外,我们看到在TN内,用mView来保存添加到窗口的布局,那么mNextView是做什么的?

首先,Toast本身有一个mNextView成员变量,保存Toast的View。当调用Toast的show方法时,会将Toast的mNextView赋值给TN的mNextView。当执行到TN的handleShow方法时,会先判断是否与TN的mView为同一个对象,不是同一对象才会真正添加到窗口。在handleHide方法的末尾,方才将TN的mView置空。handleHide执行完之后,再将TN的mNextView置空。目的就是防止同一Toast实例,当其窗口正在显示时,再次添加其View到窗口。这也是单例Toast迅速多次调用show方法不会重复显示Toast窗口的原因。

注意,Toast的工作机制依赖于INotificationManager,需要系统通知权限,如果app系统通知权限被禁用,你的app的Toast将无法弹出。以淘宝app和优酷app的"再按一次退出程序"的Toast提示为例,关闭通知权限,Toast将不再显示。

你可能感兴趣的:(源码,toast原理,TN)