Toast的window创建过程以及源码分析

Posted show_wjy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Toast的window创建过程以及源码分析相关的知识,希望对你有一定的参考价值。

Toast的window创建过程以及源码分析

  • Toast是基于Window来实现的,系统中继承了handler做定时处理
  • 内部实现IPC过程,第一类访问NotificationManagerService(NMS)
  • 通过回调TN接口,实现Toast的显示隐藏
  • Toast属于Window,它内部的视图由两种方式指定,一种是系统默认的样式,另一种是通过setView方法
    来指定一个自定义View

源码分析

  • 从Toast的方法开始
public void show() 
            if (mNextView == null) 
                throw new RuntimeException("setView must have been called");
            
            //远程调用AIDL
            INotificationManager service = getService();
            String pkg = mContext.getOpPackageName();
            TN tn = mTN;
            tn.mNextView = mNextView;

            try 
                //在NMS中实现插入Toast
                service.enqueueToast(pkg, tn, mDuration);
             catch (RemoteException e) 
                // Empty
            
        

NotificationManagerService(NMS)

  • NMS下的INotificationManager
        private final IBinder mService = new INotificationManager.Stub() 
                    //将Toast请求封装成一个ToastRecord对象
                    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) 
                        //有ArrayList集合保存这个请求对象
                        record = mToastQueue.get(index);
                        record.update(duration);
                     else 
                        //若是非系统应用,最大弹框数为50,DOS
                        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) 
                                        //打印
                                         return;
                                     
                                 
                            
                        

                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveLocked(callingPid);
                    
        
  • ToastRecord类介绍,封装请求Toast的对象
        private static final class ToastRecord
                final int pid;`这里写代码片`
                final String pkg;
                //用于回调Toast中TN对象
                final ITransientNotification callback;
                int duration;

                //.....
        
  • 最终显示在NMS下的showNextToastLocked中,callback实际上就是Toast中的TN对象的远程Binder,
    最终会运行在发起Toast请求的应用的Binder线程池
        void showNextToastLocked() 
            ToastRecord record = mToastQueue.get(0);
            while (record != null) 
                if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
                try 
                //回调TN的show方法
                record.callback.show();
                scheduleTimeoutLocked(record);
                return;
            
            //.....
        
  • Toast显示之后,NMS会通过scheduleTimeoutLocked发送延迟消息,时长取决于Toast的时长
        private void scheduleTimeoutLocked(ToastRecord r)
        
            mHandler.removeCallbacksAndMessages(r);
            Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
            //默认3.5s和2s
            long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
            //发送延迟消息
            mHandler.sendMessageDelayed(m, delay);
        
  • 同样,Toast显示之后消失,也是通过callback来完成的
void cancelToastLocked(int index) 
                ToastRecord record = mToastQueue.get(index);
                try 
                    //同样使用回调隐藏
                    record.callback.hide();
                 catch (RemoteException e) 
                    //打印
                
                //移除已处理过的弹框
                mToastQueue.remove(index);
                keepProcessAliveLocked(record.pid);
                if (mToastQueue.size() > 0) 
                   //如果还有未完成的弹框,继续调用
                    showNextToastLocked();
                
        

Toast中的TN

  • Toast的显示和影响过程实际上是通过TN这个类实现的,两个主要方法show和hide,对应显示和隐藏,
    这两个方法被NMS跨进程调用,因此它们运行在Binder线程池中,为了将执行环境切换到Toast的请求线程,在内部使用了handler
private static class TN extends ITransientNotification.Stub 
            final Runnable mShow = new Runnable() 
                @Override
                public void run() 
                    //处理显示的逻辑
                    handleShow();
                
            ;

            final Runnable mHide = new Runnable() 
                @Override
                public void run() 
                    //处理隐藏的逻辑
                    handleHide();
                    mNextView = null;
                
            ;

            //构建windManger对象,设置相应params参数
            TN() 
                final WindowManager.LayoutParams params = mParams;
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.format = PixelFormat.TRANSLUCENT;
                params.windowAnimations = com.android.internal.R.style.Animation_Toast;
                params.type = WindowManager.LayoutParams.TYPE_TOAST;
                params.setTitle("Toast");
                params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            

            @Override
            public void show() 
                if (localLOGV) Log.v(TAG, "SHOW: " + this);
                //通过handler传递消息
                mHandler.post(mShow);
            

            @Override
            public void hide() 
                if (localLOGV) Log.v(TAG, "HIDE: " + this);
                mHandler.post(mHide);
            
        

通过handleShow和handleHide才能真正显示和隐藏Toast的地方

        mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        //添加
        mWM.addView(mView, mParams);
        //删除
        if (mView.getParent() != null) 
               if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
               mWM.removeView(mView);
        

两个重要的AIDL文件

//TN实现的接口
oneway interface ITransientNotification 
    void show();
    void hide();
interface INotificationManager

    /** @deprecated use @link #enqueueNotificationWithTag instead */
    void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived);
    /** @deprecated use @link #cancelNotificationWithTag instead */
    void cancelNotification(String pkg, int id);
    void cancelAllNotifications(String pkg);
    //管理Toast显示和隐藏
    void enqueueToast(String pkg, ITransientNotification callback, int duration);
    void cancelToast(String pkg, ITransientNotification callback);

    void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
    void cancelNotificationWithTag(String pkg, String tag, int id);

以上是关于Toast的window创建过程以及源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Android源码分析Window添加

Android源码分析Window添加

Toast源码深度分析

Toast源码分析(Android 11)

postgres创建表的过程以及部分源码分析

Toast源码分析与学习