Android | 理解Window 和 WindowManager(上)

简介: Android | 理解Window 和 WindowManager(上)

前言


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


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


在我的理解中,上面第一句话中的 window 和 第二句话中的 Window 不是一个东西。


第一句话中的 Window 是一个窗口,是一个抽象的概念,并不真实存在,他只是以 View 的形式存在。例如通过 WindowManager 添加一个 Window,这个 Window 就是以 View 的形式存在的。


第二句话中的 Window 指的是一个类,他的实现类是 PhoneWindow,他是用来创建我们页面中所需要的 View 的。所以这个 Window 可以称之为 View 的直接管理者。PhoneWindow 中的 DecorView 最终也是附加到 Window(窗口)上面的。


因为在最开始的时候经常把二者搞混,Window 即是 View 管理者,也是窗口,显然是不合理的。以上是我的个人理解,如果有感觉不对的,请指出,谢谢!


Window 和 WindowManager


如果要对 Window 进行添加和删除就需要通过 WindowManager 来操作,具体如下:


WindowManager 如何添加 Window?


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)


上面这段代码可以添加一个 Window,位置在 (100,500),这里面比较重要的属性分别是 type 和 flags。


Type 窗口属性


Type 参数表示 Window 的类型,Window 分三种类型,对应着三种层级,如下:

image.png

子 Window 无法单独存在,必须依赖父级 Window,例如 Dialog 必须依赖 Activity 的存在

Window 分层,在显示时层级高的会覆盖层级低的窗口


Flags窗口的标志


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

image.png


WindowManager


WindowManager 所提供的功能很简单,常用的只有三个方法,即添加 View,更新View,和删除 View。


这三个方法定义在 ViewManager 接口中,而 WindowManager 继承了 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);
}


public interface WindowManager extends ViewManager


由此看来 WindowManager 操作 Window 的过程更像是在操作 Window 中的 View,我们平常简单的那种可以拖动的 Window 效果其实是很好实现的,只需要修改 LayoutParams 中的 x,y 值就可以改变 Window 的位置。首先给 View 设置 onTouchListener,然后在 onTouch 方法中不断的更新 View 的位置即可。


Window 内部机制


Window 是一个抽象的概念,每一个 Window 都对应着一个 VIew 和一个 ViewRootImpl。


Window 和 View 通过 ViewRootImpl 来建立联系,因此 Window 并不是实际存在的,它是以 View 的形式存在。这个从 WindowManager 的定义就可以看出,提供的三个方法都是针对 View。这说明 View 才是 Window 的实体。


在实际开发中无法直接访问 Window,对 Window 访问必须通过 WindowManager


Window 的添加过程


Window 的添加需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口,他的真正实现是 WindowManageImpl。如下:


@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 并没有直接实现 Window 三大操作,而是全部交给了 WindowManagerGlobal 来处理。


WindowManagerGlobal.addView


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 {
        //.... 
    }
    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;
    }
}


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


这些列表的定义如下:


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


mViews 中是所有 Window 对应的 View

mRoots 中是所有 Window 对应的 ViewRootImpl

mParams 存储的是所有 Window 所对应的布局参数

而 mDyingViews 中是哪些真在被删除的 View,或者说是已经调用 RemoveView 但是删除操作没有完成的 Window 对象。


ViewRootImpl.setView


public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    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.
            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();
    }
}


这个方法首先会调用 requestLayout 来进行一次刷新请求,其中 scheduleTraversals() 是 View 绘制的入口


requestLayout 调用之后,调用了 mWindowSession.addToDisplay 方法,来完成最终的 Window 的添加过程。


在上面代码中,mWindowSession 的类型是 IWindowSession,他是一个 Binder 对象,真正的实现是 Session,也就是 Window 的添加过程是一次 IPC 调用。


在 Session 内部会通过 WindoweManagerServer 来实现 Window 的添加,如下所示:


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


如此一来,Window 的添加过程就交给了 WindowManagerServer 去处理。WMS 会为其分配 Surface,确定窗口显示的次序,最终通过 SurfaceFlinger 将这些 Surface 绘制到屏幕上。


梳理一下流程


首先调用的是 WindowManagerImpl.addView()


在 addView 中将实现委托给了 WindowManagerGlobal.addView()


WindowManagerGlobal.addView()


在 addView 中创建了 ViewRootImpl 赋值给了 root 。然后将 view,params,root 全部存入了各自的列表中。


最后调用了 ViewRootImpl.setView()


ViewRootImpl.setView()


在 setView 中通过调用 requestLayout 完成刷新的请求,接着会通过 IWindowSession 来完成最终的 Window 添加的过程,IWindowSession 是一个 Binder 对象,真正的实现类是 Session,也就是说 Window 的添加过程试一次 IPC 的调用。


在 Session 中会通过 WindowManageServer 来实现 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");
    }
    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);
    }
}


ViewRootImpl.setLayoutPrams


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


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


Window 的删除过程


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


WindowManagerGlobal.removeView


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


上面代码中,找到在 views 列表中的索引,然后调用了 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) {
            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 列表中。


die 如下所示:


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


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


void doDie() {
    checkThread();
    if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
    synchronized (this) {
        if (mRemoved) {
            return;
        }
        mRemoved = true;
        if (mAdded) {
            //真正删除的逻辑是在此方法中
            dispatchDetachedFromWindow();
        }
  //....
        mAdded = false;
    }
    WindowManagerGlobal.getInstance().doRemoveView(this);
}


Window 的创建过程


通过上面的分析可以得出,View 不能单独存在,必须依附在 Window 上面,因此有视图的地方就有 Window。这些视图包括 :Activity,Dialog,Toast,PopupWindow 等等。


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


在 Activity 的 attach 方法中,系统会创建 Activity 所属的 Window,并未其设置回调接口,由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接受到外接的状态改变时就会回调 Activity 中的方法。


Callback 中的方法有很多,但是有些我们是非常熟悉的,例如 dispatchTouchEvent,onAttachedToWdindow 等等。


相关文章
|
2月前
|
Android开发 UED 开发者
Android经典实战之WindowManager和创建系统悬浮窗
本文详细介绍了Android系统服务`WindowManager`,包括其主要功能和工作原理,并提供了创建系统悬浮窗的完整步骤。通过示例代码,展示了如何添加权限、请求权限、实现悬浮窗口及最佳实践,帮助开发者轻松掌握悬浮窗开发技巧。
311 1
|
2月前
|
API Android开发 数据安全/隐私保护
Android经典实战之窗口和WindowManager
本文介绍了Android开发中“窗口”的基本概念及其重要性。窗口是承载用户界面的基础单位,而`WindowManager`系统服务则负责窗口的创建、更新和移除等操作。了解这些概念有助于开发复杂且用户体验良好的应用。
57 2
|
4月前
|
消息中间件 前端开发 Android开发
Android面试题自定义View之Window、ViewRootImpl和View的三大流程
Android开发中,View的三大核心流程包括measure(测量)、layout(布局)和draw(绘制)。MeasureSpec类在测量过程中起到关键作用,它结合尺寸大小和模式(EXACTLY、AT_MOST、UNSPECIFIED)来指定View应如何测量。onMeasure方法用于自定义View的测量,布局阶段,ViewGroup调用onLayout确定子元素位置,而draw阶段按照特定顺序绘制背景、内容、子元素和装饰。整个流程始于ViewRootImpl的performTraversals,该方法触发测量、布局和绘制。
117 0
|
5月前
|
Android开发
Android WindowManager工具类
Android WindowManager工具类
44 0
|
XML 存储 设计模式
Android Framework知识整理:WindowManager体系(上)
本篇是Android framework的第一讲《WindowManager体系-上》,重在讲解Window在被添加到WindowManagerService前的流程。
|
API uml Android开发
Android | 深入理解View.post()获取宽高、Window加载View原理
深入理解View.post()获取宽高、Window加载View原理
432 0
|
Android开发
Android | 理解Window 和 WindowManager(下)
Android | 理解Window 和 WindowManager(下)
Android | 理解Window 和 WindowManager(下)
|
Android开发
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
162 0
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
|
存储 消息中间件 API
下沉式通知的一种实现 | Android悬浮窗Window应用
当你浏览公众号时来了一条新消息,通知在屏幕顶部会以自顶向下动画的形式入场,而且它是跨界面的全局浮窗(效果如下图)。虽然上一篇中抽象的浮窗工具类已经能实现这个需求。但本文在此基础上再封装一些更加友好的
370 0
下沉式通知的一种实现 | Android悬浮窗Window应用
|
存储 Android开发 Kotlin
悬浮窗的一种实现 | Android悬浮窗Window应用
本文以业务应用为出发点,从零开始抽象一个浮窗工具类,它用于在任意业务界面上展示悬浮窗。它可以同时管理多个浮窗,而且浮窗可以响应触摸事件,可拖拽,有贴边动画。
830 0
悬浮窗的一种实现 | Android悬浮窗Window应用