0
点赞
收藏
分享

微信扫一扫

Android Window 内部机制

Gascognya 2021-09-29 阅读 71
Android

再看此篇文章前建议先看理解Window和WindowManager关系。

前言

我们知道Window是一个抽象类,真正用于展示的是View类,每个View对应ViewRootImpl类,ViewRootImpl职责非常多。WindowManager提供了3个方法添加、更新、删除都是针对View,说明Window的存在靠View来实现,对WIndow的访问需使用WIndowManager,接下来我们开始分析这三个方法具体实现。

2.1 Window 添加过程

当我们要显示一个Window时候,需要在WindowManager添加一个View,我们通过WindowManagerImpl类的addView来完成。首先我们看下WindowManagerImpl的添加源码,如下:

    //1
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    //2
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

在注释1处通过单例的方式获取WindowManagerGlobal对象。注释2处WindowManagerImpl的addView方法添加View但并没有做事情,而是把添加的过程委托给WindowManagerGlobal实现。我们接着看注释1处获取单例的源码,如下。

  public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

这是一个典型的单例模式,一个进程只有一个WindowManagerGlobal对象。接下来我们继续分析WindowManagerGlobal类的具体的添加View过程。

    //1
    @UnsupportedAppUsage
    private final ArrayList<View> mViews = new ArrayList<View>();
    //2 
    @UnsupportedAppUsage
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //Window对应的布局参数
    @UnsupportedAppUsage
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    //4
    private final ArraySet<View> mDyingViews = new ArraySet<View>();

注释1处mView存储Window对应的View。注释2处mRoots存储Window对应的ViewRootImpl。注释3处mParams存储Window对应布局参数。注释4处存储那些正在被删除的View对象,调用了removeView方法,但是删除操作还未完成的Window对象。接下来我们继续分析addView源码,如下:

       public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        //1   
        (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } 

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            //2 
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            //3
            mViews.add(view);
            //4
            mRoots.add(root);
            //5
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //6 
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
            }
        }
    }

注释1处如果是子窗口,都会根据父窗口的LayoutParams类的wparams对象来调整子窗口的wparams。给应用程序Window和子Window的LayoutParams添加token,前者token都是Activity的token,后者token都是宿主窗口token。注释2处创建ViewRootImpl对象赋值给root。注释3处将窗口view添加到mView中。注释4处将root添加到mRoots中。注释5处将窗口参数添加到mParams中。注释6处将窗口和窗口参数传递到RootView中。
ViewRootImpl在其实做了相当多的事情,如下:

  1. View的树根和管理View树。
  2. 触发View测绘、布局、绘制。
  3. 输入事件的中转站。
  4. 管理surface。
  5. 负责与AMS通讯。

ViewRootImpl的职责非常多,现在继续分析ViewRootImpl的setView方法,在此方法里面会调用requestLayout方法来布局。

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

然后会调用scheduleTraversals方法来执行View测绘、布局、绘制,具体过程会在我下一篇文章ViewRootImpl理解源码分析。我们接着看setView源码,如下:

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
        requestLayout();
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                mTempInsets);
          }
    }

通过调用mWindowSession的addDisplay方法实现了Window添加的过程,mWindowSession是IWindowSession类对象,也是binder类,真正的实现是Session类。

2.2Window 更新过程

要了解WindowManager的更新过程,接着我们继续看WindowManageImpl的updateViewLayout方法,代码如下:

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

WindowManageImpl的updateViewLayout实际没有做什么事情,具体的实现都委托给了WindowManagerGlobal来实现,那我都继续往下面走。

 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        //1 
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            //2 
            int index = findViewLocked(view, true);
            //3 
            ViewRootImpl root = mRoots.get(index);
            //4 
            mParams.remove(index);
            mParams.add(index, wparams);
            //5 
            root.setLayoutParams(wparams, false);
        }
    }

注释1处对View赋值新的布局属性。注释2处在mViews数组遍历view对象的索引位置。注释3处根据索引找出对应窗体View的ViewRootImpl对象。注释4处移出之前之前窗体布局参数,添加新的布局参数。注释5处重新设置新的布局参数。我们继续往下分析ViewRootImpl的setLayoutParams具体做了些什么事情,代码如下:

   void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
        synchronized (this) {
            scheduleTraversals();
        }
    }

从代码看出此方法的实现很简单,调用scheduleTraversals方法来执行View测绘、布局、绘制,然后调用了IWindowSession的relayout方法,最后又调用了WindowManagerService的relayoutWindow方法实现了Window的更新,这两步过程进行了跨进程调用。

2.3 Window删除过程

我们先看下WindowManager的remove方法,源码如下:

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

WindowManageImpl的removeView方法实际没有做什么事情,具体的实现都委托给了WindowManagerGlobal来实现,那我都继续往下面走。

    public void removeView(View view, boolean immediate) {
        synchronized (mLock) {
            //1
            int index = findViewLocked(view, true);    
            //2
            View curView = mRoots.get(index).getView();
            //3
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
        }
    }

注释1处在mViews数组找到对应窗体view的索引。注释2处找到对应索引的ViewRootImpl的对象拿出对应窗体View来。注释3处removeViewLocked方法根据索引移出窗体view。继续分析removeViewLocked方法的具体实现方式,源码如下:

     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) {
                //1 
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        //2 
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                //3 
                mDyingViews.add(view);
            }
        }
    }

注释1处结束view的输入相关逻辑。注释2处执行相应的删除操作,immediate表示是否立即删除。注释3处存储待删除的view,立刻删除不会存入此数组中。我们接着分析注释2处ViewRoot的die源码,如下:

   boolean die(boolean immediate) {
        //1 
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }
        //2 
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

注释1处立即执行删除操作。注释2处发送消息执行删除操作。继续看doDie方法做了些什么事情,代码如下:

  void doDie() {
        //1 
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                //2 
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    destroySurface();
                }
            }

            mAdded = false;
        }
        //3 
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

注释1调用checkThread方法检测是否在主线程操作。注释2处移出view,移出监听回调,移出绘制消息,数据重置,最终会调用onDetachedFromWindow方法,可以继承view的此方法拿到回调 ,会调用mWindowSession的remove方法移出window对象,是一个IPC过程。注释3处调用WindowManagerGlobal的doRemoveView方法删除具体的存在的mView、mRoot、mParams数组数据。

举报

相关推荐

0 条评论