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 {//定义handlerfinal Handler mHandler = new Handler();//显示Toast窗口final Runnable mShow = new Runnable() {@Overridepublic void run() {handleShow();}};//隐藏Toast窗口final Runnable mHide = new Runnable() {@Overridepublic void run() {handleHide();mNextView = null;}};//供服务端调用的show方法@Overridepublic void show() {mHandler.post(mShow);}//供服务端调用的hide方法@Overridepublic 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;//添加到窗口的ViewView 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");}//获得INotificationManagerINotificationManager 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将不再显示。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部