Android学习笔记14-从源码分析Toast的创建过程

栏目: Android · 发布时间: 5年前

内容简介:显示一个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时传入的 显示时长。

接下来我们会一层一层的深入分析,贴一张图来记录进度:

Android学习笔记14-从源码分析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                }
复制代码

我只截取了重要的一部分代码,有点长,我们来慢慢看:

  1. enqueueToast() 方法首先把Toast的请求封装到 ToastRecord 中。
record = new ToastRecord(callingPid, pkg, callback, duration);
复制代码
  1. 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()分析完了,记录一下

Android学习笔记14-从源码分析Toast的创建过程

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显示时长发送一个延时消息。

当前记录:

Android学习笔记14-从源码分析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_DELAYSHORT_DELAY 分别是 3.5s和 2s. 在经过这么长的延时后,发送message

来看一下对应此Message的处理:

@Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_TIMEOUT:
                    handleTimeout((ToastRecord)msg.obj);
                    break;
                ...
            }
        }
复制代码

好,接下来又要进 handleTimeout() 方法中看一下,码住:

Android学习笔记14-从源码分析Toast的创建过程

handleTimeout(ToastRecord record)

Android学习笔记14-从源码分析Toast的创建过程
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();
        }
    }
复制代码
Android学习笔记14-从源码分析Toast的创建过程

到这里,一个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的流程图:

Android学习笔记14-从源码分析Toast的创建过程

ps: 这是我第一次写关于源码分析的博客,还有很多可能写的不清楚的地方,大家可以指出来互相学习O(∩_∩)O。我觉得通过看源码能够让你对系统的理解层次清晰,不会及停留在表面。看源码的时候注意不要被各个类之间的调用关系搞混,可以随手画出来记录一下。。。

Reference: 《Android艺术开发探索》-

(完~)

Android学习笔记14-从源码分析Toast的创建过程

以上所述就是小编给大家介绍的《Android学习笔记14-从源码分析Toast的创建过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软件构架实践

软件构架实践

林·马斯 / 车立红 / 清华大学出版社 / 2012-6 / 49.00元

软件构架实践(第2版),ISBN:9787302080428,作者:(美)林·巴斯(Len Bass),(美)保罗·克莱门茨(Paul Clements),(美)瑞克·凯兹曼(Rick Kazman)著;车立红译;车立红译一起来看看 《软件构架实践》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具