Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定:

  1: Toast.makeText(this, "333", Toast.LENGTH_LONG).show();

但是我们经常会遇到这样一种情况,比如说我们有两个按钮,每次点击之后就会弹出一个Toast,但是如果这两个按钮快速点击,我们看到的效果是这样的:

但实际上我们想要的效果应该是这样的:

源码解读

看了上面两张效果图的对比之后,我们应该明白了,第一种情况是在我们点击完之后,Toast还在不断的显示,直到把我们点过的全部显示一遍,那么我们不得不猜测这里有一个Toast队列,每当我们makeText的时候,系统就会往这个队列当中添加一个Toast,然后再不断从队列中取出一个一个的Toast显示,那么真实情况是不是这样呢?看看源码就知道了。

我们先来看看makeText方法的源码:

  1:     public static Toast makeText(Context context, CharSequence text, int duration) {
  2:         Toast result = new Toast(context);
  3:
  4:         LayoutInflater inflate = (LayoutInflater)
  5:                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  6:         View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
  7:         TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
  8:         tv.setText(text);
  9:
 10:         result.mNextView = v;
 11:         result.mDuration = duration;
 12:
 13:         return result;
 14:     }

makeText方法,进来之后先new一个Toast对象,然后就是我们熟悉的LayoutInflater,通过LayoutInflater拿到一个View对象之后,再获取这个View中的TextView,然后把我们传进来的文本内容设置给这个TextView,最后返回一个Toast实例,原来Toast的源码这么简单!!拿到一个Toast对象之后,接下来的工作就是显示了,显示的时候我们调用的是show方法,那就一起来看看这个show方法吧。

  1:     public void show() {
  2:         if (mNextView == null) {
  3:             throw new RuntimeException("setView must have been called");
  4:         }
  5:
  6:         INotificationManager service = getService();
  7:         String pkg = mContext.getPackageName();
  8:         TN tn = mTN;
  9:         tn.mNextView = mNextView;
 10:
 11:         try {
 12:             service.enqueueToast(pkg, tn, mDuration);
 13:         } catch (RemoteException e) {
 14:             // Empty
 15:         }
 16:     }

这个show方法也不长,先是获得一个服务,然后拿到一个TN对象,再把Toast的视图交给这个TN的实例,最后调用服务的一个队列方法,把这个TN的实例扔到这个队列中去,看到队列两个字我们就应该明白了为什么会出现上面第一幅图的情况,原来我们每点击一次按钮,就会往这个队列中放一个Toast,当我们点击很多次之后不再点击了,但是队列中还是有很多Toast,这时系统就会把这些还没有显示过的Toast一个一个的读出来显示,这就是我们在第一幅图中看到的现象。那么我们循序渐进,一步一步的来分析这里的情况,看看怎么从根本上解决这个问题。先来看一下这里的这个getService()方法。

  1:     // =======================================================================================
  2:     // All the gunk below is the interaction with the Notification Service, which handles
  3:     // the proper ordering of these system-wide.
  4:     // =======================================================================================
  5:
  6:     private static INotificationManager sService;
  7:
  8:     static private INotificationManager getService() {
  9:         if (sService != null) {
 10:             return sService;
 11:         }
 12:         sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
 13:         return sService;
 14:     }

这里我们重点看一下这个方法的注释,意思是说下面这段代码,获得的是一个与系统通知有关的服务,这个服务控制着整个系统的通知正常有序的进行(大概就是这个意思吧,原谅我英语是个渣渣)。拿到这个服务之后呢,下面就是TN这个类了,我们先来看看TN这类的源码:

  1:     private static class TN extends ITransientNotification.Stub {
  2:         final Runnable mShow = new Runnable() {
  3:             @Override
  4:             public void run() {
  5:                 handleShow();
  6:             }
  7:         };
  8:
  9:         final Runnable mHide = new Runnable() {
 10:             @Override
 11:             public void run() {
 12:                 handleHide();
 13:                 // Don't do this in handleHide() because it is also invoked by handleShow()
 14:                 mNextView = null;
 15:             }
 16:         };
 17:
 18:         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
 19:         final Handler mHandler = new Handler();
 20:
 21:         int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
 22:         int mX, mY;
 23:         float mHorizontalMargin;
 24:         float mVerticalMargin;
 25:
 26:
 27:         View mView;
 28:         View mNextView;
 29:
 30:         WindowManager mWM;
 31:
 32:         TN() {
 33:             // XXX This should be changed to use a Dialog, with a Theme.Toast
 34:             // defined that sets up the layout params appropriately.
 35:             final WindowManager.LayoutParams params = mParams;
 36:             params.height = WindowManager.LayoutParams.WRAP_CONTENT;
 37:             params.width = WindowManager.LayoutParams.WRAP_CONTENT;
 38:             params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 39:                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
 40:                     | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 41:             params.format = PixelFormat.TRANSLUCENT;
 42:             params.windowAnimations = com.android.internal.R.style.Animation_Toast;
 43:             params.type = WindowManager.LayoutParams.TYPE_TOAST;
 44:             params.setTitle("Toast");
 45:         }
 46:
 47:         /**
 48:          * schedule handleShow into the right thread
 49:          */
 50:         @Override
 51:         public void show() {
 52:             if (localLOGV) Log.v(TAG, "SHOW: " + this);
 53:             mHandler.post(mShow);
 54:         }
 55:
 56:         /**
 57:          * schedule handleHide into the right thread
 58:          */
 59:         @Override
 60:         public void hide() {
 61:             if (localLOGV) Log.v(TAG, "HIDE: " + this);
 62:             mHandler.post(mHide);
 63:         }
 64:
 65:         public void handleShow() {
 66:             if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
 67:                     + " mNextView=" + mNextView);
 68:             if (mView != mNextView) {
 69:                 // remove the old view if necessary
 70:                 handleHide();
 71:                 mView = mNextView;
 72:                 Context context = mView.getContext().getApplicationContext();
 73:                 if (context == null) {
 74:                     context = mView.getContext();
 75:                 }
 76:                 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
 77:                 // We can resolve the Gravity here by using the Locale for getting
 78:                 // the layout direction
 79:                 final Configuration config = mView.getContext().getResources().getConfiguration();
 80:                 final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
 81:                 mParams.gravity = gravity;
 82:                 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
 83:                     mParams.horizontalWeight = 1.0f;
 84:                 }
 85:                 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
 86:                     mParams.verticalWeight = 1.0f;
 87:                 }
 88:                 mParams.x = mX;
 89:                 mParams.y = mY;
 90:                 mParams.verticalMargin = mVerticalMargin;
 91:                 mParams.horizontalMargin = mHorizontalMargin;
 92:                 if (mView.getParent() != null) {
 93:                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
 94:                     mWM.removeView(mView);
 95:                 }
 96:                 if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
 97:                 mWM.addView(mView, mParams);
 98:                 trySendAccessibilityEvent();
 99:             }
100:         }
101:
102:         private void trySendAccessibilityEvent() {
103:             AccessibilityManager accessibilityManager =
104:                     AccessibilityManager.getInstance(mView.getContext());
105:             if (!accessibilityManager.isEnabled()) {
106:                 return;
107:             }
108:             // treat toasts as notifications since they are used to
109:             // announce a transient piece of information to the user
110:             AccessibilityEvent event = AccessibilityEvent.obtain(
111:                     AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
112:             event.setClassName(getClass().getName());
113:             event.setPackageName(mView.getContext().getPackageName());
114:             mView.dispatchPopulateAccessibilityEvent(event);
115:             accessibilityManager.sendAccessibilityEvent(event);
116:         }
117:
118:         public void handleHide() {
119:             if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
120:             if (mView != null) {
121:                 // note: checking parent() just to make sure the view has
122:                 // been added...  i have seen cases where we get here when
123:                 // the view isn't yet added, so let's try not to crash.
124:                 if (mView.getParent() != null) {
125:                     if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
126:                     mWM.removeView(mView);
127:                 }
128:
129:                 mView = null;
130:             }
131:         }
132:     }

这个类虽然有点长,但是我们不用怕,一点一点来解剖,首先TN这个类继承自ITransientNotification.Stub,这个一看就是个AIDL,这里有两个方法添加了@override注解,看来这个接口里边有两个方法需要我们实现。一个是show一个是hide,这里用到了Handler,关于Handler的讲解,可以查看我的另一个篇博客从源码角度深入理解Handler,我们知道Handler内部也有排队机制,这里的show和hide方法主要是调用了两个线程mShow和mHide,而这两个线程最终调用的是handleShow和handleHide方法,先看这个handleShow方法,首先布局文件肯定得有,然后这里拿到了一个WindowManager,然后就是给mView设置各种布局参数,最后这一行代码非常重要mWM.addView(mView, mParams);看到这里恍然大悟,原来是Toast的视图是通过WindowManager的addView来加载的。再看这个handleHide方法,就是把mView从WindowManager中移除。现在我们再回过头来看TN的构造方法,在构造方法中就是对WindowManager的初始化。

这下我们应该有个大致的脉络了,当我们调用Toast的show方法时,并不会直接去显示它,而是先new一个TN变量,将这个TN对象的实例添加到队列中,至于Toast的显示与隐藏,则是通过TN来调控的,比如Toast的cancel方法,我们来看看这个方法的源码:

  1:     /**
  2:      * Close the view if it's showing, or don't show it if it isn't showing yet.
  3:      * You do not normally have to call this.  Normally view will disappear on its own
  4:      * after the appropriate duration.
  5:      */
  6:     public void cancel() {
  7:         mTN.hide();
  8:
  9:         try {
 10:             getService().cancelToast(mContext.getPackageName(), mTN);
 11:         } catch (RemoteException e) {
 12:             // Empty
 13:         }
 14:     }

果然是调用了TN的hide方法。

好了,分析了这么多,下面我们该说说正事了,

就是我们该怎么样随心所欲的控制Toast的显示时间?

我们先说说开篇第二幅图中显示的效果要怎么实现,看了上面的分析我想大家心中应该已经清楚了要怎么实现?就是让我们的队列中时时刻刻只有一个Toast,这样就不会点击完成很久之后Toast还在那里悠哉游哉的显示,下面是具体实现代码:

  1: public class ToastUtil {
  2:
  3:   private static Toast toast;
  4:
  5:   public static void showTextLong(Context context, String text) {
  6:     if (toast == null) {
  7:       toast = Toast.makeText(context, text, Toast.LENGTH_LONG);
  8:     } else {
  9:       toast.setText(text);
 10:       toast.setDuration(Toast.LENGTH_LONG);
 11:     }
 12:     toast.show();
 13:   }
 14:
 15:   public static void showTextShort(Context context, String text) {
 16:     if (toast == null) {
 17:       toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
 18:     } else {
 19:       toast.setText(text);
 20:       toast.setDuration(Toast.LENGTH_SHORT);
 21:     }
 22:     toast.show();
 23:   }
 24:
 25:   public static void cancelToast() {
 26:     if (toast != null) {
 27:       toast.cancel();
 28:     }
 29:   }
 30: }

我们只要一个Toast实例,当Toast不为空的时候我们只是重新设置它的显示文本和时间,调用方法如下:

  1: ToastUtil.showTextLong(this, "111");
  2:
  3: ToastUtil.showTextLong(this, "222");

调用方式也是很简单,这个时候我们得想想另外一个问题了,假如我们想延长Toast的显示时间该怎么办?duration直接设置为10秒?我们先来看看源码吧:

  1:     private static final int LONG_DELAY = 3500; // 3.5 seconds
  2:     private static final int SHORT_DELAY = 2000; // 2 seconds
  3:     private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
  4:     {
  5:         Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
  6:         long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
  7:         mHandler.removeCallbacksAndMessages(r);
  8:         mHandler.sendMessageDelayed(m, delay);
  9:     }

这一段源码位于\sources\android-16\com\android\server包的NotificationManagerService类中,这里代码很明确的告诉我们,如果我们show一个Toast的时候,设置的时间为Toast.LENGTH_LONG,那么就按Toast.LENGTH_LONG来显示,如果设置了其他值则全部按Toast.LENGTH_SHORT来显示。而LONG_DELAY是3.5秒,SHORT_DELAY是2秒,也就是说一个Toast最长的显示时间为3.5秒。

那么我们有什么方法来延长Toast的显示时间呢?一个简单的方法就是前文说的,先把N个Toast加入到队列中去,然后让他们自己一个一个慢慢去显示,作为这种显示方式的一种优化,我们也可以在一个线程中来显示Toast,思路是这样的:先show一个Toast出来,然后sleep()3秒,这个时候Toast就要消失了,然后我们再show一个出来,就这样循环显示,代码我就不贴了,关于这里的详情请查看android开发之Toast的多种应用。但是这样可控性太差,有没有好一点方法呢?抱歉,我目前还没想到更好的解决方案,如果有哪位童鞋知道,烦请不吝赐教。

本文涉及到的源码下载https://github.com/lenve/Toast

从源码角度深入理解Toast相关推荐

  1. 从源码角度彻底理解ReentrantLock(重入锁)

    源码分析ReentrantLock https://blog.csdn.net/lxltmac/article/details/84871929白话讲解lock

  2. [Java]源码角度深入理解哈希表,手撕常见面试题

    专栏简介 :java语法及数据结构 题目来源:leetcode,牛客,剑指offer 创作目标:从java语法角度实现底层相关数据结构,达到手撕各类题目的水平. 希望在提升自己的同时,帮助他人,,与大 ...

  3. 从源码角度理解 FragmentTransaction实现

    谈到fragment的使用,肯定绕不过FragmentTransaction事务,对fragment的操作必定用到它,其提供show,hide,add,remove,replace等常用的fragme ...

  4. 从源码角度理解LinearLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从LinearLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简单起见 ...

  5. 从源码角度理解FrameLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从最简单的FrameLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简 ...

  6. 从源码角度理解ConstraintLayout#onMeasure对child的measure调用次数

    熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们简单看下较为复杂的ConstraintLayout,看一下它对子View的onMeasure调用次数具体是多 ...

  7. 对应到对象 数据库驼峰_从源码角度理解Mybatis字段映射(一) - 驼峰式命名

    凯伦说,公众号ID: KailunTalk,努力写出最优质的技术文章,欢迎关注探讨. 在上篇博客-[JDBC] 处理ResultSet,构建Java对象中提到,我们需要分析Mybatis在转换Resu ...

  8. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  9. 从源码角度来读Handler

    最近打算从源码角度来读一下Handler,MessageQueue,Message,Looper,这四个面试必考项.所以今天先从Handler开始. 一.Handler的作用 源码定义 There a ...

最新文章

  1. 从协议入手,剖析OAuth2.0(译 RFC 6749)
  2. 投影转换_即插即用,办公投影不用愁:毕亚兹Mini DP转HDMIVGA转换器
  3. plsql设置字段可为空_2015最新整理PLSQL常用设置
  4. pythonjson数据写入csv_将JSON数据从“Requests”Python模块写入CSV
  5. 浅析jQuery的链式调用 之 each函数
  6. vs为什么打了断点不断_为什么西餐厅里的牛排又嫩又多汁?原来大厨都做了“这一步”...
  7. Android7.1的EDP屏替换
  8. ssh连接+执行系统命令
  9. 怎样在html中实现图层重叠,javascript – 在HTML5画布中实现图层
  10. php 常用的知识点归集(下)
  11. 终于知道以后该咋办了!
  12. 故障诊断:12cR2 Flex ASM 环境中节点启动失败的诊断和分析
  13. 4.5管道实现机制和模拟构建管道「深入浅出ASP.NET Core系列」
  14. js点击事件onclick_关于JavaScript的事件绑定问题
  15. VS2013+VAX使用技巧
  16. 英文简历模板计算机专业,2016计算机专业英文简历模板
  17. concurrent包中atomic中的怪异现象
  18. 酷我音乐在计算机其他的图标怎么去掉,酷我音乐这个快捷方式怎么删除?
  19. mysql脏写_图解脏写、脏读、不可重复读、幻读
  20. Trajectory following with MAVROS OFFBOARD on Raspberry Pi

热门文章

  1. 文件粉碎机解决AppInit.Dlls劫持
  2. html编辑器自定义脚本,我的自定义MAX脚本编辑器,代码高亮功能
  3. webmatrix3 php,webmatrix
  4. 并查集——奇偶性(Parity)
  5. 报童问题求解最大利润_钢管下料问题模型
  6. Delphi 10 Seattle小票打印控件TQ_Printer
  7. 一文读懂“生成式 AI”
  8. 对腾讯云的TDSQL的简单入门学习
  9. Linux个人防火墙的设计与实现
  10. JavaWeb - Servlet实现文件下载漂亮小姐姐视频(文末有小姐姐视频Gif图哦)