美文网首页
Toast源码分析

Toast源码分析

作者: 风月寒 | 来源:发表于2021-01-15 15:41 被阅读0次
    Toast的使用
    Toast.makeText(MainActivity.this,"-----",Toast.LENGTH_LONG).show();
    
    
    
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }
    
    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
                @NonNull CharSequence text, @Duration int duration) {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            Toast result = new Toast(context, looper);
            result.mText = text;
            result.mDuration = duration;
            return result;
        } else {
            Toast result = new Toast(context, looper);
            View v = ToastPresenter.getTextToastView(context, text);
            result.mNextView = v;
            result.mDuration = duration;
            return result;
        }
    }
    

    在makeText就是创建一个Toast实例。

    下面我们来看一下show方法。

    public void show() {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                checkState(mNextView != null || mText != null, "You must either set a text or a view");
            } else {
                if (mNextView == null) {
                    throw new RuntimeException("setView must have been called");
                }
            }
    
            INotificationManager service = getService();//1
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;//2
            tn.mNextView = mNextView;
            final int displayId = mContext.getDisplayId();
    
            try {
                if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                    if (mNextView != null) {
                        // It's a custom toast
                        service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                    } else {
                        // It's a text toast
                        ITransientNotificationCallback callback =
                                new CallbackBinder(mCallbacks, mHandler);
                        service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback);
                    }
                } else {
                    service.enqueueToast(pkg, mToken, tn, mDuration, displayId);//3
                }
            } catch (RemoteException e) {
                // Empty
            }
        }
    

    注释1通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,

    注释2TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub,ITransientNotification.Stub是出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已。该内部类主要的作用是控制Toast的show和hide。

    private static class TN extends ITransientNotification.Stub{
        TN(Context context, String packageName, Binder token, List<Callback> callbacks,
                    @Nullable Looper looper) {
                IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface(
                        ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
                mPresenter = new ToastPresenter(context, accessibilityManager, getService(),
                        packageName);
                mParams = mPresenter.getLayoutParams();
                mPackageName = packageName;
                mToken = token;
                mCallbacks = callbacks;
    
                mHandler = new Handler(looper, null) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case SHOW: {
                                IBinder token = (IBinder) msg.obj;
                                handleShow(token);
                                break;
                            }
                            case HIDE: {
                                handleHide();
                                // Don't do this in handleHide() because it is also invoked by
                                // handleShow()
                                mNextView = null;
                                break;
                            }
                            case CANCEL: {
                                handleHide();
                                // Don't do this in handleHide() because it is also invoked by
                                // handleShow()
                                mNextView = null;
                                try {
                                    getService().cancelToast(mPackageName, mToken);
                                } catch (RemoteException e) {
                                }
                                break;
                            }
                        }
                    }
                };
            }
        
    }
    

    注释3把TN对象和一些参数传递到远程NotificationManagerService中去,调用enqueueToast().

     public void enqueueToast(String pkg, ITransientNotification callback, int duration)
            {
                
                final boolean isSystemToast = isCallerSystemOrPhone() || ("android".equals(pkg));//4
                final boolean isPackageSuspended =
                        isPackageSuspendedForUser(pkg, Binder.getCallingUid());
    
                synchronized (mToastQueue) {
                    int callingPid = Binder.getCallingPid();
                    long callingId = Binder.clearCallingIdentity();
                    try {
                        ToastRecord record;
                        int index;
                        if (!isSystemToast) {
                            index = indexOfToastPackageLocked(pkg);//5
                        } else {
                            index = indexOfToastLocked(pkg, callback);//6
                        }
                        //index>0,表明队列中已经存在,则调用hide(),然后进行更新
                        if (index >= 0) {
                            record = mToastQueue.get(index);
                            record.update(duration);
                            try {
                                record.callback.hide();
                            } catch (RemoteException e) {
                            }
                            record.update(callback);
                        } else {
                        //表示队列中没有,则创建一个WindowToken和ToastRecord,然后加入到队列中。
                            Binder token = new Binder();
                            mWindowManagerInternal.addWindowToken(token, TYPE_TOAST, DEFAULT_DISPLAY);
                            record = new ToastRecord(callingPid, pkg, callback, duration, token);
                            mToastQueue.add(record);
                            index = mToastQueue.size() - 1;
                        }
                        keepProcessAliveIfNeededLocked(callingPid);//7
                      
                        if (index == 0) {
                            showNextToastLocked();//8
                        }
                    } finally {
                        Binder.restoreCallingIdentity(callingId);
                    }
                }
            }
    

    注释4通过isSystemToast判断是否为系统Toast。如果当前Toast所属的进程的包名为“android”,则为系统Toast。如果是系统Toast一定可以进入到系统Toast队列中,不会被黑名单阻止。

    注释5和6则是根据是否为系统Toast分别去判断队列中根据报名判断队列中是否存在。

    注释7则是将当前的Toast所在的进程设置为前台进程,这也是为什么activity销毁后Toast仍会显示。

    注释8 index=0 表示Toast就是当前的Toast,

    @GuardedBy("mToastQueue")
        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(record.token);//9
                    scheduleDurationReachedLocked(record);//10
                    return;
                } catch (RemoteException e) {
                    
                }
            }
        }
    
    

    注释9是record.callback中的callback是ITransientNotification类型,这说明就是传经来的TN实例。也就是调用tn重点额show()。

    public void show(IBinder windowToken) {
        mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
    }
    
    mHandler = new Handler(looper, null) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case SHOW: {
                                IBinder token = (IBinder) msg.obj;
                                handleShow(token);
                                break;
                            }
                            
                };
            }
            
    public void handleShow(IBinder windowToken) {
                if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                    return;
                }
                if (mView != mNextView) {
                    // remove the old view if necessary
                    handleHide();
                    mView = mNextView;
                    mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
                            mHorizontalMargin, mVerticalMargin,
                            new CallbackBinder(getCallbacks(), mHandler));
                }
            }
    

    经过一系列的调用,最后调用mPresenter的show()方法。

    public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
                int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
                @Nullable ITransientNotificationCallback callback) {
        
            mView = view;
            mToken = token;
    
            adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
                    horizontalMargin, verticalMargin);//11
            if (mView.getParent() != null) {
                mWindowManager.removeView(mView);
            }
            try {
                mWindowManager.addView(mView, mParams);//12
            } catch (WindowManager.BadTokenException e) {
               
            }
        
        }
    

    注释11是调用adjustLayoutParams(),主要作用是将将参数赋值到WindowManager.LayoutParams。

    private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
                int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
                float verticalMargin) {
            Configuration config = mResources.getConfiguration();
            int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
            params.gravity = absGravity;
            if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                params.horizontalWeight = 1.0f;
            }
            if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                params.verticalWeight = 1.0f;
            }
            params.x = xOffset;
            params.y = yOffset;
            params.horizontalMargin = horizontalMargin;
            params.verticalMargin = verticalMargin;
            params.packageName = mContext.getPackageName();
            params.hideTimeoutMilliseconds =
                    (duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
            params.token = windowToken;
        }
    

    注释12则将view添加到window上。

    这样Toast就显示出来了,再回到注释10.

    private void scheduleDurationReachedLocked(ToastRecord r)
        {
            mHandler.removeCallbacksAndMessages(r);
            Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
            long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;//13
            mHandler.sendMessageDelayed(m, delay);
        }
    

    在makeText中有一个默认的时间段,

    @Duration
        public int getDuration() {
            return mDuration;
        }
        
    @IntDef(prefix = { "LENGTH_" }, value = {
                LENGTH_SHORT,
                LENGTH_LONG
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface Duration {}
    
        public static final int LENGTH_SHORT = 0;
    
        public static final int LENGTH_LONG = 1;
    
    

    从这里可知,LENGTH_SHORT = 0,表明会立马消失,而事实是湖等一下消失,实际的操作就是在注释10这里。LONG_DELAY为3.5秒,SHORT_DELAY为2秒。

    然后会发一个延时消息,延时的时间就是消失的时间。

    这个mHandler是WorkerHandler的实例。

     protected class WorkerHandler extends Handler
        {
            public WorkerHandler(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg)
            {
                switch (msg.what)
                {
                    case MESSAGE_DURATION_REACHED:
                        handleDurationReached((ToastRecord)msg.obj);
                        break;
                    case MESSAGE_FINISH_TOKEN_TIMEOUT:
                        handleKillTokenTimeout((IBinder)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;
                }
            }
            
            private void handleDurationReached(ToastRecord record)
        {
            if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
            synchronized (mToastQueue) {
                int index = indexOfToastLocked(record.pkg, record.callback);
                if (index >= 0) {
                    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
            }
    
            ToastRecord lastToast = mToastQueue.remove(index);
    
            mWindowManagerInternal.removeWindowToken(lastToast.token, false /* removeWindows */,
                    DEFAULT_DISPLAY);
            // We passed 'false' for 'removeWindows' so that the client has time to stop
            // rendering (as hide above is a one-way message), otherwise we could crash
            // a client which was actively using a surface made from the token. However
            // we need to schedule a timeout to make sure the token is eventually killed
            // one way or another.
            scheduleKillTokenTimeout(lastToast.token);
    
            keepProcessAliveIfNeededLocked(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();
            }
        }
    

    最终会调用cancelToastLocked,进而调用record.callback.hide()。然后将toast从队列中移除,token从WMS中移除。

    总结

    1、Toast 调用 show 方法的时候 ,实际上是将自己纳入到 NotificationManager 的 Toast 管理中去,期间传递了一个本地的 TN 类型或者是 ITransientNotification.Stub 的 Binder 对象

    2、NotificationManager 收到 Toast 的显示请求后,将生成一个 Binder 对象,将它作为一个窗口的 token 添加到 WMS 对象,并且类型是 TOAST

    3、NotificationManager 将这个窗口 token 通过 ITransientNotification 的 show 方法传递给远程的 TN 对象,并且抛出一个超时监听消息 scheduleTimeoutLocked

    4、TN 对象收到消息以后将往 Handler 对象中 post 显示消息,然后调用显示处理函数将 Toast 中的 View 添加到了 WMS 管理中, Toast 窗口显示

    5、NotificationManager 的 WorkerHandler 收到 MESSAGE_TIMEOUT 消息, NotificationManager 远程调用进程隐藏 Toast 窗口,然后将窗口 token 从 WMS 中删除

    1610696436(1).png

    相关文章

      网友评论

          本文标题:Toast源码分析

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