• Connor学Android - Window和WindowManager


    在这里插入图片描述

    Learn && Live

    虚度年华浮萍于世,勤学善思至死不渝

    前言

    Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/CjLtU,话不多说我们马上开始!

    1.Window 和 WindowManager

    Window

    (1)整体上看,Window 是一个窗口的概念,是所有视图的载体,不管是 Activity、Dialog、Toast,它们的视图都是附属在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现

    (2)从源码来看,Window 是一个抽象类,它的唯一实现类是 PhoneWindow,每一个 Window 都对应着一个 View 和一个 ViewRootImpl,Window 和 View 通过 ViewRootImpl 来建立联系,因此实际上 Window 是以 View 的形式存在的

    (3)Activity 中的 DecorVIew,Dialog 中的 View 都是在 PhoneWindow 中创建的,因此 Window 实际上是 View 的直接管理者,例如在 View 事件分发机制中,在 Activity 里面收到点击事件后,会首先通过 window 将事件传递到 DecorView,最后再分发到我们的 View 上。Activity 的 SetContentView 在底层也是通过 Window 来完成的。还有 findViewById 也是调用的 window

    (4)Window 分三种类型

    • 应用 Window:对应一个 Activity
    • 子 Window:不能单独存在,需要附属在特定的父 Window 之中,如 Dialog
    • 系统 Window:需要声明权限在能创建的 Window,如 Toast

    .(5)Window 是分层的,层级大的会覆盖在层级小的 Window 上面,根据类型不同,层级范围不同

    • 应用 Window:1-99
    • 子 Window:1000-1999
    • 系统 Window:2000-2999

    WindowManager

    (1)WindowManager 主要用于管理 Window,可以实现的操作包括:

    • 创建一个 Window 并向其添加 View
    • 更新 Window 中的 View
    • 删除一个 Window,即删除其内的 View

    (2)以添加 Window 为例,通过调用 Manager 的 addView() 方法实现,这个方法包含两个参数

    • flags:表示 Window 的显示属性

      • FLAG_NOT_FOCUSABLE:表示 Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODEL,最终事件会直接传递给下层的具有焦点的 Window
      • FLAG_NOT_TOUCH_MODEL:表示当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理,一般需要开启此标记,否则其他 Window 将无法收到单击事件
      • FLAG_SHOW_WHEN_LOCKED:开启此模式可以让 Window 显示在锁屏的界面上
    • type:表示 Window 的类型,包含三种类型

      • 应用类 Window:对应一个 Activity
      • 子 Window:不能单独存在,需要附属在特定的父 Window 之中,如 Dialog
      • 系统 Window:需要声明权限在能创建的 Window,如 Toast
    val textView = TextView(this).apply {
        text = "window"
        textSize = 18f
        setTextColor(Color.BLACK)
        setBackgroundColor(Color.WHITE)
    }
    val parent = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT
    )
    parent.type = WindowManager.LayoutParams.TYPE_APPLICATION
    parent.flags =
        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    parent.gravity = Gravity.END or Gravity.BOTTOM
    parent.y = 500
    parent.x = 100
    windowManager.addView(textView, parent)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.Window 的内部机制

    2.1 Window 的添加过程

    (1)Window 的添加主要完成两个工作:绘制 View、完成 Window 添加,这一过程通过 WindowManager 的 addView() 方法实现

    (2)WindowManager 是一个接口,实现类是 WindowManagerImpl,因此实际调用的是 WindowManagerImpl 中的 addView() 方法

    (3)WindowManagerImpl 中的 addView() 方法内由 WindowManagerGlobal 的 addView() 来真正实现 Window 的添加

    @Override void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    
    • 1
    • 2
    • 3

    WindowManagerGlobal 的 addView 方法主要分为如下三步

    一、检查传入的参数是否合法,如果是子 Window 则还需要调整一些布局参数

    if (view == null) {
    	throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        //.... 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    二、创建 ViewRootImpl 并将 View 添加到列表中

    (1)WindowManagerGlobale 内部有如下几个列表:

    • mViews:存储所有 Window 所对应的 View
    • mRoots:存储所有 Window 所对应的 ViewRootImpl
    • mParams:存储所有 Window 所对应的布局参数
    • mDyingViews:存储被标记了即将被删除的 View 对象(Window)

    (2)addView() 方法内会根据检查后的参数创建 ViewRootImpl 并为 View 设置布局参数,最后将内容加入到上述的对应的列表中

    ViewRootImpl root;
    View panelParentView = null;
    
    root = new ViewRootImpl(view.getContext(), display);
    	
    view.setLayoutParams(wparams);
    	
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    三、通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

    (1)在 addView 方法中通过调用 ViewRootImpl 的 setView 方法来完成

    (2)setView 方法内部会完成以下任务:

    • 首先会通过 requestLayout 来完成异步刷新请求,其内的 scheduleTraversals 会进入 View 绘制的流程,从而完成页面的更新
    • 接着调用 WindowSession 来完成 Window 的添加过程,WindowSession 是一个 Binder 对象,实现类是 Session
    • Session 内部会通过 WindowManagerService 来实现 WIndow 的添加,所以可见 Window 的添加过程是一次 IPC 调用
    // WindowManagerGlobal.addView
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        if (index >= 0) {
        	removeViewLocked(index, true);
        }
        throw e;
    }
    
    
    // ViewRootImpl
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
    
                requestLayout();
    
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                }
                //.....
            }
        }
    }
    
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
     Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,outInsetsState);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    2.2 Window 的删除过程

    (1)WindowManager 中提供了两种删除接口

    • remoteView:异步删除,仅发送一个请求删除的消息后就立刻返回,不会等待完成删除操作
    • remoteViewImmediate:同步删除,等待删除操作完成后再返回

    (1)与添加过程类似,都是先通过 WindowManagerImpl 后,再进一步通过 WindowManagerGlobal 来实现的

    @Override
    public void removeView(View view) {
    	mGlobal.removeView(view, false);
    }
    
    • 1
    • 2
    • 3
    • 4

    (2)WindowManagerGlobal 的 removeView 中会完成以下工作

    • 调用 findViewLocked 来查找待删除的 View 的索引
    • 根据找到的索引调用 removeViewLocked 完成进一步的删除
    @UnsupportedAppUsage
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
    
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
    
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (3)removeViewLokced 内部会调用 ViewRootImpl 的 die 方法完成异步删除操作,即只发送一个请求删除的消息后就会立刻返回了,这个时候 View 并没有完成删除操作,最后会将其作为待删除 View 添加到 mDyingViews 中

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();
    
        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (4)die 方法内部会根据传入的参数判断是异步删除还是同步删除

    • 如果是异步删除,发送一个 MSG_DIE 消息,ViewRootImpl 中的 Handler 会处理此消息并调用 doDie 方法
    • 如果是同步删除,则不发消息直接调用 doDie 方法
    boolean die(boolean immediate) {
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
    
        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (5)在 doDie 方法内会调用 dispatchDetachedFromWIndow 方法,其内部完成了真正删除 View 的逻辑,主要做四件事

    • 垃圾回收相关的工作,比如清除数据和消息、移除回调
    • 通过 Session 的 remove 方法删除 Window,与添加相同,是一个 IPC 过程,最终会调用 WMS 的 removeWindow 方法
    • 调用 View 的 dispatchDetachedFromWindow 方法,其内部会调用 View 的 onDetachedFromWindow() 以及 onDetachedFromWindowInternal,当 View 从 Window 中移除时,会完成一些如终止动画、停止线程等资源回收工作
    • 调用 WindowManagerGlobal 的 doRemoteView 方法刷新数据,包括 mRoots、mParams、mDyingViews,需要将当前 Window 所关联的这三类对象从列表中删除
    2.3 Window 的更新过程

    也是一样调用 WindowManagerGlobal 的 updateViewLayout 方法,主要做如下工作

    (1)首先更新 View 的 LayoutParams

    (2)接着调用 findViewLocked 方法获取更新 View 的索引

    (3)根据 View 获取对应的 ViewRootImpl,更新其中的 LayoutParams 参数

    (4)通过 setLayoutParams 更新参数从而进一步对 View 重新绘制

    (5)最后通过 WMS 的 relayoutWindow 更新 Window 的视图

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
    	
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    	//将更新的参数设置到 view 中
        view.setLayoutParams(wparams);
    
        synchronized (mLock) {
            //获取到 view 在列表中的索引
            int index = findViewLocked(view, true);
            //拿到 view 对应的 ViewRootImpl
            ViewRootImpl root = mRoots.get(index);
            //从参数列表中移除旧的参数
            mParams.remove(index);
            //将新的参数添加到指定的位置中
            mParams.add(index, wparams);
            //调用 ViewRootImpl.setLayoutPrams 对参数进行更新
            root.setLayoutParams(wparams, false);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.Window 的创建过程

    3.1 Activity 的 Window 创建过程

    前置知识

    (1)首先明确 Activity、Window、DecorView 的关系:Activtiy 内包含一个 Window,Window 内部包含一个 DecorView,这个 DecorView 的 content 部分是 Activity 的视图,由 setContentView 方法设置

    (2)Activity 的启动过程在最终会调用 AcitvityThread 中的 performLaunchActivity() 方法,这个方法内部会

    • 通过类加载器创建 Activity 的实例对象

    • 调用 attach 方法为其关联运行过程中所依赖的一系列上下文环境变量

    • 此外,attach 方法中就会创建 Activity 所属的 Window 对象并为其设置回调接口,Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的

    • 由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态变化时会调用 Activity 的方法,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            if (activity != null) {
                
                appContext.setOuterContext(activity);
                
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
    
                //....
            }
        return activity;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (3)Activity 的 Window 创建只要完成两件事:Window 创建、将 Activity 的视图附属到 Window 上

    Window 创建

    (1)Activity 的 attach 方法中就会创建 Activity 所属的 Window 对象并为其设置回调接口

    (2)Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的

    (3)makeNewWindow 方法内会根据 context ,new 并返回一个 PhoneWindow 对象

    (4)由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接收到外界的状态变化时会调用 Activity 的方法,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等

    视图附属

    (1)由 Activity 的 setContentView 方法,而具体的实现交给 Window 来完成

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    
    • 1
    • 2
    • 3
    • 4

    (2)setContentView 大致分为如下三步

    一、如果没有 DecorView,则创建

    (1)DecorView 的创建过程由 installDecor 方法完成,其内部会通过 generateDecor 方法直接 new 并返回一个 DecorView

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
    
    • 1
    • 2
    • 3

    (2)创建完成后继续初始化 DecorView 的结构,这个过程由 PhoneWindow 的 generateLayout 完成

    • 通过 inflate 拿到 View
    • 调用 DecorView 的 addView 将 View 添加进去
    • 将当前 DecorView 设置为顶级 View
    • 根据 R.id.content 获取 contentParent,方便后续将 View 设置为 ContentView
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
    ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
    
    • 1
    • 2
    • 3
    • 4

    二、将 View 添加到 DecorView 的 mContentParent

    将 Activity 的视图添加到 DecorView 的 mContentParent 中

    mLayoutInflater.inflate(layoutResID, mContentParent);
    
    • 1

    三、回调 Activity 的 onContentChanged 方法通知 Activity 视图发生改变

    由于 Activity 实现了 Window 的 Callback 接口,当完成第二步时会回调 onContentChanged 方法通知 Activity 视图发生改变

    final Callback cb = getCallback();
    if(cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    
    • 1
    • 2
    • 3
    • 4
    3.2 Dialog 的 Window 创建过程

    与 Activity 的类似

    一、创建 PhoneWindow

    同样由 PhoneWindow 的 makeNewWindow 方法直接创建一个对象完成

    二、初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

    通过 Window 的 setContentView 实现

    三、将 DecorView 添加到 Window 中并显示

    在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中

    mWindowManager.addView(mDecor, 1);
    mShowing = true;
    
    • 1
    • 2

    当 Dialog 被关闭时,通过 WindowManager 来删除 DecorView,注意这里是同步删除

    mWindowManager.removeViewImmediate(mDecor);
    
    • 1
    3.3 Toast 的 Window 创建过程

    (1)Toast 是系统 Window,其内部视图有两种指定方法:默认样式、通过 setView 指定自定义 View,两种方法都对应一个 View 对象

    (2)Toast 提供 show、cancel 方法来显示、隐藏 Toast,其内部是一个 IPC 过程

    (3)Toast 的显示和隐藏需要通过 NotificationManagerService,NMS 来实现,NMS 会回调 Toast 里的 TN 接口

    (4)TN 是一个 Binder 类,在 Toast 和 NMS 进行 IPC 的过程中,当 NMS 处理 Toast 的显示或隐藏请求时会跨进程回调 TN 中的方法,此时由于 TN 运行在 Binder 线程池中,所以需要通过 Handler 切换到当前线程,即发送 Toast 请求的线程

    (5)Toast 通过 WindowManager 将 view 直接添加到 Window 中,没有创建 PhoneWindow 和 DecorView,和 Activity、Dialog 不同

    public void show() {
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;
        final int displayId = mContext.getDisplayId();
        
        try {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                if (mNextView != null) {
                    service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
                } else {
                    // ...
                }
            }
        }
        //....
    }
    public void cancel() {
        mTN.hide();
        try {
            getService().cancelToast(mContext.getOpPackageName(), mToken);
        } catch (RemoteException e) {
            // Empty
        }
        //....
    }
    
    static private INotificationManager getService() {
    	if (sService != null) {
        	return sService;
        }
        sService = INotificationManager.Stub.asInterface(
        	ServiceManager.getService(Context.NOTIFICATION_SERVICE));
        return sService;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Toast 显示和隐藏

    (1)show 方法内会调用 enqueueToast 方法,这个方法需要三个参数

    • 当前应用包名
    • TN 远程回调对象
    • Toast 的时长
    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    final int displayId = mContext.getDisplayId();
        
    try {
    	if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
        	if (mNextView != null) {
            	service.enqueueToast(pkg, mToken, tn, mDuration, displayId);
            } else {
                // ...
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (2)enqueueToast 方法会完成如下工作

    • 首先将 Toast 请求封装成 ToastRecord,并加入 mToastQueue 队列中
      • 这个队列最多能存放 50 个 Record
      • 如果队列中有,则更新
    • 添加完成后,NMS 会通过 showNextToastLocked 方法来显示当前的 Toast
    public void enqueueToast(String pkg, IBinder token, ITransientNotification callback,
            int duration, int displayId) {
        enqueueToast(pkg, token, null, callback, duration, displayId, null);
    }
    
    private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, int displayId,
    @Nullable ITransientNotificationCallback textCallback) {
        synchronized (mToastQueue) {
            int callingPid = Binder.getCallingPid();
            final long callingId = Binder.clearCallingIdentity();
            try {
                ToastRecord record;
                int index = indexOfToastLocked(pkg, token);
    			//如果队列中有,就更新它,而不是重新排在末尾
                if (index >= 0) {
                    record = mToastQueue.get(index);
                    record.update(duration);
                } else {
                    int count = 0;
                    final int N = mToastQueue.size();
                    for (int i = 0; i < N; i++) {
                        final ToastRecord r = mToastQueue.get(i);
                        //对于同一个应用,taost 不能超过 50 个
                        if (r.pkg.equals(pkg)) {
                            count++;
                            if (count >= MAX_PACKAGE_TOASTS) {
                                Slog.e(TAG, "Package has already queued " + count
                                       + " toasts. Not showing more. Package=" + pkg);
                                return;
                            }
                        }
                    }
    
                    //创建对应的 ToastRecord
                    record = getToastRecord(callingUid, callingPid, pkg, 
                    	isSystemToast, token,text, callback, duration, windowToken, displayId, textCallback);
                    mToastQueue.add(record);
                    index = mToastQueue.size() - 1;
                    keepProcessAliveForToastIfNeededLocked(callingPid);
                }
                // ==0 表示只有一个 toast了,直接显示,否则就是还有toast,真在进行显示
                if (index == 0) {
                    showNextToastLocked(false);
                }
            } finally {
                Binder.restoreCallingIdentity(callingId);
            }
        }
    }
    
    private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
    	IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback,
        int duration, Binder windowToken, int displayId,
        @Nullable ITransientNotificationCallback textCallback) {
    	if (callback == null) {
        	return new TextToastRecord(this, mStatusBar, uid, pid, packageName,
            	isSystemToast, token, text, duration, windowToken, displayId, textCallback);
        } else {
            return new CustomToastRecord(this, uid, pid, packageName,
            	isSystemToast, token, callback, duration, windowToken, displayId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    (3)showNextToastLocked 方法内,Toast 的显示是通过 ToastRecord 的 callback 完成的,这个 callback 实际上是 TN 的远程 Binder

    (4)为了将执行环境切换到 Toast 请求所在线程,其内部使用 Handler,TN 会调用 handleShow 方法将 Toast 视图添加到 Window 中

    mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    mWM.addView(mView, mParams);
    
    • 1
    • 2

    (5)显示以后,NMS 还会通过 scheduleTimeoutLocked 方法来发送一个延时消息,时长为之前的参数:LONG(3.5S) / SHORT(2S)

    private void scheduleTimeoutLocked(ToastRecord r) {
    	mHandler.removeCallbacksAndMessage(r);
    	Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    	long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    	mHandler.sendMessageDelayed(m, delay);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (6)延迟相应的时间后,NMS 会通过 cancelToastLocked 方法隐藏 Toast 并移除出 mToastQueue

    (7)这里也是通过 ToastRecord 的 callback 完成,也是调用 TN 中的 handleHide 方法将 Toast 的视图从 Window 中移除

    if(mView.getParent() != null) {
        ...
        mWM.removeView(mView);
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    【笔试实战】蓝桥官网在线刷题100题计划【第一轮】
    传输层协议 --TCP报文格式详细介绍
    PMP 项目质量管理
    学会使用这些电脑技巧,可以让你在工作中受益无穷
    用PHP实现极验验证功能
    172.阶乘后的零 | 793.阶乘函数后k个零
    第八章 接口
    混淆技术研究-OLLVM混淆-虚假控制流(BCF)
    C++类的构造:简单的complex搭建
    vue创建色带组件。
  • 原文地址:https://blog.csdn.net/scottb/article/details/126478195