第8章-理解 Window 和 WindowManager 读书笔记

   日期:2020-08-31     浏览:111    评论:0    
核心提示:`Window` 表示一个窗口的概念。`Window` 是一个抽象类,它的具体实现是 `PhoneWindow`,同时也是唯一的实现。通过 `WindowManager` 可以创建一个 `Window`。外界访问 `Window` 要通过 `WindowManager`,`Window` 的具体实现位于 `WindowManagerService` 中,`WindowManager` 和 `WindowManagerService` 的交互是一个 IPC 过程。

目录

  • 1 Window 和 WindowManager
    • 1.1 说一说对于 Window 的理解。
    • 1.2 WindowManager 的常用方法有哪些?
    • 1.3 WindowManager 中有一个静态内部类 LayoutParams,绘制一下它的类图。
    • 1.4 Window 的类型分类
  • 2 Window 的内部机制
    • 2.1 Window 和 View 的关系
    • 2.2 Window 的添加过程
    • 2.3 Window 的删除过程
    • 2.4 Window 的更新过程

1 Window 和 WindowManager

1.1 说一说对于 Window 的理解。

Window 表示一个窗口的概念。Window 是一个抽象类,它的具体实现是 PhoneWindow,同时也是唯一的实现。通过 WindowManager 可以创建一个 Window。外界访问 Window 要通过 WindowManagerWindow 的具体实现位于 WindowManagerService 中,WindowManagerWindowManagerService 的交互是一个 IPC 过程。

Android 中所有的视图都是通过 Window 来呈现的,不管是 ActivityDialog 还是 Toast,它们的视图实际上都是附加在 Window 之上的,因此 Window 实际是 View 的直接管理者。

Abstract base class for a top-level window look and behavior policy. An
instance of this class should be used as the top-level view added to the
window manager. It provides standard UI policies such as a background, title
area, default key processing, etc.

1.2 WindowManager 的常用方法有哪些?

WindowManager 是一个接口,常用的方法有三个,即添加 View,更新 View 和删除 View,这三个方法是定义在 ViewManager 接口中,WindowManager 是继承于 ViewManager 的。

«interface» ViewManager +addView(View view, ViewGroup.LayoutParams params) : void +updateViewLayout(View view, ViewGroup.LayoutParams params) : void +removeView(View view) : void «interface» WindowManager +removeViewImmediate(View view) : void +getDefaultDisplay() : Display extends

可以看到,addViewupdateViewLayoutremoveView 这三个接口方法都是针对 View 来操作的。

1.3 WindowManager 中有一个静态内部类 LayoutParams,绘制一下它的类图。

ViewGroup_LayoutParams +int width +int height LayoutParams +int x +int y +float horizontalWeight +float verticalWeight +int type +int flags +int softInputMode +int gravity +float horizontalMargin +float verticalMargin +int format +int windowAnimations +float alpha +float dimAmount +IBinder token +String packageName +int screenOrientation +float preferredRefreshRate +int systemUiVisibility -CharSequence mTitle +setTitle(CharSequence title) : void +getTitle() : CharSequence Parcelable extends implements

参考链接:·WindowManager.LayoutParams详解总结和对应实例

1.4 Window 的类型分类

WindowManager.LayoutParamstype参数表示 Window 的类型。

类型 层级范围 说明
应用 Window 1~99 应用类 Window 对应着一个 Activity
子 Window 1000~1999 子 Window 不能单独存在,需要附属在特定的父 Window 里,比如常见的 Dialog 就是一个子 Window
系统 Window 2000~2999 系统 Window 是需要声明权限 android.permission.SYSTEM_ALERT_WINDOW 才能创建的 Window,如 Toast 和系统状态栏都是系统 Window

Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 的上面。

应该注意的是,在有些手机上,需要手动打开 android.permission.SYSTEM_ALERT_WINDOW 才可以。

2 Window 的内部机制

2.1 Window 和 View 的关系

Window 是一个抽象的概念,每一个 Window 都对应着一个 ViewViewRootImplWindowView 通过 ViewRootImpl 来建立联系。所以,Window 并不是实际存在的,它是以 View 的形式存在的。

2.2 Window 的添加过程

public class WindowActivity extends Activity implements View.OnTouchListener {

    private WindowManager mWindowManager;
    private Button mFloatingButton;
    private WindowManager.LayoutParams mLayoutParams;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_window);
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    }

    public void button(View view) {
        mFloatingButton = new Button(this);
        mFloatingButton.setText("click me");
        mLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        // 如果不写下面这行,那么锁屏后竟然无法解锁了。
        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL // 设置了这个标记,即便当前 Window 具有焦点,
                // 也允许将当前 Window 区域以外的单击事件传递给底层的 Window;否则,当前窗口将消费所有的点击事件,不管点击事件是否在窗口之内。
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 表示 Window 不需要获取焦点,也不需要接收各种输入事件,
                // 此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; // 让 Window 显示在锁屏的界面上。
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        mLayoutParams.x = 100;
        mLayoutParams.y = 300;
        mFloatingButton.setOnTouchListener(this);
        // 添加
        mWindowManager.addView(mFloatingButton, mLayoutParams);
    }
}

从上面的代码中,可以看到:Window 的添加过程需要通过 WindowManageraddView 来实现。但是,WindowManager 是一个接口,那么它的实现类是谁呢?

其实在 ContextImpl 里有一个静态代码块,包含了初始化 WindowManager 的代码:

static {
	 registerService(WINDOW_SERVICE, new ServiceFetcher() {
	                Display mDefaultDisplay;
	                public Object getService(ContextImpl ctx) {
	                    Display display = ctx.mDisplay;
	                    if (display == null) {
	                        if (mDefaultDisplay == null) {
	                            DisplayManager dm = (DisplayManager)ctx.getOuterContext().
	                                    getSystemService(Context.DISPLAY_SERVICE);
	                            mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
	                        }
	                        display = mDefaultDisplay;
	                    }
	                    // 构造了 WindowManagerImpl 的实例
	                    return new WindowManagerImpl(display);
	                }});
}
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
            new HashMap<String, ServiceFetcher>();

private static void registerService(String serviceName, ServiceFetcher fetcher) {
        if (!(fetcher instanceof StaticServiceFetcher)) {
            fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
        }
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }

再看一下 ContextImplgetSystemService 方法:

public Object getSystemService(String name) {
   	ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
    return fetcher == null ? null : fetcher.getService(this);
}

这里获取的正是 WindowManagerImpl 的实例。所以,WindowManager 接口的真正实现是 WindowManagerImpl 类。

我们看一下 WindowManagerImpl 类如何实现添加 Window 的:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, 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);
    }

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

可以看到,WindowManagerImpl 并没有直接实现 Window 的添加,更新以及删除,而是全部交给了 WindowManagerGlobal 来处理。WindowManagerGlobal 是单例类。

WindowManagerImplWindowManagerGlobal 的关系用到了桥接模式,如下:

WindowManagerImpl -WindowManagerGlobal mGlobal +addView(View view, ViewGroup.LayoutParams params) : void +updateViewLayout(View view, ViewGroup.LayoutParams params) : void +removeView(View view) : void +removeViewImmediate(View view) : void WindowManagerGlobal +addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) : void +updateViewLayout(View view, ViewGroup.LayoutParams params) : void +removeView(View view, boolean immediate) Aggregation

先介绍 WindowManagerGlobal 里几个重要的列表:

// 存储的是所有 Window 对应的 View 对象
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储的是所有 Window 对应的 ViewRootImpl 对象
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储的是所有 Window 对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
       new ArrayList<WindowManager.LayoutParams>();
// 存储的是那些正在被删除的 View 对象,或者说是那些已经调用 removeView 方法
// 但是删除操作还未完成的 View 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();

WindowManagerGlobaladdView 方法的执行流程:

  1. 检查参数合法性,如果是子 Window (存在父 Window 的就是子 Window)就调整一些布局参数;如果不存在父 Window 并且版本大于等于 21,会默认设置窗口的硬件加速属性。

  2. 检查 View 是否在 mViews 集合中,是否在 mDyingViews 集合中。如果已经在 mView 里,是不可以添加的,会抛出异常;如果已经在 mDyingViews 集合里,需要立马销毁掉对应的 View

    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.
    }
    
  3. 创建 ViewRootImpl 对象,并将 VIew 添加到列表中

    相关的代码如下:

    // 创建 ViewRootImpl 对象
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    // 添加到
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
  4. 通过 VIewRootImpl 来更新界面完成 Window 的添加过程

    root.setView(view, wparams, panelParentView);
    

    4.1 在 setView 方法内部,先通过 requestLayout() 来完成异步刷新请求

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
        	// 检查是否是 UI 线程
            checkThread();
            mLayoutRequested = true;
            // 开启异步刷新请求(这里面用到了同步屏障消息和异步消息)
            scheduleTraversals();
        }
    }
    

    4.2 接着通过 WindowSession 最终来完成 Window 的添加过程。

    // 这个返回值,就是平常开发中遇到的 `BadTokenException`,`InvalidDisplayException` 的来源。
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(),
        mAttachInfo.mContentInsets, mInputChannel);
    

    mWindowSession 的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,这说明 Window 的添加过程需要一次 IPC 调用。
    4.3 在 Session 内部通过 WindowManagerService 来实现 Window 的添加。

    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outInputChannel);
    }
    

2.3 Window 的删除过程

mWindowManager.removeView(mFloatingButton);
  1. 调用 WindowManagerremoveView 方法,是调用它的实现类 WindowManagerImplremoveView 方法;再通过桥接调用 WindowManagerGolbal 对象的 removeView(View view, boolean immediate) 方法(第二个参数取值为 false)如下:
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        synchronized (mLock) {
        	// 先在 mViews 集合里查找索引,找不到会抛出异常
            int index = findViewLocked(view, true);
            // 从 mRoots 里根据索引找到 ViewRootImpl 对象,获取对应的 `View` 对象
            View curView = mRoots.get(index).getView();
            // 最后移除 View 对象
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
    
  2. 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);
            }
        }
    }
    
    这里就是调用了 ViewRootImpldie 方法。这里传给 die 的实参是 false,表示异步删除,由 removeView 方法调用而来;传 true 时,表示同步删除,由 removeViewImmediate 方法调用而来。一般不要使用同步删除方式来删除 Window 以免发生意外的错误。
  3. ViewRootImpldie 方法:
    boolean die(boolean immediate) {
    // Make sure we do execute immediately if we are in the middle of a traversal o
    // done by dispatchDetachedFromWindow will cause havoc on return.
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                " window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
    
    如果 immediate 取值为 true,即同步删除,那么会直接调用 doDie() 方法,die 方法返回 false,表示已经完成删除;如果 immediate 取值为 false,即异步删除,那么发送一个请求删除的消息(MSG_DIE),die 方法返回 true,表示请求在排队中,再回到 removeViewLocked 方法中,会把 view 添加到 mDyingViews 集合(表示存那些正在被删除的 View 对象集合)。
  4. ViewRootImpldoDie() 方法:
    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }
            mAdded = false;
        }
        // 刷新几个列表的数据
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
    
  5. ViewRootImpldispatchDetachedFromWindow() 方法:
    void dispatchDetachedFromWindow() {
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            // 回调 View 的 onDetachedFromWindow 方法
            mView.dispatchDetachedFromWindow();
        }
        mView.assignParent(null);
        mView = null;
        try {
        	// Session 的 remove 方法移除 Window
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        unscheduleTraversals();
    }
    

2.4 Window 的更新过程

直接看 WindowManagerGlobalupdateViewLayout 方法:

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.setLayoutParams(wparams);
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

首先是对参数的一些检查,接着把布局参数设置给 viewint index = findViewLocked(view, true); 检查 view 是否还在 mViews 集合里,不在的话会抛出异常;最后调用 ViewRootImpl 对象的 setLayoutParams 方法更新 WindowViewRootImpl 里会通过 scheduleTraversals 方法来对 View 重新布局,包括测量,布局,重绘三个过程。除了 View 本身的重绘外,ViewRootImpl 还会通过 WindowSession 来更新 Window 的视图,这个过程最终是 WMS 的 relayoutWindow() 来实现的,它同样是一个 IPC 过程。(加粗部分需要继续研究)

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服