自定义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