Android Framework-理解 Window & WindowManager

一、概论

    Window 是一个窗口的概念,是所有视图的载体,不管是 Activity,Dialog,还是 Toast,他们的视图都是附加在 Window 上面的。例如在桌面显示一个悬浮窗,就需要用到 Window 来实现。WindowManager 是访问 Window 的入口。

  • Window 是一个抽象类,他的实现类是 PhoneWidow
  • Activity 中的 DecorView ,Dialog 中的 View 都是在 PhoneWindow 中创建的

因此 Window 实际是 View 的直接管理者

1.1 如何新增一个window
 /**
     * 添加新窗口
     */
    fun addNewWindow() {
        val mTextView = TextView(this).apply {
            text = "window"
            textSize = 20f
            setTextColor(Color.WHITE)
            setBackgroundColor(Color.RED)
        }
        val mParent = WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            0, 0, PixelFormat.TRANSPARENT
        )
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mParent.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            mParent.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        }
        mParent.flags =
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        mParent.x = 250
        mParent.y = 450
        windowManager.addView(mTextView, mParent)
    }

以上代码就是往页面新增一个系统window,效果图如下:

需要注意的几点是:

  • 在Android O (13)上需要改对应的 TYPE
  • 在Android O (13)上需要给 悬浮窗 权限
  • 需要在清单文件中加上这句权限:
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    上面代码中 Type 和 Flags 的属性比较重要,这里把它列出来

Type 窗口属性
Window 类型 层级范围 说明
应用 Window 1 ~ 99 对应着一个 Activity
子 Window 1000 ~ 1999 不能单独存在,需要附属在特定的 Window 之中,例如常见的 PopupDialog,就是子 Window。
系统 Window 2000 ~ 2999 需要声明权限才能创建的 Window,,例如 Toast 和 系统状态栏这些都是系统的 Window
  • 子 Window 无法单独存在,必须依赖父级 Window,例如 PopWindow 必须依赖 Activity
  • Window 分层,在显示时层级高的会覆盖层级低的窗口
Flags窗口的标志

Flags 表示 Window 的属性,它有多选项,通过这些可以通知 Window 显示的特性,例如:

Flags 特性
FLAG_NOT_FOCUSABLE 表示 Window 不需要获取焦点,也不需要各种输入事件,此标记通同时启用 FLAG_NOT_TOUCH_MODAL最终事件会直接传递给下层具有焦点的 Window。
FLAG_NOT_TOUCH_MODAL 将 Window 区域以外的单击事件传递给底层的 Window,当前 Window 内的单击事件自己处理,一般都要开启此事件,否则其他 Window 无法收到单击事件
FLAG_SHOW_WHEN_LOCKED 可以将 Window 显示在锁屏的界面上
FLAG_TURN_SCREEN_ON Window 显示时将屏幕点亮

二、源码流程解析

    上面demo的最后一句代码中通过 windowManager 在屏幕上添加了一个系统级别的 Window,通过 windowManager 的addView 方法。那我们从这里跟进去看源码,看看具体的添加流程是怎样的。

2.1 WindowManager & ViewManager
#WindowManager 
@[SystemService]
public interface WindowManager extends ViewManager

--------------------------------------------------------------------------------------

#ViewManager
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

可以看到 Windowmanger 其实是个接口,其中 (addView,updateViewLayout,removeView) 更新方法其实是继承自ViewManager。而真正实现 WindowManger 这3个方法的实现类是 WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {
    
    //·····

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

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

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

上面代码看到,真正实现操作的是 mGlobal 这个对象:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
2.2 WindowManagerGlobal.addView

可以看到 WindowManagerImpl 并没有直接实现 Window 三大操作,而是全部交给了 WindowManagerGlobal 来处理。

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();


public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    //检测参数是否合法
    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 {
        //.... 
    }

    // 真正设置window的主
    ViewRootImpl root;
    View panelParentView = null;

    //创建 ViewRootImpl,并赋值给 root
    root = new ViewRootImpl(view.getContext(), display);
    
    //设置 View 的params
    view.setLayoutParams(wparams);
    
    //将 view,RootRootImpl,wparams 添加到列表中
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    
    try {
        //调用 ViewRootImpl 来更新界面并完成 Window 的添加过程
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

首先声明了几个列表:

  • mViews 中是所有 Window 对应的 View
  • mRoots 中是所有 Window 对应的 ViewRootImpl
  • mParams 存储的是所有 Window 所对应的布局参数
  • 而 mDyingViews 中是哪些真在被删除的 View,或者说是已经调用 RemoveView 但是删除操作没有完成的 Window 对象。

上面代码中,创建了 ViewRootImpl,然后将 view,ViewRootImpl,wparams 添加到列表中。最后通过 ViewRootImpl 来完成添加 Window 的过程。

2.2 ViewRootImpl.setView

接着我们进到 ViewRootImpl 中的 setView 方法来看这里的添加过程

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //   Binder 对象
    final IWindowSession mWindowSession;

    synchronized (this) {
        if (mView == null) {
            mView = view;

            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            // 添加window之前,先进行一次刷新请求
            requestLayout();

            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();

               //调用了 mWindowSession.addToDisplay 方法,来完成最终的 Window 的添加过程
                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();
    }
}

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在上面的代码中首先会调用 requestLayout 来进行一次刷新请求,其中 scheduleTraversals() 是 View 绘制的入口
requestLayout 调用之后,调用了 mWindowSession.addToDisplay 方法,来完成最终的 Window 的添加过程。
在上面代码中,mWindowSession 的类型是 IWindowSession,他是一个 Binder 对象,真正的实现是 Session,也就是 Window 的添加过程是一次 IPC 调用。

// IWindowSession 
interface IWindowSession { 
    int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, 
    in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities, 
    out InputChannel outInputChannel, out InsetsState insetsState, 
    out InsetsSourceControl[] activeControls, out Rect attachedFrame, 
    out float[] sizeCompatScale);
    //.......
  }

//------------------------------------------------------------------------------------------------------------------
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {

final WindowManagerService mService;

 @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
                outActiveControls, outAttachedFrame, outSizeCompatScale);
    }

可以看到 IWindowSession 是个aidl文件的接口,真实的实现类是Session,在其中真正实现了addToDisplay方法,接着就交给了mService调用addWindow()方法。而这个mService就是 WindowManagerService。
如此一来,Window 的添加过程就交给了WindowManagerService去处理。WMS 会为其分配 Surface,确定窗口显示的次序,最终通过 SurfaceFlinger 将这些 Surface 绘制到屏幕上。这部分的内容我们在后续的文章进行分析。

2.3 添加window流程分析
  1. windowManager.addView
  2. WindowManagerImpl.addView 在 addView 中将实现委托给了 WindowManagerGlobal.addView()
  3. 在 addView 中创建了 ViewRootImpl 赋值给了 root 。然后将 view,params,root 全部存入了各自的列表中。最后调用了 ViewRootImpl.setView()
  4. 在 setView 中通过调用 requestLayout 完成刷新的请求,接着会通过 IWindowSession 来完成最终的 Window 添加的过程,IWindowSession 是一个 Binder 对象,真正的实现类是 Session,也就是说 Window 的添加过程试一次 IPC 的调用。在 Session 中会通过 WindowManagerService 的addWindow 方法来实现 Window 的添加。
2.4 Window 更新源码分析

上面看到window的更新也是通过 WindowManagerGlobal 这个代理类来进行操作的,那就从WindowManagerGlobal 的 updateViewLayout 进去看看底层的更新操作

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");
        }
        //将更新的参数设置到 view 中
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        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);
        }
    }

通过 ViewRootImpl 的setLayoutParams方法对Window进行更新操作:

void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { 
  //..........
  scheduleTraversals();

  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    //通过runnable进行刷新
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            // 进行刷新操作
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}

void doTraversal() {
    //.....
            // 刷新操作
            performTraversals();
    //.....
}

private void performTraversals() {
  //刷新操作
  relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
      // 通过mWindowSession进行刷新
        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,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurface);
    }
}

在 setLayoutPrams 方法中,最终调用了 scheduleTraversals 方法来对 View 重新策略,布局,重绘。

 @Override
    public int relayout(IWindow window, WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
            int lastSyncSeqId, ClientWindowFrames outFrames,
            MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
            Bundle outSyncSeqIdBundle) {
      // 通过WindowManagerService来更新视图
        int res = mService.relayoutWindow(this, window, attrs,
                requestedWidth, requestedHeight, viewFlags, flags, seq,
                lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
                outActiveControls, outSyncSeqIdBundle);
        return res;
    }

除了 View 本身的重绘外,ViewRootImpl 还会通过 WindowSession 来更新 Window 视图,这个过程是由 WindowManagerService 的 relayoutWindow 来实现的,这同样也是一个 IPC 过程。

2.5 Window 删除源码分析

Window 的删除过程和添加过程都一样,都是先通过 WindowManagerImpl 后,在进一步通过 WindowManagerGlobal 来实现的:

    @Override
    public void removeView(View view) {
        // 异步移除
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        // 同步移除
        mGlobal.removeView(view, true);
    }


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);
            //找到在 views 列表中的索引
            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);
        }
    }

上面代码中,找到在 views 列表中的索引,然后调用了 removeViewLocked 来做进一步的删除

private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            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);
            }
        }
    }

removeViewLocked 是通过 ViewRootImpl 来完成删除操作的。在 WindowManager 中提供了两种删除接口 removeView 和 removeViewImmedialte 分别是异步删除和同步删除。
一般不会使用 removeViewImmedialte 来删除 Window,以免发生意外错误。
所以这里使用的是 异步的删除情况,采用的是 die 方法。die 方法只是发送了一个请求删除的消息就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews列表中。

#ViewRootImpl

/**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        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;
    }

从源码的注释看出:

  • 如果传入是true则马上移除,同时返回 false 代表移除完成(同步删除)
  • 如果传入的是false则加入到队列中等待执行,同时返回true表示等待执行。(异步删除)

这个方法里面做了判断,如果是异步删除就会发送一个 MSG_DIE 的消息,ViewRootImpl 中的 handler 会收到这个消息,并调用 doDie 方法,这就是这两种删除方式的区别。

        @Override
        public void handleMessage(Message msg) {
              case MSG_DIE:
                    doDie();
                    break
}

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                 //真正执行删除的逻辑
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();
              //...........
            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }


   void dispatchDetachedFromWindow() {
      try {
            //........
            mWindowSession.remove(mWindow);
           //........
        } catch (RemoteException e) {
        }
    }

在上面代码中最后通过ViewRootImpl 的 doDie 方法执行移除操作,接着在dispatchDetachedFromWindow方法中执行真正的移除操作。而最终还是交给了mWindowSession的remove方法。
依旧是通过IPC,最终交给Session的 remove方法来执行移除操作。

@Override
    public void remove(IWindow window) {
        mService.removeWindow(this, window);
    }

可以看到还是通过WindowManagerService来执行最后的移除操作。

三、总结

Window 的 创建,更新,移除 操作都是通 ViewRootImpl 使用 IPC与WMS(WindowManagerService )通信来实现的。

Window 添加,更新,移除

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容