自定义View基础(二)—— 理解Window和WindowManger

来源:互联网 发布:幼儿园美工活动室 编辑:程序博客网 时间:2024/06/05 14:16

自定义View基础的第一篇文章自定义View基础(一)——追根溯源,透过源码认识ViewRoot,DecorView和performTraversals方法大体介绍了一个Android页面展示出来的大体的流程,但是,中间仍有很多小的知识点需要去具体的了解、掌握,这篇文章主要就是记录我自己对于Window和WindowManger的一些理解吧。

在平时的开发中,我们接触最多的是View而不是Window,只有在某些特殊的情况下才会想到使用Window,单单对于开发而言,Window的意义并没有View那么重大,但是,想要对Android的框架有个全面的了解,还是应该去看看Window,以及和它相关的一些知识。

一、 关于Window

1. 什么是Window?

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.

这是Window抽象类的注释,Window是Android的顶级窗口,控制了Android的视图以及行为策略(比如事件分发),Window的具体实现实例应该是和WindowManger在一起使用的。在Android中PhoneWindow是Phone的实现类,这篇没有介绍Window的具体创建的过程,下一篇中会介绍到各种Window的创建。

2. Window的类型

Window有3种类型,分别是应用Window,子Window以及系统Window,不同的Window对应着不同的层级,层级大的会覆盖在层级小的Window之上,而Window的层级是需要借助WindowManger.LayoutParams.type属性进行设置的,通常我们可以选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,具体可参看下面的表格:

Window类型 层级 常见案例 备注 应用Window 1~99 Activity 子Window 1000~1999 Dialog 不能单独存在,需要依附特定的父Window 系统Window 2000~2999 Toast

3. Window的显示特性

Window的显示特性是通过WindowManger.LayoutParams.flags来设置的,常用的参数类型以及意义见下表:

flags类型 意义 FLAG_NOT_FOCUSABLE 该Window不获取焦点,同时启用FLAG_NOT_TOUCH_MODAL,最终事件会传传递给下层具有焦点的Window FLAG_NOT_TOUCH_MODAL 当前Window区域以外单击事件传递给底层Window,当前Window区域以内单击事件自己处理 FLAG_SHOW_WHEN_LOCKEDL 开启此模式可以让Window显示在锁屏界面

二、 关于WindowManger

什么是WindowManger?

WindowManager主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等。
通过Context.getSystemService(Context.WINDOW_SERVICE)的方式可以获得WindowManager的实例。
WindowManager是一个继承自ViewManager的接口,其实现类WindowManagerImpl里面涉及到窗口管理的三个重要方法,分别是:

  • addView();
  • updateViewLayout();
  • removeView();

在WindowManager中还有一个重要的静态类LayoutParams.通过它可以设置和获得当前窗口的一些属性。


三、 通过WindowManger添加一个View

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Button floatingButton = new Button(this);        floatingButton.setText("button");        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(                WindowManager.LayoutParams.WRAP_CONTENT,                WindowManager.LayoutParams.WRAP_CONTENT,                0, 0,                PixelFormat.TRANSPARENT        );        // flag 设置 Window 属性        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;        // type 设置 Window 类别(层级),使用TYPE_TOAST不需要请求权限        layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;        layoutParams.gravity = Gravity.CENTER;        WindowManager windowManager = getWindowManager();        //通过WindowManger.addView()把Button添加到Window中        windowManager.addView(floatingButton, layoutParams);    }}

上面的代码,可以把Button添加到Window中,并且这个Button会一直出现在屏幕中间直到你把测试程序杀死,如下图:
这里写图片描述

(注:貌似有些手机不能显示出来,如果显示不出来建议先换个手机试试,这里就不深究了)


四、 Window的内部机制

Window是一个抽象类,WindowManger为Window的显示提供了参数设置,也为Window添加,更新以及删除视图提供了方法,上面的Demo就是通过addView为Window添加一个Button视图,为了分析Window的内部机制,分别从添加,删除以及更新三个部分来分析。而Window的视图相关的内容都是通过WindowManger来实现的,WindowManger本身是一个借口,所以,看它的实现类WindowMangerImpl

    @Override    public void addView(View view, ViewGroup.LayoutParams params) {        mGlobal.addView(view, params, mDisplay, mParentWindow);    }    @Override    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {        mGlobal.updateViewLayout(view, params);    }    @Override    public void removeView(View view) {        mGlobal.removeView(view, false);    }

WindowMangerImpl也不是直接管理Window的视图的,而是通过mGlobal实例,

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

也就是WindowManagerGlobal类,这个类,实现了Window的这三种操作。

1. Window的添加过程(addView)

WindowManagerGlobal的addView方法主要分为以下几个步骤:

1.1 检查参数是否合法,如果是子Window则需要调整一些布局参数
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 and we're running on L or above (or in the    // system context), assume we want hardware acceleration.    final Context context = view.getContext();    if (context != null && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {        wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;    }}
1.2 View,root,params对应的列表
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>();

ViewRootImpl类中有这4个List,分别存放着View,root,params

List 意义 mViews 存放的是所有Window中所所对应的View mRoots 存放的是所有Window中所所对应的ViewRootImpl(与View一一对应) mParams 存放的是所有Window中所所对应的布局参数(与View一一对应) mDyingViews 存放的是正在删除的View
1.3 在addView中,创建ViewRootImpl并将View,root,params存放到列表中
        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);                    }                }            }            root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);        }

这段源码中,前面的一大段看注释大概可以明白也是一些准备工作,最重要的就是最后三句话,分别将View,root,params存放到列表中

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

这段源码中比较重要的就是root.setView(view, wparams, panelParentView);

                requestLayout();//第一部分                if ((mWindowAttributes.inputFeatures                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {                    mInputChannel = new InputChannel();                }                try {                    mOrigWindowType = mWindowAttributes.type;                    mAttachInfo.mRecomputeGlobalAttributes = true;                    collectViewAttributes();                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                            getHostVisibility(), mDisplay.getDisplayId(),                            mAttachInfo.mContentInsets, mInputChannel);//第二部分                } catch (RemoteException e) {                    mAdded = false;                    mView = null;                    mAttachInfo.mRootView = null;                    mInputChannel = null;                    mFallbackEventHandler.setView(null);                    unscheduleTraversals();                    setAccessibilityFocus(null, null);                    throw new RuntimeException("Adding window failed", e);                } finally {                    if (restore) {                        attrs.restore();                    }                }

分析:
root.setView()此方法中重要的阶段分成两部分:
第一部分:requestLayout();
在上一篇文章我们就是想要分析这一部分,在这一部分中,最终会调用ViewRootImpl .performTraversals(),完成对View的measure,layout和draw
第二部分:mWindowSession.addToDisplay

IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(    new IWindowSessionCallback.Stub() {    @Override    public void onAnimatorScaleChanged(float scale) {        ValueAnimator.setDurationScale(scale);    }    },imm.getClient(), imm.getInputContext());

mWindowSession这个对象也是在WindowMangerGlobal类中创建的,先得到WindowMangerService,之后打开Session,WindowMangerService是FrameWork层中的内容,所以需要IPC调用。WindowMangerService怎么执行就不深究了。

2. Window的删除过程(removeView)

Window的删除也是借助WindowManagerGlobal来实现的,下面的WindowManagerGlobal的removeView方法:

public void removeView(View view, boolean immediate) {    if (view == null) {        throw new IllegalArgumentException("view must not be null");    }    synchronized (mLock) {        //查找待删除View的索引        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);    }}

findViewLocked方法是用来查找待删除View的索引,这就是用到上文建立的数组遍历,然后再调用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中的die方法,WindowManger中提供了两种删除接口removeView和removeViewImmediate,他们分别代表了同步和异步删除View,查看ViewRootImpl中的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(TAG, "Attempting to destroy the window while drawing!\n" +                "  window=" + this + ", title=" + mWindowAttributes.getTitle());    }    mHandler.sendEmptyMessage(MSG_DIE);    return true;}

die方法返回的结果是一个boolean类型的数据,如果immediate为true的时候,同步删除,立即执行doDie方法,并且返回的结果为false;如果是immediate为false的话则继续往下,通过Handler发送了一个“MSG_DIE”消息,并返回true,

if (deferred) {    mDyingViews.add(view);}

mDyingViews就是我们在上面定义的List,表示待删除的View对象,异步的话则需要将这个View对象添加进去,同步的话则不需要,那么异步在消息接受之后又做了什么呢?其实同样是执行了doDie方法,继续看这个方法,在doDie中dispatchDetachedFromWindow方法真正执行了删除View,类似addView的是,这也是最终调用了WindowMangerService的RemoveWindow方法。

3. Window的更新过程(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.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);    }}

这里分分为两部分:1. view.setLayoutParams(wparams);给View设置新的params;2.root.setLayoutParams(wparams, false);在这个方法中会通过scheduleTraversals();对View本身重新布局,包括测量,布局,重绘三个过程

如有错误,欢迎指正
邮箱:hello_zhangchao@163.com