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

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

内容简介:显示一个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的创建过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Building Websites with Joomla!

Building Websites with Joomla!

H Graf / Packt Publishing / 2006-01-20 / USD 44.99

This book is a fast paced tutorial to creating a website using Joomla!. If you've never used Joomla!, or even any web content management system before, then this book will walk you through each step i......一起来看看 《Building Websites with Joomla!》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具