内容简介:显示一个Toast只需要调用它的可以看到显示由我们发现,在
显示一个Toast只需要调用它的 show() 方法,看一下源码
/**
109 * Show the view for the specified duration.
110 */
111 public void show() {
112 if (mNextView == null) {
113 throw new RuntimeException("setView must have been called");
114 }
115
116 INotificationManager service = getService(); //获得NotificationManagerService
117 String pkg = mContext.getOpPackageName(); // 包名
118 TN tn = mTN;
119 tn.mNextView = mNextView;
120
121 try {
122 service.enqueueToast(pkg, tn, mDuration); // 调用NMS
123 } catch (RemoteException e) {
124 // Empty
125 }
126 }
复制代码
可以看到显示由 getService() 获得了 NMS,NMS主要是Android系统用来管理 通知服务的,而且Toast也属于系统通知的一种。 NMS调用了 enqueueToast(pkg,tn,mDuration) ,这三个参数分别是 :
- pkg : 应用包名
- tn : 是Toast的一个静态内部类Tn,用于被回调,内部含有两个主要方法用来显示,隐藏Toast , 并且这两个方法是等着被回调,不会主动调用
private static class TN extends ITransientNotification.Stub {
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.obtainMessage(HIDE).sendToTarget();
}
public void cancel() {
if (localLOGV) Log.v(TAG, "CANCEL: " + this);
mHandler.obtainMessage(CANCEL).sendToTarget();
}
}
复制代码
我们发现,在 show() 和 hide() 方法中,都是调用了 Handler 来处理,这是因为 NMS是运行在系统的进程中,Toast和NMS之间是一个IPC过程,NMS只能通过远程调用的方式来显示和隐藏Toast, 而 TN 这个类是一个 Binder 类,它里面的 show() 'hide()'方法会在Toast和NMS进行IPC时回调。
这时, TN 是运行在Binder的线程池中的,而我们的Toast需要在当前UI线程中显示,所以需要通过 Handler ,配合着 Looper 来完成切换线程
- mDuration : 这个就是我们创建Toast时传入的 显示时长。
接下来我们会一层一层的深入分析,贴一张图来记录进度:
INotificationManager.enqueueToast(pkg,tn,mDurtion)
接下来分析 enqueueToast(pkg,tn,mDuration) 里面是做了什么事情呢?我们继续点开看看
NotificationManagerService.java #enqueueToast()
1087 synchronized (mToastQueue) {
1089 ...
1090 try {
//将Toast请求封装为ToastRecord 见 1117行
1091 ToastRecord record;
1092 int index = indexOfToastLocked(pkg, callback);
1093 //如果Toast已经在列表中,则更新它的信息
1095 if (index >= 0) {
1096 record = mToastQueue.get(index);
1097 record.update(duration);
1098 } else {
1099 // 限制Toast的个数, MAX_PACKAGE_NOTIFICATIONS = 50
1101 if (!isSystemToast) {
1102 int count = 0;
1103 final int N = mToastQueue.size();
1104 for (int i=0; i<N; i++) {
1105 final ToastRecord r = mToastQueue.get(i);
1106 if (r.pkg.equals(pkg)) {
1107 count++;
1108 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
1109 Slog.e(TAG, "Package has already posted " + count
1110 + " toasts. Not showing more. Package=" + pkg);
1111 return;
1112 }
1113 }
1114 }
1115 }
1116
1117 record = new ToastRecord(callingPid, pkg, callback, duration);
1118 mToastQueue.add(record);
...
1121 }
1122 // 如果index==0,代表就是当前的Toast
1126 if (index == 0) {
1127 showNextToastLocked();
1128 }
1129 } finally {
1130 Binder.restoreCallingIdentity(callingId);
1131 }
复制代码
我只截取了重要的一部分代码,有点长,我们来慢慢看:
-
enqueueToast()方法首先把Toast的请求封装到ToastRecord中。
record = new ToastRecord(callingPid, pkg, callback, duration); 复制代码
- 将
ToastRecord添加到一个存储到到mToastQueue中,这是一个ArrayList的存储结构,对于非系统应用,最多能存下50个Toast,
mToastQueue.add(record); 复制代码
1106 if (r.pkg.equals(pkg)) {
1107 count++;
1108 if (count >= MAX_PACKAGE_NOTIFICATIONS) { //MAX_PACKAGE_NOTIFICATIONS = 50
1109 Slog.e(TAG, "Package has already posted " + count
1110 + " toasts. Not showing more. Package=" + pkg);
1111 return;
1112 }
1113}
复制代码
- 接下来NMS通过
showNestToastLocked()来显示当前的Toast ,index = 0,就代表队列中只剩下一个Toast,就是当前的Toast
1119 index = mToastQueue.size() - 1;
...
1126 if (index == 0) {
1127 showNextToastLocked();
1128 }
复制代码
enqueueToast()分析完了,记录一下
INotificationManager.showNextToastLocked()
先贴上源码
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
...
try {
record.callback.show(record.token); //回调 callback中的show方法
scheduleTimeoutLocked(record); //发送延时消息,取决于Toast的时长
return;
} catch (RemoteException e) {
...//省略部分代码
}
}
}
复制代码
这里 record.callback 就是 我们前面提到的 TN ,在这里回调它的 show() 方法Toast,并通过 scheduleTimeoutLocked(record) 根据指定的Toast显示时长发送一个延时消息。
当前记录:
下面来看一下延时消息是如何实现的
scheduleTimeoutLocked(record)
private void scheduleTimeoutLocked(ToastRecord r)
{
mHandler.removeCallbacksAndMessages(r);
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
mHandler.sendMessageDelayed(m, delay);
}
复制代码
在上面代码中, LONG_DELAY 和 SHORT_DELAY 分别是 3.5s和 2s. 在经过这么长的延时后,发送message
来看一下对应此Message的处理:
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MESSAGE_TIMEOUT:
handleTimeout((ToastRecord)msg.obj);
break;
...
}
}
复制代码
好,接下来又要进 handleTimeout() 方法中看一下,码住:
handleTimeout(ToastRecord record)
private void handleTimeout(ToastRecord record)
{
synchronized (mToastQueue) {
int index = indexOfToastLocked(record.pkg, record.callback);
if (index >= 0) {
cancelToastLocked(index);
}
}
}
复制代码
在经过一定的延时时间后,就该去除当前这个Toast了,跟 index 判断 当前这个Toast是否还在队列中,如果还在,NMS就会通过 cancelToastLocked() 方法来隐藏Toast,并将其从队列中移除。 如果队列中还有其他的Toast,继续调用 showNextToastLocked(); 将其显示.
cancelToastLocked(int index)
void cancelToastLocked(int index) {
ToastRecord record = mToastQueue.get(index);
try {
record.callback.hide();
} catch (RemoteException e) {
...
}
ToastRecord lastToast = mToastQueue.remove(index); //从队列中移除
mWindowManagerInternal.removeWindowToken(lastToast.token, true, DEFAULT_DISPLAY); //移除window
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) { //如果还有其他的Toast,继续显示
// 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();
}
}
复制代码
到这里,一个Toast的显示到隐藏就结束了。刚刚我们说过,Toast的显示和隐藏都是回调 TN 中的方法 :
现在来看一下 TN 中具体显示Toast的方法 : 可以看一下注释
public void handleShow(IBinder windowToken) {
...
//如果此时handler又发送 隐藏 或者 取消的消息,则返回,也就是不显示了。
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
if (mView != mNextView) {
// 如果正在显示的Toast不是当前的Toast.(是之前显示的还没隐藏掉),那就隐藏它
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
//获得WindowMangaer
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
... //省略了显示Toast的一些布局参数的设置代码
try {
mWM.addView(mView, mParams); //将Toast添加到Window中
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
...
}
}
}
复制代码
handleShow() 主要做的就是将Toast添加到Window中。相反, handleHide() 会把Toast的View从Window中移除:
public void handleHide() {
if (mView != null) {
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeViewImmediate(mView);
}
mView = null;
}
}
复制代码
mView.getParent() 用来判断此View是否已经被添加到Window,如果!= null,说明 有Window包含这个Toast的View,那就移除它。
Toast的流程图:
ps: 这是我第一次写关于源码分析的博客,还有很多可能写的不清楚的地方,大家可以指出来互相学习O(∩_∩)O。我觉得通过看源码能够让你对系统的理解层次清晰,不会及停留在表面。看源码的时候注意不要被各个类之间的调用关系搞混,可以随手画出来记录一下。。。
Reference: 《Android艺术开发探索》-
(完~)
以上所述就是小编给大家介绍的《Android学习笔记14-从源码分析Toast的创建过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Spring容器创建源码解析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- LayoutInflater创建View源码阅读
- Spring源码系列:BeanFactory的创建
- Sphinx源码学习笔记(一):索引创建
- Netty NioEventLoop 创建过程源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
机器学习实践指南
麦好 / 机械工业出版社 / 2014-4-1 / 69.00
《机器学习实践指南:案例应用解析》是机器学习及数据分析领域不可多得的一本著作,也是为数不多的既有大量实践应用案例又包含算法理论剖析的著作,作者针对机器学习算法既抽象复杂又涉及多门数学学科的特点,力求理论联系实际,始终以算法应用为主线,由浅入深以全新的角度诠释机器学习。 全书分为准备篇、基础篇、统计分析实战篇和机器学习实战篇。准备篇介绍了机器学习的发展及应用前景以及常用科学计算平台,主要包括统......一起来看看 《机器学习实践指南》 这本书的介绍吧!
MD5 加密
MD5 加密工具
RGB CMYK 转换工具
RGB CMYK 互转工具