WMS简单分析

WMS

带着问题看源码

token的作用?

WMS在移除一个window的时候会移除它的子window嘛?

我们以一个dialog为例

  public void show() {
        //省略、、
        //mWindowManager是在前面初始化的 还是进行的一个addview的方式

        mWindowManager.addView(mDecor, l);
        if (restoreSoftInputMode) {
            l.softInputMode &=
                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        }

        mShowing = true;

        sendShowMessage();
    }
WindowManagerImpl的addView方式
 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
在 WindowManagerGlobal 里的addView方式 

前面有提到 WindowManagerGlobal的作用 在这里不在重复贴了

WMS的小问题_一只刘小彤!的博客-CSDN博客

那么 继续看

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        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 {
            // 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) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            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.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            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);
                    }
                }
            }
            
            //在这里建立 ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //然后setView 看下ViewRootImpl 的构造函数和 setView 
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

看下ViewRootImpl的构造函数

   public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }

其实这里面就获得了WMS的对象,老规矩,一个跨进程的服务

看下ViewRootImpl setView方法

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
                   
、、、、、
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    //看下 mWindowSession 的 addToDisplayAsUser
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                
    }

然后看下Seccion里的 

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
    }

实际上调用的是WMS里的 addWindow

接下来先介绍WMS里的几个基础类

DisplayContent

用来管理一个逻辑屏上的所有窗口,有几个屏幕就会有几个DisplayContent。使用displayId来区分。

处于不同DisplayContent的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合。 因此,就这几个方面来说,DisplayContent就像一个孤岛,所有这些操作都可以在其内部独立执行。

1.DisplayContent的子容器是其内部类DisplayChildWindowContainer

2.DisplayContent内部使用:IBinder为key,WindowToken为value的键值对保存在HashMap中

3.DisplayContent是在Window添加到WMS的时候初始化的:

WMS

mRoot是RootWindowContainer类型的对象,看名字就知道其是窗口容器的根。 说明在Window体系中,RootWindowContainer节点是容器最顶端的父容器。

其继承了WindowContainer,这里的DisplayContent是一个泛型声明,表示其子容器的类型是DisplayContent, 在getDisplayContent方法中也可知,其mChildren列表是DisplayContent的集合。这也变相的说明DisplayContent也是一个容器

class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener {

RootWindowContainer 继承了 WindowContainer mChildren 是一个 DisplayContent 集合

    protected final WindowList<E> mChildren = new WindowList<E>();

WindowToken:

类声明:

class WindowToken extends WindowContainer<WindowState>

表明WindowToken也是子容器,其子容器是WindowState,所以WindowState也是一个容器。

WindowToken在窗口体系中有两个作用

  • 1.应用组件标识:将属于同一个应用组件的窗口组织在一起,这里的应用组件可以是:Activity、InputMethod、Wallpaper以及Dream。 WMS在对窗口的管理过程中:用WindowToken来指代一个应用组件。例如在进行窗口的Z-Order排序时,属于同一个WindowToken的窗口会被安排在一起。
  • 2.令牌作用:WindowToken由应用组件或其管理者负责向WMS声明并持有,应用组件在需要更新窗口时,需要向WMS提供令牌表明自己的身份, 并且窗口的类型必须与所持有的WindowToken的类型一致。

但是系统窗口是个例外,并不需要提供token,WMS会隐式声明一个WindowToken

那是不是说谁都可以添加系统窗口了呢?非也,在addWindow开始处就会调用下面代码:

mPolicy.checkAddPermission()

它要求客户端必须拥有SYSTEM_ALERT_WINDOW或INTERNAL_SYSTEM_WINDOW权限才能创建系统类型的窗口。

WindowState:

表明WindowState也是一个WindowContainer容器,但是其子容器也是WindowState,一般窗口有子窗口SUB_WINDOW的情况下,WindowState才有子容器节点。

WindowState在WMS中表示一个Window窗口状态属性,其内部保存了一个Window所有的属性信息。

其与View以及WindowToken关系如下:

 

如何查看当前设备Window窗口状态命令?

adb shell dumpsys window windows

 接下来分析下addWindow的流程 

  public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        //首先先进行鉴权
        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                appOp);

     
        synchronized (mGlobalLock) {
           
            //在这里创建 displayContent 
            final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

            if (displayContent == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "
                        + "not exist: %d. Aborting.", displayId);
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            //如果不是特殊类型的 窗口
            //之前在一个项目里Activity给的也是这个类型的窗口,不给token就会抛异常
            //还有个项目 因为父布局是个window,如果不给token也会抛异常
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                //就必须有父窗口,没有则抛异常
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

        
            // 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.
            //如果前面都没问题,就会在这进行token获取,如果没有则新建,并且将它加入到displayContent 里
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
           
            if (token == null) {
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
            } 
            
            在这新建一个 WindowState 并将它加入 token
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
            //根据mPolicy调整window的attr属性,mPolicy的实现类是PhoneManagerPolicy。
            displayPolicy.adjustWindowParamsLw(win, win.mAttrs, callingPid, callingUid);

            //执行WindowState的openInputChannel,这里主要是打通和Input系统的通道,用于接收IMS的输入事件请求。
            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }

           
            win.attach();
            //将客户端app层的Window对象和WindowState关联上,这样WMS就可以通过Window找到WMS中的WindowState对象、
            mWindowMap.put(client.asBinder(), win);
            win.initAppOpsState();
            //win.mToken是前面创建的WindowToken对象,所以此处就是将WindowState加入到WindowToken的子容器集合中。
            win.mToken.addWindow(win);
            displayPolicy.addWindowLw(win, attrs);
         
    }

 结构关系如图所示

那么add既然是这个流程,那大胆猜测,移除掉一个window会移除掉它所有的子window(因为可以拿到这个数据)我们看下移除是否是这样

    void removeWindow(Session session, IWindow client) {
        synchronized (mGlobalLock) {
            //首先先拿到这个WindowState (也就是window)
            WindowState win = windowForClientLocked(session, client, false);
            if (win != null) {
                win.removeIfPossible();
                return;
            }

            // Remove embedded window map if the token belongs to an embedded window
            mEmbeddedWindowController.remove(client);
        }
    }
   @Override
    void removeIfPossible() {
        super.removeIfPossible();
        removeIfPossible(false /*keepVisibleDeadWindow*/);
        immediatelyNotifyBlastSync();
    }

看下父类这个方法 果然,移除一个window它会把自己所有子window移除掉


    /**
     * Removes this window container and its children taking care not to remove them during a
     * critical stage in the system. For example, some containers will not be removed during
     * animation if this method is called.
     */
    // TODO: figure-out implementation that works best for this.
    // E.g. when do we remove from parent list? maybe not...
    void removeIfPossible() {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer wc = mChildren.get(i);
            wc.removeIfPossible();
        }
    }

继续分析WMS的一些知识

比如动画

我们知道在Android内部有两种动画:Window切换移动动画以及app层的View的动画, 动画操作的是View而Window切换操作的是Surface,对不同层级的SurfaceControl进行操纵,会产生不同的动画效果, 注意区分。

我们这里涉及到的是Window切换移动动画

但是不管是View的动画还是Window切换操作,对底层屏幕刷新来说都是针对不同帧动画来说,所以会涉及到VSync同步信号相关知识。

WindowStateAnimator

 WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, int showUserId,
            boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
        super(service);
        。。。

        mWinAnimator = new WindowStateAnimator(this);
        mWinAnimator.mAlpha = a.alpha;
        。。。
    }

看方法说明,这个类还是用于WMS中的窗口动画以及Surface操作的单例工具类,WMS将动画的工作都委托他来处理。其在WMS构造的时候创建了实例。

 final WindowSurfacePlacer mWindowPlacerLocked;

这个对象是用于Surface的摆放的操作,说明WindowAnimator还支持Surface的各种操作

final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());

WindowSurfacePlacer对象,这个对象是用于Surface的摆放的操作,说明WindowAnimator还支持Surface的各种操作 注释2处使用AnimationThread线程进行Window的动画操作,AnimationThread内部使用的是HandlerThread机制,说明其内部也创建了一个异步消息处理机制

Choreographer设置一个回调,在Choreographer接收到 VSync信号时,在doFrame中触发这个回调,一般是用来监听帧率等操作

而这里是在接收到doFrame的时候回调的是一个animate(frameTimeNs)动画处理的方法。animate函数执行流程很长,包括更新壁纸、转屏动画等逻辑均包含在其中

那么mAnimationFrameCallback回调是什么时候发送到Choreographer中去的呢?

WindowAnimator的scheduleAnimation方法: 当我们需要一个动画就会进行这个post

   void scheduleAnimation() {
        if (!mAnimationFrameCallbackScheduled) {
            mAnimationFrameCallbackScheduled = true;
            mChoreographer.postFrameCallback(mAnimationFrameCallback);
        }
    }
void updateWindowsForAnimator(WindowAnimator animator) {
    mTmpWindowAnimator = animator;
    forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
}
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {

```
...
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
    final DisplayChildWindowContainer child = mChildren.get(i);
    if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
        // In this case the Ime windows will be processed above their target so we skip
        // here.
        continue;
    }
    if (child.forAllWindows(callback, traverseTopToBottom)) {
        return true;
    }
}
...
return false;
```

}

forAllWindows方法会遍历整个容器树都去调用mUpdateWindowsForAnimator回调。 这个回调内部就会去执行winAnimator.stepAnimationLocked去更新Window的更新操作。 stepAnimationLocked,代表单步动画。这里面的操作大家自行查看也不难、

这里对动画做个小结通过在需要动画的时候,post一个FrameCallBack给Choreographer,在VSync信号到来的时候,会优先执行动画操作。动画回调内部会去遍历整个容器树模型,依次更改每个Window对应的Surface的状态。然后在绘制完成后,提交给SurfaceFlinger

Surface管理:

WMS负责创建Surface以及对Surface的摆放工作,之后将Surface提交给SurfaceFlinger进行合并。 在App层也创建了一个Surface对象,但是那个是空对象,用于WMS的填充

Surface的创建:

Surface的创建在WMS中使用WindowStateAnimator代理创建,而WindowStateAnimator中又创建了一个WindowSurfaceController对Surface进行管理。

    private WindowSurfaceController mPendingDestroySurface;

什么时候发起 Surface绘制的?看下ViewRootImpl

 private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        此时 mSurfaceControl还是空的 等待WMS填充
        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
       
        return relayoutResult;
    }
        result = createSurfaceControl(outSurfaceControl, outBLASTSurfaceControl,
                            result, win, winAnimator);

getSurface方法将WMS中创建的WindowSurfaceController中SurfaceControl对象的mNativeObject对象传递给新的Surface

那么为什么谷歌要绕这么大圈来创建Surface呢?直接在App层去创建不就可以了么?

个人见解谷歌是希望统一管理Surface而不是单独让某个应用持有,且Surface的摆放操作等都是得由WMS进行处理,所以就直接让WMS去创建,然后返回给App层去绘制Surface操作。

WindowSurfacePlacer

WMS在构造的时候就创建了WindowSurfacePlacer对象。这个对象主要用来给Surface进行位置的定位、

定位到WindowSurfacePlacer的performSurfacePlacement方法,这个方法可以说是WMS最核心的方法,其负责了所有窗口的摆放工作:如何显示?显示在屏幕什么位置?区域大小等。 这些将在确认后,下发给SurfaceFlinger进行处理。

WMS中任何窗口状态发生改变都会触发该方法,整个方法进行容器树的遍历,确认窗口可见性等。

 final void performSurfacePlacement() {
        performSurfacePlacement(false /* force */);
    }

    final void performSurfacePlacement(boolean force) {
        if (mDeferDepth > 0 && !force) {
            mDeferredRequests++;
            return;
        }
        int loopCount = 6;
        do {
            mTraversalScheduled = false;
            performSurfacePlacementLoop();
            mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
            loopCount--;
        } while (mTraversalScheduled && loopCount > 0);
        mService.mRoot.mWallpaperActionPending = false;
    }

最终在showSurfaceRobustlyLocked中调mSurfaceController.showRobustlyInTransaction()方法进行Surface的提交给SurfaceFlinger进行合成并显示在屏幕上

点击事件的处理

InputChannel

    public int addWindow(InputChannel outInputChannel,) {

addWindow 时候会把 App层的 outInput添加到WMS

WindowState里会进行

  void openInputChannel(InputChannel outInputChannel) {
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        String name = getName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);

        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
registerInputChannel传入的是server端InputChannel给IMS。 
        mWmService.mInputManager.registerInputChannel(mInputChannel);
        mInputWindowHandle.token = mInputChannel.getToken();
        if (outInputChannel != null) {
将client端的InputChannel与app端传入的outInputChannel关联起来了。
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
        } else {
    private native void nativeTransferTo(InputChannel other);

接下来就是底层

android_view_InputChannel.cpp:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env...) {
    ...
    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
23  status_t InputChannel::openInputChannelPair(const std::string& name,
224          sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
225      int sockets[2];
226      if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
227          status_t result = -errno;
228          ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",
229                  name.c_str(), errno);
230          outServerChannel.clear();
231          outClientChannel.clear();
232          return result;
233      }
234  
235      int bufferSize = SOCKET_BUFFER_SIZE;
236      setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
237      setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
238      setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
239      setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
240  
241      std::string serverChannelName = name;
242      serverChannelName += " (server)";
243      outServerChannel = new InputChannel(serverChannelName, sockets[0]);
244  
245      std::string clientChannelName = name;
246      clientChannelName += " (client)";
247      outClientChannel = new InputChannel(clientChannelName, sockets[1]);
248      return OK;
249  }

通过以上代码可以看出InputChannel使用的是sockets通讯

这样服务端在InputChannel就可以写入input事件,然后在app端的InputChannel就可以接受到数据了。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值