Android 理解Window 和 WindowManager
2015-12-26 20:11
501 查看
这几天阅读了《Android开发艺术探索》的关于Window和WindowManager的章节,特此写一片博文来整理和总结一下学到的知识,做一个笔记方便自己查阅。
说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.
FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件
FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启
FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上
Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.具体Type参数表示Window的类型如下
应用 Window:层级范围 1-99,优先级最低;一个应用 Window 对应一个 Activity
子 Window:1000-1999,优先级中等;例如对应一个 Dialog 。
系统 Window:2000-2999,一般为 TYPE_SYSTEM__OVERLAY 或 TYPE_SYSTEM_ERROR ,需要声明 SYSTEM_ALERT_WINDOW 权限
每一个 Window 都对应着一个 View 和一个 ViewRootImpl
Window 和 View 通过 ViewRootImpl 建立联系
无法直接操作 Window,只能通过 WidowManager
看看WindowManagerImpl#addView
接着看WindowManagerGlobal#addView
最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window,sheduleTraversals()会调用到下面的方法
所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.
通过 ViewRootImpl 来完成删除操作,在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除(不常用).而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.
ViewRootImpl.die()
当 immediate 为 false,使用异步删除,就发送一个 MSG_DIE 的消息,ViewRootImpl 的 mHandler 将处理此消息并调用 doDie 方法;如果是同步删除,就是直接调用 doDie 方法;doDie 方法会调用 dispatchDetachedFromWindow 方法,内部真正实现了 View 的删除逻辑。
dispatchDetachedFromWindow 方法做了四步工作:
垃圾回收,例如清除数据、消息、移除回调
通过 Session 的 remove 方法删除 Window
调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow、onDetachedFromInternal()
调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams、mDyingViews
更新 View 的 LayoutParams,更新 ViewRootImpl 的 LayoutParams,内部调用 scheduleTraversals 对 view 重绘,通过 WindowSession 更新 Window 的视图,最终调用 WindowMService 的 relayoutWindow() 具体实现。
ActivityThread
performLaunchActivity()
Activity 的 attach 方法内部,系统为 Activity 创建所属 Window 对象并设置回调,注意此时并未与 WindowManager 关联,最终 onResume 时才会完成关联:
实现了 IPolicy 定义的四个方法:
的实现 Policy,makeNewWindow
说到Window,大家都会想到所有的视图,包括Activity,Dialog,Toast,它们实际上都是附加在Window上的,Window是这些视图的管理者.今天我们就来具体将一下这些视图是如何附加在Window上的,Window有是如何管理这些视图的.
1.Window的属性和类别
当我们通过WindowManager添加Window时,可以通过WindowManger.LayoutParams来确定Window的属性和类别.其中Flags参数标示Window的属性,我们列出几个比较常见的属性:FLAG_NOT_FOCUSABLE 这个参数表示Window不需要获取焦点,也不需要接收任何输入事件
FLAG_NOT_TOUCH_MODAL 这个参数表示当前Window区域之外的点击事件传递给底层Window,区域之内的点击事件自己处理,一般默认开启
FLAG_SHOW_WHEN_LOCKED 这个属性可以让Window显示在锁屏界面上
Window不仅有属性,还有类型.Type参数表示Window的类型,分别为应用Window(activity对应的),子window(dialog对应的),和系统Window(Toast和系统通知栏).Window是分层的,每个window都有z-ordered,层级大的window会覆盖层级小的window,其大小关系为系统window>子window>应用window.所以系统window总会显示在最上边,但是使用系统window是需要声明相应的权限的.这一点需要注意.具体Type参数表示Window的类型如下
应用 Window:层级范围 1-99,优先级最低;一个应用 Window 对应一个 Activity
子 Window:1000-1999,优先级中等;例如对应一个 Dialog 。
系统 Window:2000-2999,一般为 TYPE_SYSTEM__OVERLAY 或 TYPE_SYSTEM_ERROR ,需要声明 SYSTEM_ALERT_WINDOW 权限
2.WindowManager接口
WindowManger实现了ViewManager这个接口,所提供的主要函数只有三个:public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);
每一个 Window 都对应着一个 View 和一个 ViewRootImpl
Window 和 View 通过 ViewRootImpl 建立联系
无法直接操作 Window,只能通过 WidowManager
2.1 Window的添加过程
示例代码可以在Window中简单添加一个ButtonButton btn = new Button(this); btn.setText("Button"); LayoutParams params = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED; params.gravity = Gravity.LEFT | Gravity.TOP; params.x = 100; params.y = 300; windowManager.addView(btn, params);
看看WindowManagerImpl#addView
public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); } private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();<span style="font-family: monospace;font-size:14px; white-space: pre;"></span>
接着看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 { // 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; } } ViewRootImpl root; View panelParentView = null; 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); } // do this last because it fires off messages to start doing things 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; } }顺便看下这个类里面比较重要的成员变量
// 存储所有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,但是没有完成的 private final ArraySet<View> mDyingViews = new ArraySet<View>();WindowManagerGlobal#addView按照书中精简一下代码就是
// 创建ViewRootImpl,然后将下述对象添加到列表中 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams);//设置Params mViews.add(view);//window列表添加 mRoots.add(root);//ViewRootImpl列表添加 mParams.add(wparams);//布局参数列表添加最后添加要加入的View
// 通过ViewRootImpl的setView来完成 root.setView(view, wparams, panelParentView);在ViewRootImpl的setView函数中,会调用requestLayout来完成异步刷新,然后在requestLayout中调用scheduleTraversals来进行view绘制.
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); // 实际View绘制的入口 } }
最后通过WindowSession来完成Window的添加过程,它是一个Binder对象,通过IPC调用来添加window,sheduleTraversals()会调用到下面的方法
mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);
所以,Window的添加请求就交给WindowManagerService去处理,在其内部为每个应用保留一个单独的Session.
2.2 Window的删除过程
WindowManagerGlobal.removeView()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的index 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); } }removeView先通过findViewLocked来查找待删除的View的索引,然后用removeViewLocked来做进一步删除.
private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); //获得当前的view的viewRootImpl View view = root.getView(); if (view != null) { //先让imm下降 InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); //die方法只是发送一个请求删除的消息之后就就返回 if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view);//加入dyingView } } }WindowManagerGlobal
通过 ViewRootImpl 来完成删除操作,在WindowManager中提供了两种删除接口removeVIew()和removeViewImmediate(),它们分别表示异步和同步删除(不常用).而异步操作中会调用die函数,来发送一个MSG_DIE消息来异步删除,ViewRootImpl的Handler会调用doDie(),而如果是同步删除,那么就直接调用doDie(),然后在removeView函数中把View添加到mDyingViews中.
ViewRootImpl.die()
boolean die (boolean immediate) { if (immediate && !mIsInTraversal) { doDia(); return flase; } if (!mIsDrawing) { destroyHardwareRenderer(); } else { Log.e(TAG, "尝试摧毁正常绘制中的 Window"); } // ViewRootImpl 的 mHandler 将处理此消息并调用 doDie mHandler.sendEmptyMessage(MSG_DIE); return true; }
当 immediate 为 false,使用异步删除,就发送一个 MSG_DIE 的消息,ViewRootImpl 的 mHandler 将处理此消息并调用 doDie 方法;如果是同步删除,就是直接调用 doDie 方法;doDie 方法会调用 dispatchDetachedFromWindow 方法,内部真正实现了 View 的删除逻辑。
dispatchDetachedFromWindow 方法做了四步工作:
垃圾回收,例如清除数据、消息、移除回调
通过 Session 的 remove 方法删除 Window
mWindow.remove(mWindow) WindowManagerService.removeWindow()
调用 View 的 dispatchDetachedFromWindow 方法,内部调用 View 的 onDetachedFromWindow、onDetachedFromInternal()
调用 WindowManagerGlobal 的 doRemoveView 方法刷新数据,包括 mRoots、mParams、mDyingViews
2.3 Window 的更新过程
WindowManagerGlobal.updateViewLayoutpublic void updateViewLayout(View view, ViewGroup.LayoutParams params) { ..... 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);//这是主要的方法 } }
更新 View 的 LayoutParams,更新 ViewRootImpl 的 LayoutParams,内部调用 scheduleTraversals 对 view 重绘,通过 WindowSession 更新 Window 的视图,最终调用 WindowMService 的 relayoutWindow() 具体实现。
3.Window的创建
学习完第二章我就有个疑问:WindowManager提供的接口只有Window的增删改,那么Window的创建到底是在哪进行的?不同类型的Window的创建过程不同,这里我只来讲一下Activity的Window的创建过程。在Window的启动过程中,会调用attach()函数来为其关联运行过程中所依赖的一系列上下文环境变量,这里的流程也可以参照setContentView源码浅析中的第三点。ActivityThread
performLaunchActivity()
java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); ... if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); // 为 Activity 关联运行过程中所依赖的一系列上下文环境变量 activity.attch(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor); }在
Activity 的 attach 方法内部,系统为 Activity 创建所属 Window 对象并设置回调,注意此时并未与 WindowManager 关联,最终 onResume 时才会完成关联:
mWindow = PolicyManager.makeNewWindow(this); // 当 Window 接收到外界状态改变时回调 Activity 的接口实现 // onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); }PolicyManager
实现了 IPolicy 定义的四个方法:
public interface IPolicy { public Window makeNewWindow(Context context); public LayoutInflater makeNewLayoutInflater(Context context); public WindowManagerPolicy makewNewWindowManager(); public FallbackEventHandler makeNewFallbackEventHandler(Context context); }PolicyManager
的实现 Policy,makeNewWindow
public Window makeNewWindow(Context context) { return new PhoneWindow(context); }Window对象是通过PolicyManager的makeNewWindow方法实现的,由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变时就会回调Activity的对应方法.而我们去追寻Window的具体实现类,会发现它就是PhoneWindow,而Activity中最常用的setContentView方法的具体操作都是在PhoneWindow的相应方法中实现的.至于setContentView的具体流程请参考setContentView源码解析。
相关文章推荐
- Android中的动画具体解释系列【1】——逐帧动画
- 使用清华镜像下载Android源码
- Android手机如何录制屏幕及转GIF
- Android 多种方式正确的加载图像,有效避免oom
- Android 获取USB设备的类型
- Android开源控件ViewPager Indicator的使用方法
- Android 开源框架ViewPageIndicator 和 ViewPager 仿网易新闻客户端Tab标签
- Android自定义控件之仿知乎详情页
- Android项目欢迎界面实现方式及代码
- Android中跳转到系统设置界面大全
- android超级课程表原理(各大高校教务平台的数据获取原理)
- How is android.Manifest.class created?
- Android自定义View的实现方法,带你一步步深入了解View
- Android SQLite操作封装
- Android-Canvas.save() Canvas.restore() 总结
- Android 动画 —— 属性动画
- Android 在Activity中获取控件尺寸的方法
- Android 仿QQ界面的实现
- Android 禁止ViewPager滑动
- Android事件分发机制之View篇