问题引入:当我们弹出toast的时候,一般会
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
mToast.cancel();//取消上次
mToast.setText("new message");//设置新内容
mToast.show();// 再次呈现
//mToast.cancel();
mToast.setText("new message");
mToast.show();
不管,你怎么疯狂点击,当停止以后,2s内就会消失。那么肯定心中有疑问:深入研究:从源码层次下,逐步分析。
1、创建 toast的时候
public static Toast makeText(Context context, CharSequence text, int duration) {
Toast result = new Toast(context);// 创建一个对象,并在构造函数里面实例化TN( 访问远程服务时候用到的callback函数)
//加载view
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);
//保存view
result.mNextView = v;
result.mDuration = duration;
return result;
}
2、在setText方法里面进行赋值
public void setText(CharSequence s) {
if (mNextView == null) {
throw new RuntimeException("This Toast was not created with Toast.makeText()");
}
TextView tv = (TextView) mNextView.findViewById(com.android.internal.R.id.message);
if (tv == null) {
throw new RuntimeException("This Toast was not created with Toast.makeText()");
}
// 把文本直接赋值到 R.id.message 对应的TextView上面。
tv.setText(s);
}
3、然后show方法源码
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
//NotificationManagerService 服务
INotificationManager service = getService();
//和应用相关联,这样消息队列方便管理,每个应用最多显示50个 toast
String pkg = mContext.getPackageName();
//callback 用于处理服务器调度,比如 开始显示,隐藏等操作。
TN tn = mTN;
tn.mNextView = mNextView;
// 加入 toast队列,等待被调度
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
4、下面跟踪到 NotificationManagerService 源码的 enqueueToast方法
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
.....
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
//如果队列中已经存在,那么就直接取出来,并更新数值。
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
//不存在,走创建流程,保存到队列中去。
.........
record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
}
if (index == 0) {
//调度下一个 toast
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
这也就解释了,为什么一个全局的对象toast可以频繁使用,因为当队列中存在该 toast的时候,直接进行了 更新操作,从头开始计时;当不存在的时候,就会走创建流程,然后 通过方法 showNextToastLocked 进行调度,进行显示。
private void showNextToastLocked() {
//从队列中取出第一个toast
ToastRecord record = mToastQueue.get(0);
while (record != null) {
try {
//调用toast的 TN对象
record.callback.show();
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
......
}
}
}
6、然后我们回到 Toast 对象的TN内部类中的show方法,改方法最终会调用handleShow方法。
public void handleShow() {
//mView 是TN类的内部对象,保留是最近一次引用的view,初始为null
//mNextView是TN类的内部对象,引用的是Toast中的mNextView,在 toast的show方法中赋值。
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
// 此处对mView进行赋值,表示该 toast 的view已经显示。
mView = mNextView;
mWM = (WindowManager)mView.getContext().getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
..............
// 先进行remove操作
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
//然后通过 WindowManager 添加到 系统中,进行toast的显示。
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
7、当时间到达,或者 调用 cancel方法的时候,会调用 TN的hide方法
handleHide();
// 把 该对象 赋值为 null
mNextView = null;
handleHide方法里面的源码如下:
public void handleHide() {
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.removeView(mView);
}
//重置 mView。
mView = null;
}
}
8、下面当用户调用Toast的cancel方法时候
public void cancel() {
//调用TN的hide方法,如第7步中代码所示。
mTN.hide();
//调用 NotificationManagerService 服务移除toast队列
try {
getService().cancelToast(mContext.getPackageName(), mTN);
} catch (RemoteException e) {
// Empty
}
}
总结: