【Android API】理解Window和WindowManager

来源:互联网 发布:中国影响力人物数据库 编辑:程序博客网 时间:2024/05/29 15:33

【★★☆☆☆】我们在初学Android的时候总是会听到别人说Android视图的层级关系,然后最外层就是一个Window,但Window是一个很抽象的概念,不容易理解,那么本文就带大家从源码的角度分析一下Window到底是个什么东西
  系统定义了3种类型的Window,在WindowManager中定义为一个常数:应用级别的Window(1-99)、子Window(1000-1999)、系统级别的Window(2000-2999)。

认识Activity的Window

  从Activity的启动到onCreate的调用,到Activity显示在屏幕上,这个过程我们在前文已经分析过,startActivity流程分析 &setContentView流程分析 ,对此过程不熟悉的小伙伴建议先去看看。
  由上面我们可以知道Activity在调用onCreate之前就会调用本身的attach方法,这个方法里面会new 一个PhoneWindow,PhoneWindow是Window的子类,而且还会调用window.setWindowManager方法,这个方法里面会new 一个WindowManagerImpl对象,这个对象就是WindowManager这个接口的实现类。
  到此,我们知道在onCreate调用前系统就会new一个Window和WindowManager(的实现类)出来,所以一个Activity持有一个Window对象,和一个WindowManager对象,他们是1对1的关系。
  紧接着这些对象创建完毕后,我们调用onCreate中的setContentView方法,其实最终调用的就是PhoneWindow的setContentView方法,这个方法里面会创建一个DecorView对象(Decor为装饰的意思),这个DecorView继承自FrameLayout,里面还会创建一个ViewGroup,这个ViewGroup 变量名是mContentRoot,顾名思义,这个ViewGroup就是屏幕中显示的内容的”根节点”了,我们从setContentView流程分析 这篇文章中还可以知道,这个mContentRoot的实例是R.layout.screen_simple,这个文件的地址在这,我们可以看到里面只有一个垂直的LinearLayout,里面有一个ViewStub和一个FrameLayout那个ViewStub是用来填充顶部状态栏的,而那个id为content的FrameLayout就是我们setContentView最终放置的地方。
所以这个包含关系为
包含关系
  由此图我们知道Activity和Window都是一个抽象的对象,里面只是负责一些逻辑,而真正显示的视图是从DecorView开始的。
  最后在Activity的onResume方法调用后,会调用刚刚创建的WindowManager的addView(mDecorView),而这个方法中会new 一个ViewRootImpl对象,并且调用setView方法,将DecorView传入ViewRootImpl中,在setView方法中会通过进程间通信(IPC)将ViewRootImpl对象传递到WindowManagerService中,至此我们的视图已经交给WindowManagerService来处理了。
  所以Window是一个抽象的概念,ViewRootImpl也是一个抽象的概念,他们都持有DecorView,而DecorView持有状态栏和我们自己定义的视图。所以真正视图的根在DecorView,然后这个DecorView由持有他的ViewRootImpl来同一管理。

认识Dialog的Window

  一个弹窗的视图又如何显示在屏幕上的呢?我们可以去看Dialog这个类的用法。

Dialog dialog = new Dialog(context);dialog.setContentView(R.layout.xxx);dialog.show();

  进入源码看一下,原来在Dialog创建的时候也是new 了一个PhoneWindow,然后获取到一个WindowManager对象
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
  然后setContentView也是和Activity一样调用的是PhoneWindow的setContentView,里面同样会创建一个DecorView,然后将我们自己的视图放入DeocrView中。
  然后show()方法通过WindowManager的addView方法来将PhoneWindow的DecorView来添加到屏幕上。
  另外说一句,Dialog传过来的Context必须是Activity,不能是Application或者Service中的Context,否则会报一个BadTokenException的异常,Token是一个IBinder的实例,是我们App显示Window的一种认证,如果Window没有Token,那么就无法显示,这样做的好处是保证Android系统的安全性,如果没有这种机制,那么我们就可以在用户操作别的App时弹出一个界面,如果是一些欺诈的信息诱导你输入密码什么的就很危险了。
  而我们的Dialog是一种子Window,它需要依赖父Window的Token,就是Activity的Token,而Application和Service都是没有Token的,所以不能用他们的Context来做显示,否则会抛出异常。
  但是我们经常提到一个问题,就是如何在Service中开启一个Dialog,可以有两种做法,一种是开启一个Activity,这个Activity的背景是透明的,样式设计成和Dialog的一样即可。一种是我们直接向WindowManager中添加一个我们自定义的View,但是必须将这个属性设置为系统级别的Window,还要加上一个权限才能允许我们的App来弹出系统级别的Window。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);WindowManager.LayoutParams params = new WindowManager.LayoutParams();params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;//系统级别wm.addView(new View(),params);

认识Toast的Window

  其实Toast没有创建Window,他是直接调用WindowManager的addView方法来将Toast视图显示在屏幕上,但是通过上面我们知道,Window只是一个抽象的概念,实际addView的参数是Window中的DecorView,Window只是来管理DecorView,所以,我们可以认为Toast就是一个Window,它直接管理自己的视图,不需要Window这个对象了。
  最后Toast的WindowManager.LayoutParams的type是WindowManager.LayoutParams.TYPE_TOAST类型的,所以这是一个系统级别的Window,所以它不需要必须是Activity的Context,所以我们在Service中照样能弹出Toast。

认识PopupWindow的Window

  同样,PopupWindow也是通过WindowManager的addView来将视图显示到屏幕上的,它的type是WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,所以他要依附于Activity的Context。 #PANEL是嵌入的意思
  他也没有创建Window对象,因为它本身就可以当做Window!