Window和ViewRootImpl

Window代表窗口的概念,在Android里面我们看到的页面几乎都跟Window有关。为了对它有些概念,我们就从添加一个Window开始吧:

private void addWindow() {
        //1:先获取WindowManager,这是一个Interface,它的实现是WindowManagerImpl
        //WindowManager继承了ViewManager,ViewManager只有三个方法:addView、updateViewLayout、removeView
        //感觉好熟悉啊!
        WindowManager windowManager = getWindowManager();

        FrameLayout frameLayout = new FrameLayout(this);
        frameLayout.setBackgroundColor(Color.parseColor("#ff0000"));

        //添加View的时候需要设置LayoutParams,具体的我们之后再讲
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_APPLICATION);
        params.height = 100;
        params.width = 100;
        params.format = PixelFormat.RGBA_8888;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.gravity = Gravity.CENTER;

        //调用WindowManager的添加View的方法
        windowManager.addView(frameLayout, params);
}

接下来我们就可以直接去看WindowManagerImpladdView方法的实现了:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

WindowManagerImpl其实啥也没干,直接把工作交给了mGlobal,这个mGlobalWindowManagerGlobal的实例,WindowManagerGlobal对外提供一个单例。在WindowManagerGlobal里面,addView的实现主要分为以下几步:

public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        //1、检查一些参数是不是合法
        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");
        }

        //2、布局参数也需要一些调整,最重要的的是token(这是一个省份证,主要用于在跨
        // 进程的时候表明window的身份,这是一个很重要的参数,后面会讲)的赋值
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            
            ........
            
            //3、同一个View不能添加到两个Window里面,
            //如果当前View已经添加到Window里面,假如正在移除,里面调用移除操作
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            //WindowManager.LayoutParams的成员变量type表示的是Window的类别
            //Android里面一共有三种Window,分别是应用Window,子window和系统Window
            //每个Window都有对应的z-ordered,层级大的会覆盖在层级小的上
            //应用Window的层级范围是1-99,一般对应的是一个Activity
            //子Window的层级范围是1000-1999,它不能单独存在,需要嵌套在其他Window(不能是子Window)里面
            //系统window的层级范围是2000-2999,系统Window需要声明权限
            //4、如果是子Window,那么就需要找到他的父View了
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            //5、创建ViewRootImpl对象,这个类是View的层次结构的最高层,负责把View和WindowMAnager联系起来
            //WindowManagerGlobal的内部的大多数实现都是由ViewRootImpl来完成的
            root = new ViewRootImpl(view.getContext(), display);

            //给View设置布局参数
            view.setLayoutParams(wparams);

            //mViews是WindowManagerGlobal的成员变量,保存了所有添加到Window的View
            //由于WindowManagerGlobal是单例形式,这个所有代表的是整个进程
            mViews.add(view);
            //mRoots保存了所有的对应ViewRoot对象
            mRoots.add(root);
            //mParams保存了所有的对应的布局参数
            mParams.add(wparams);
            //WindowManagerGlobal还有一个比较重要的成员变量mDyingViews,保存了所有正在被移除的View

            // do this last because it fires off messages to start doing things
            try {
                //6、最后调用ViewRoot的setView方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

经过上面的123456步,就把实现交给了ViewRootImpl来实现了,我移除了一些代码后,它的执行如下:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ........
                
                //1、在把Window加到WindowManager前先执行一次布局,确保可以接受到系统事件前relayout
                //这是一个很重要的方法,下面会详细解析
                requestLayout();
                
                .....
                
                try {
                    //mWindowSession的实现类是Session,这个类负责连接ViewRootImpl和WindowManagerService,并且
                    //在WindowManagerService里每个进程只有一个Session对象,这是一个跨进程请求
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                ......
            }
        }
    }

这里需要简单先看1:requestLayout方法的执行:

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //执行线程检测,如果我们在子线程操作UI,这里就会抛出异常
            checkThread();
            mLayoutRequested = true;
            //继续执行
            scheduleTraversals();
        }
 }
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //卡片阻断,位于这个卡片之后的同步消息将不会执行,知道这个卡片被移除,异步消息正常执行
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //这个类其实有点像Handler,这里也可以把它当做Handler来理解,mTraversalRunnable里面只做了一件事doTraversal();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
 }
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除卡片,这里主要是为了确保doTraversal先执行,防止被前面的消息堵塞
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            //继续执行
            performTraversals();
        }
 }

performTraversals是一个巨长巨长的方法,但是我们可以从哪里找到几个有意思的执行:

private void performTraversals() {
            
        //重新布局,这里是跨进程,最终是交给WindowManagerService,给以调整Window的状态
        relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
        
        //执行测量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

        //执行layout方法
        performLayout(lp, mWidth, mHeight);
        
        //执行draw
        performDraw();
}

到这里关于View的展示就基本结束了,接下来我们就要继续去关注对Window的管理了。 mWindowSession的addToDisplay方法,里面执行很简单,调用WindowManagerService的addWindow方法,接下来就需要去看看这个方法的具体实现了:

public int addWindow(Session session, IWindow client, int seq,
                         WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
                         Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                         InputChannel outInputChannel) {
        int[] appOp = new int[1];
        //权限检查,对于系统级别的window需要我们手动添加权限,app级别和子window一般都自带权限
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        ......

        //window类别,有application window、有sub window、有system window
        final int type = attrs.type;

        //使用锁机制,由此看来,如果多进程同时操作addWindow会按照到达的先后顺序去执行
        //mWindowMap持有了所有添加的window
        synchronized(mWindowMap) {

            //获取手机屏幕(展示window的屏幕),如果没有就返回失败
            final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
            if (displayContent == null) {
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            //检查当前应用(确切来说是进程)是否可以在屏幕上显示window
            if (!displayContent.hasAccess(session.mUid)
                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            //判断该window是否已被加过,一个Window不能重复添加
            if (mWindowMap.containsKey(client.asBinder())) {
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            //sub window需要一个父window parentWindow
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                //查询当前的父window是否已经添加,从mWindowMap里面读取
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                //从这里看就能知道, sub window不能嵌套
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            AppWindowToken atoken = null;
            final boolean hasParent = parentWindow != null;
            // Use existing parent window token for child windows since they go in the same token
            // as there parent window so we can apply the same policy on them.
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // If this is a child window, we want to apply the same type checking rules as the
            // parent window type.
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

            boolean addToastWindowRequiresToken = false;

            if (token == null) {
                //应用进程需要token来表明window的身份,从这里可以看出,dialog不能传Application 的context
                if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                //输入键盘window
                if (rootType == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_TOAST) {
                    //应用在版本大于25的时候,不再允许随便添加Toast
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                //系统window会自动创建token不需要
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                //应用token也是AppWindowToken
                atoken = token.asAppWindowToken();
                if (atoken == null) {
                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
                            + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                            + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
            }......继续一系列判断

            //这个可以代表一个window
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            //跨进程通信,一般都会添加死亡监听
            if (win.mDeathRecipient == null) {
                // Client has apparently died, so there is no reason to
                // continue.
                Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                        + " that is dead, aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

           .....

            //开始添加到页面, 准备绘制的surface
            win.attach();
            //将添加的window保存起来
            mWindowMap.put(client.asBinder(), win);

            boolean imMayMove = true;

            //token也保存一份window
            win.mToken.addWindow(win);

            ........
            
            //对View的层次进行管理
            displayContent.assignWindowLayers(false /* setLayoutNeeded */);
            
        }

        return res;
    }

这样,Window的添加过程基本就是这么一个过程,当然里面还有一些Window怎么去具体的层次管理,以及怎么去绘制到Surface,请原谅小弟才疏学浅,可能以后会写吧。
removeView,updateViewLayout,操作都差不多,就不再赘言了。
总结:
1、Window是一个虚化的概念,真正操作的对象还是View
2、View需要依附于Window去展示
3、ViewRootImpl负责把View和WindowManager联系起来
4、WindowManager负责Window的管理

你可能感兴趣的:(Window和ViewRootImpl)