美文网首页Android知识Android开发Android进阶之路
《Android开发艺术探索》读书笔记——Toast调用流程的源

《Android开发艺术探索》读书笔记——Toast调用流程的源

作者: 程田 | 来源:发表于2017-02-20 14:10 被阅读363次

    本文将介绍Android中Toast显示时的调用过程,细节逻辑我们不去分析,而是从整体上来看Toast内部的调用过程
    CSDN地址:http://blog.csdn.net/myterabithia/article/details/56012877

    首先看Toast的show()方法

    public void show() {
            if (mNextView == null) {
                throw new RuntimeException("setView must have been called");
            }
    
            INotificationManager service = getService();
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;
            tn.mNextView = mNextView;
    
            try {
                service.enqueueToast(pkg, tn, mDuration);
            } catch (RemoteException e) {
                // Empty
            }
    }
    

    注意这句INotificationManager service = getService(); 很明显,INotificationManager是一个Binder接口,他的具体实现是NotificationManagerService里的一个成员变量:mService,看一下它的enqueueToast方法:

    @Override
     public void enqueueToast(String pkg, ITransientNotification callback, int duration)
            {
                if (DBG) {
                    Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                            + " duration=" + duration);
                }
    
                if (pkg == null || callback == null) {
                    Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                    return ;
                }
    
                final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
                final boolean isPackageSuspended =
                        isPackageSuspendedForUser(pkg, Binder.getCallingUid());
    
                if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid())
                        || isPackageSuspended)) {
                    if (!isSystemToast) {
                        Slog.e(TAG, "Suppressing toast from package " + pkg
                                + (isPackageSuspended
                                        ? " due to package suspended by administrator."
                                        : " by user request."));
                        return;
                    }
                }
    
                synchronized (mToastQueue) {
                    int callingPid = Binder.getCallingPid();
                    long callingId = Binder.clearCallingIdentity();
                    try {
                        ToastRecord record;
                        int index = indexOfToastLocked(pkg, callback);
                        // If it's already in the queue, we update it in place, we don't
                        // move it to the end of the queue.
                        if (index >= 0) {
                            record = mToastQueue.get(index);
                            record.update(duration);
                        } else {
                            // Limit the number of toasts that any given package except the android
                            // package can enqueue.  Prevents DOS attacks and deals with leaks.
                            if (!isSystemToast) {
                                int count = 0;
                                final int N = mToastQueue.size();
                                for (int i=0; i<N; i++) {
                                     final ToastRecord r = mToastQueue.get(i);
                                     if (r.pkg.equals(pkg)) {
                                         count++;
                                         if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                             Slog.e(TAG, "Package has already posted " + count
                                                    + " toasts. Not showing more. Package=" + pkg);
                                             return;
                                         }
                                     }
                                }
                            }
    
                            record = new ToastRecord(callingPid, pkg, callback, duration);
                            mToastQueue.add(record);
                            index = mToastQueue.size() - 1;
                            keepProcessAliveLocked(callingPid);
                        }
                        // If it's at index 0, it's the current toast.  It doesn't matter if it's
                        // new or just been updated.  Call back and tell it to show itself.
                        // If the callback fails, this will remove it from the list, so don't
                        // assume that it's valid after this.
                        if (index == 0) {
                            showNextToastLocked();
                        }
                    } finally {
                        Binder.restoreCallingIdentity(callingId);
                    }
                }
            }
    

    这里有一个细节,注意这段代码:

    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
    final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
    ...
     // Limit the number of toasts that any given package except the android
     // package can enqueue.  Prevents DOS attacks and deals with leaks.
                            if (!isSystemToast) {
                                int count = 0;
                                final int N = mToastQueue.size();
                                for (int i=0; i<N; i++) {
                                     final ToastRecord r = mToastQueue.get(i);
                                     if (r.pkg.equals(pkg)) {
                                         count++;
                                         if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                             Slog.e(TAG, "Package has already posted " + count
                                                    + " toasts. Not showing more. Package=" + pkg);
                                             return;
                                         }
                                     }
                                }
                            }
    
    

    为了防止DOS(Denial of Service,拒绝服务)攻击,系统做了一个限制,非系统应用的toast队列只让放50个,如果大于50个直接return,否则record = new ToastRecord(callingPid, pkg, callback, duration);创建一个新的ToastRecord放入队列。

    然后调用了showNextToastLocked()方法,留意scheduleTimeoutLocked(record);在后文会分析这个方法的作用。

    void showNextToastLocked() {
            ToastRecord record = mToastQueue.get(0);
            while (record != null) {
                if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
                try {
                    record.callback.show();
                    scheduleTimeoutLocked(record);
                    return;
                } catch (RemoteException e) {
                    Slog.w(TAG, "Object died trying to show notification " + record.callback
                            + " in package " + record.pkg);
                    // remove it from the list and let the process die
                    int index = mToastQueue.indexOf(record);
                    if (index >= 0) {
                        mToastQueue.remove(index);
                    }
                    keepProcessAliveLocked(record.pid);
                    if (mToastQueue.size() > 0) {
                        record = mToastQueue.get(0);
                    } else {
                        record = null;
                    }
                }
            }
    }
    

    注意这句:

    record.callback.show();
    

    这里的callback是什么呢?回到前面看看ToastRecord创建代码:

    record = new ToastRecord(callingPid, pkg, callback, duration);
    

    这里传入了一个callback,这个callback是

    public void enqueueToast(String pkg, ITransientNotification callback, int duration)
    

    里的参数,从前文的分析中,这个方法是Toast里的show方法里面调用的

    service.enqueueToast(pkg, tn, mDuration);
    

    所以record.callback.show();里的show实际上是回调了TN中的show方法,这里是一个跨进程调用。

    TN中show()方法直接用mHandler post了一个Runnable

    /**
    * schedule handleShow into the right thread
    */
    @Override
    public void show() {
        if (localLOGV) Log.v(TAG, "SHOW: " + this);
        mHandler.post(mShow);
    }
    
    final Runnable mShow = new Runnable() {
        @Override
        public void run() {
            handleShow();
        }
    };
    

    最终执行了handleShow()方法。

    public void handleShow() {
                if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                        + " mNextView=" + mNextView);
                if (mView != mNextView) {
                    // remove the old view if necessary
                    handleHide();
                    mView = mNextView;
                    Context context = mView.getContext().getApplicationContext();
                    String packageName = mView.getContext().getOpPackageName();
                    if (context == null) {
                        context = mView.getContext();
                    }
                    mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                    // We can resolve the Gravity here by using the Locale for getting
                    // the layout direction
                    final Configuration config = mView.getContext().getResources().getConfiguration();
                    final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                    mParams.gravity = gravity;
                    if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                        mParams.horizontalWeight = 1.0f;
                    }
                    if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                        mParams.verticalWeight = 1.0f;
                    }
                    mParams.x = mX;
                    mParams.y = mY;
                    mParams.verticalMargin = mVerticalMargin;
                    mParams.horizontalMargin = mHorizontalMargin;
                    mParams.packageName = packageName;
                    mParams.removeTimeoutMilliseconds = mDuration ==
                        Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                    if (mView.getParent() != null) {
                        if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                        mWM.removeView(mView);
                    }
                    if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                }
    }
    
    public void handleHide() {
                if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
                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 = null;
                }
    }
    

    handleShow()方法中,首先调用了handleHide()将Toast的视图从WindowManager里移除,然后再添加进去。至此Toast显示过程就结束了。

    现在来分析showNextToastLocked方法中的scheduleTimeoutLocked(record)方法,这个方法的作用是延时一段时间让Toast消失,具体时间根据创建Toast时duration字段值来判断。

    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);
    }
    
    static final int LONG_DELAY = 3500; // 3.5 seconds
    static final int SHORT_DELAY = 2000; // 2 seconds
    

    所以我们设置Toast.LENGTH_LONG其实是3.5秒,Toast.LENGTH_SHORT是2秒。

    然后通过mHandler发送了一个MESSAGE_TIMEOUT消息,看下mHandler怎么处理这个消息,在NotificationManagerService里的mHandler是一个WorkerHandler对象

    private final class WorkerHandler extends Handler
        {
            @Override
            public void handleMessage(Message msg)
            {
                switch (msg.what)
                {
                    case MESSAGE_TIMEOUT:
                        handleTimeout((ToastRecord)msg.obj);
                        break;
                    case MESSAGE_SAVE_POLICY_FILE:
                        handleSavePolicyFile();
                        break;
                    case MESSAGE_SEND_RANKING_UPDATE:
                        handleSendRankingUpdate();
                        break;
                    case MESSAGE_LISTENER_HINTS_CHANGED:
                        handleListenerHintsChanged(msg.arg1);
                        break;
                    case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
                        handleListenerInterruptionFilterChanged(msg.arg1);
                        break;
                }
            }
        }
    

    mHandler处理MESSAGE_TIMEOUT调用了handleTimeout((ToastRecord)msg.obj),handleTimeout有调用了cancelToastLocked(index),

    void cancelToastLocked(int index) {
            ToastRecord record = mToastQueue.get(index);
            try {
                record.callback.hide();
            } catch (RemoteException e) {
                Slog.w(TAG, "Object died trying to hide notification " + record.callback
                        + " in package " + record.pkg);
                // don't worry about this, we're about to remove it from
                // the list anyway
            }
            mToastQueue.remove(index);
            keepProcessAliveLocked(record.pid);
            if (mToastQueue.size() > 0) {
                // 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();
            }
    }
    

    首先会调用record.callback.hide(),实际上是最终调用了TN里的handleHide()方法隐藏当前显示的Toast,这个过程和record.callback.show()一样,这里就不分析了。然后从mToastQueue中移除已经显示了的Toast,最后判断mToastQueue中还有没有Toast要显示,有就继续执行showNextToastLocked()显示Toast。

    至此Toast的显示和消失流程就分析完啦,希望能帮到你。

    相关文章

      网友评论

        本文标题:《Android开发艺术探索》读书笔记——Toast调用流程的源

        本文链接:https://www.haomeiwen.com/subject/pyzswttx.html