自定义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,具体可参看下面的表格:
3. Window的显示特性
Window的显示特性是通过WindowManger.LayoutParams.flags来设置的,常用的参数类型以及意义见下表:
二、 关于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
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
- 自定义View基础(二)—— 理解Window和WindowManger
- WindowManger与window进阶篇_1(ViewRootImpl深入理解,View测量)
- Window和WindowManger
- Android中的Window和WindowManger
- WindowManger与window之基础篇
- Android自定义View基础篇(二)
- Android 自定义view基础(二)
- 自定义View笔记(二) ---深入理解自定义属性
- 理解Window和WindowManager(二)
- Android 自定义View基础(二)
- 自定义View基础(二)View的滑动
- android 自定义view——自定义属性(二)
- Android自定义View基础——弧度和角度
- android:Activity、View和Window的关系(二)
- 自定义view(二)
- 自定义view(二)
- 自定义View(二)
- 自定义View(二)
- windows下端口号查看
- 共享栈
- 算法入门经典第二版 3-5 Puzzle
- 手动安装opencc(中文简繁体转换插件) ——解决安装opencc时出现HTTP 403错误的问题
- 《kubernetes-1.8.0》11-addon-Harbor
- 自定义View基础(二)—— 理解Window和WindowManger
- Java并发编程学习——《Java Concurrency in Practice》学习笔记 5.基础构建模块
- python中defaultdict方法的使用
- Add Binary(LeetCode)
- C++ 类,构造函数,析构函数
- 项目中常用的19条MySQL优化
- Java并发编程学习——《Java Concurrency in Practice》学习笔记 并发技巧清单
- ABP基础实践训练,一个简易的博客(增删改查)等功能 一:
- 使用jquery解析json文件