Android中Window的管理深入讲解
一、理解Android的Window
Window表示一个窗口的概念,是一个抽象的概念,每一个Window都对应一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在。
Android中的每个窗口View都有一个对应的Window,例如Activity、Dialog,在他们初始化的时候就会为其创建对应的PhoneWindow并赋值到其内部的一个引用
window的层级
WindowLayoutParams.setType设置
每个window都有其对应的层级,应用window在1-99,子window在1000-1999,系统window在2000-2999,层级高的会覆盖层级低的
子window必须依赖于父window存在,例如Dialog必须在Activity中弹出,Dialog中的window为子window,Activity中的window为父window
显示系统级别的window需要权限
WindowLayoutparams的flags
FLAG_NOT_FOUCSABLEwindow不需要获取焦点,也不需要接收各种输入事件,回同时启用FLAG_NOT_TOUCH_MODAL
FLAG_NOT_TOUCH_MODAL系统会将当前Window区域外的单击事件传递给底层的Window,在当前Window区域内的事件则自己处理
FLAG_SHOW_WHEN_LOCKED开启此模式让window显示在锁屏界面上
二、理解Android中的WindowManager
Android中对Window的管理都是通过WindowManager来完成的,创建PhoneWindow之后还会为该Window对象设置WindowManager,WindowManager是一个接口继承ViewManager接口,从这里也能看出对Window的操作其实就是对View的操作,WindowManager的实现类是WindowMangerImpl,WindowMangerImpl通过new创建。
三、Window与WindowManagerImpl的关联
通过ContextImpl的getSystemService可以得到WindowManagerImpl实例,同一ContextImpl得到的是同一个WindowManagerImpl对象,得到WindowMangerImpl之后,调用Window的setWindowManager方法建立Window与WindowManagerImpl之间的联系。
setWindowManager中主要完成在WindowManagerImpl实例的基础上重新创建一个与当前Window绑定的WindowManagerImpl,并为Window中的属性mWindowManager赋值
也就是说在Java层上Window与WindowManager建立了第一步联系,并将Activity、Dialog等中的WindowManager赋值为新的WindowManagerImpl对象。
注意:这里是使用单例的WindowManagerImpl,结合不同的Window,最后构建了与Window有关联的非单例的WindowManagerImpl对象
四、对 Window的操作
1.添加操作WindowManagerImpl.addView,注意,是添加一个新的Window,不是对一个Window中的view做操作
Android中每显示一个窗口,其实就是将View显示到屏幕的过程,如果我们自定义一个要显示的布局,拿到View对象,这时候只要调用WindowManagerImpl对象的addView方法就行了,通过ContextImpl的getSystemService可以得到WindowManagerImpl实例
WindowManagerImpl对象,在WindowManagerImpl中存在一个单例存在的WindowManagerGlobal对象,在WindowManagerImpl的各个方法中,将任务的执行过程传递到了WindowManagerGlobal中,在传递过程中除了将View、LayoutParams传递,还将WindowManagerImpl中关联的window对象也一起传递
WindowManagerGlobal的addView方法
WindowMAnagerGlobal中判断view、LayoutParams等参数合法性,创建ViewRootImpl,将ViewRootImpl、View、LayoutParams添加到WindowMAnagerGlobal中对应的ArayyList集合中,再调用ViewRootImpl的setView方法
ViewRootImpl
继承自Handler类,是作为native层和Java层View系统通信的桥梁
ViewRootImpl创建时保存了创建其的线程的引用,开发过程中更新View时会判断当前线程是否是创建ViewRootImpl的线程,如果不是会抛出异常。
一般都是在主线程中创建ViewRootImpl,所以在子线程更新UI会抛出异常,是因为ViewRootImpl是UI线程中创建的,并不是因为只有UI线程才可以更新UI
在Activity的onResume之前如果在子线程中修改UI是不会抛出异常的,因为在onResume之后才创建ViewRootImpl,这时更新UI需要经过ViewRootImpl来更新,在onResume之前Activity的屏幕并没有显示,修改UI操作只是会修改layout中的UI,并不会调用ViewRootImpl的方法显示到屏幕上。
所以得出结论,只有UI显示到屏幕上之后,在更新UI时就会判断线程是否为创建UI的线程,如果不匹配则抛出异常,在UI没有显示到屏幕上时更新UI是不会进行线程判断的
ViewRootImpl的setView方法:
- setView方法中会首先调用requestLayout()方法,在这里进行线程判断,如果线程匹配则调用scheduleTraversals()完成View的测量、布局、绘制过程
- setView中接下来远程调用IWindowSession对象中的addToDisplay方法,将window等信息传递到NMS中调用NMS的addWindow方法完成最后window在屏幕上的展示。
IWindowSessionWindowManagerService
这里是将View显示到屏幕上的关键,是将View从应用进程传递到系统进程然后完成显示的地方。ViewRootImpl中的IWindowSession对象是通过WindowManagerGlobal的静态方法getWindowSession()得到的,该方法中首先通过ServiceManager通过Binder进行IPC得到系统服务WindowsManagerService在应用进程的远程代理,然后通过AIDL通信的方式调用WMS的openSession()方法得到IWindowSession接口的实现类Session类远程代理对象,Session类也是一个Binder所以可以跨进程传递
在Session的远程代理的addToDisplay方法中通过AIDL调用Session的addToDisplay方法将window信息传递到系统进程,然后调用AMS的addWindow方法,AMS最后将Window中的View显示到屏幕上
2.删除操作,是删除一个屏幕上已有的Window
WindowManagerImpl.removeView操作中,先通过findViewLocked查找要删除的View,再通过View找到对应的Window的ViewRootImpl,将View、LayoutParams、ViewRootImpl从相对应的ArrayList中删除,再通过IPC调用Session的remove其中调用WMS的removeWindow方法,在屏幕上移除该Window对应的View
3.更新操作
WindowManagerImpl.updateViewLayout,为view设置新的LayoutParams,通过findViewLocked找到对应ViewRootImpl,删除LayoutParams集合中旧的LayoutParams,在集合原位置加入新的LayoutParams,调用ViewRootImpl的setLayoutParams完成View的重新测量,布局,绘制,最后通过IPC调用Session再调用WMS完成window的更新。
4.添加Window代码
自定义的Window在创建过程中并没有主动的创建Window,而是在显示的时候由系统维护,这里也体现了Window是一个抽象的概念,最终需要处理的还是View
privatevoidaddWindow(){ TextViewview=newTextView(this); view.setText("Text"); view.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewv){ Log.i("renxl","onClick"); } }); view.setBackgroundColor(Color.RED);//要显示的View可以是新创建的,也可以是LayoutInflater从xml布局中获取的 WindowManager.LayoutParamsmLayoutParams=newWindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);//必须是WindowManager.LayoutParams mLayoutParams.flags=WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//三种flag从中选一 mLayoutParams.type=WindowManager.LayoutParams.TYPE_TOAST;//type表示优先级 mLayoutParams.gravity=Gravity.START|Gravity.TOP; mLayoutParams.x=100;//在屏幕上X轴位置 mLayoutParams.y=300;//在屏幕上Y轴位置 WindowManagermanager=(WindowManager)getSystemService(WINDOW_SERVICE); manager.addView(view,mLayoutParams);//将View添加到界面上 } **注意,如果是系统级别的Window也就是优先级超过1999的,需要声明权限**
五、用户触摸屏幕事件处理流程
WMS将事件IPC传递到Window,Window中调用其内部的CallBack对象也就是Activity或者Dialog对象或者的方法。最终将事件传递到View,再通过ViewRootImpl将响应以后的View和对应windowIPC提交到WMS完成响应后的展示。
六、常见Window的创建
1.Activity的Window创建过程
Activity启动流程中,Activity的attach方法中为window赋值为一个新的PhoneWindow,setContentView将layout传递到PhoneWindow中,PhoneWindow中通过LayoutInflater的inflate方法加载布局View,并添加到PhoneWindow内部的DecorView中,在onResume的时候,调用WindowManager的addView方法将Window中的DecorView通过WMS显示到屏幕上
Activity在创建Window的时候,实现了Window的Callback接口中的方法,在Window收到触摸时,则会回调Callback中的方法将事件传递到Activity中,Activity中会调对应PhoneWindow中的分发方法,PhoneWindow中会调用DecordView中的方法,最终将事件传递到View中。
猜测事件由WMS传递到Window再到Activity再到Window这样多一层Activity的原因是,开发者可以在Activity中处理事件,不一定非要传递到View
2.Dialog的Window创建过程
同Activity,实例化Dialog对象时创建PhoneWindow,show方法调用时通过AIDL调用WMS的addView方法将View添加到屏幕
3.Toast的Window创建过程
Toast在创建过程中并没有主动的创建Window,而是在显示的时候由系统维护Toast的window,这里也体现了Window是一个抽象的概念,最终需要处理的还是View
Toast的工作工程需要TNNMSWMS三个部分协同完成,IN也是一个Binder,NMS中调用TN是远程访问,TN调用WMS也是远程调用
NMS即NotificationManagerService
Toast的工作过程分为两步
- 第一步是当前线程中要先生成一个Binder类型的TN的对象,远程调用NMS中的enquneue方法将TN传到NMS中,NMS远程调用TN的show方法,TN中 show方法运行在当前应用的Binder线程池中,通过Handler的post系列方法将进程切换到主线程,主线程再通过WindowManager来调用WMS中的方法完成show过程
- NMS中调用了TN的show方法之后,会通过自己内部的Handler延时发送一个时间为Toast展示时间的消息,NMS中的Handler收到消息之后,再调用TN的hide方法(远程调用过程),TN中的hide方法又会通过WindowManager远程调用WMS中的hide方法,将Toast隐藏。完成整个过程。
七、总结
屏幕展示的每一个window,都需要window和View两个相互结合,屏幕中可以有多个Window。以下所说的View都是一个Window中包含的根View
window的创建以及对View的添加,删除、更新是由WindowManager来实现的,而WindowManager中对window的操作通过每个window对应的ViewRootImpl中通过 IPC远程请求IWindowSession中的方法再调用WMS的对应方法将对当前window操作的实现到屏幕上。
每一个Window都对应一个ViewRootImpl,window通过对应的ViewRootImpl来完成对view的管理
在屏幕有用户交互的时候,WMS又会将事件传递到相应界面的Window,Window会调用当前界面的对应的CallBack来处理事件
WindowManager是接口,实现类是WindowManagerImpl,WindowManagerImpl中又通过WindowMAnagerGlobal来完成操作。典型的桥接模式
添加Window显示不出来问题
由于国内对于ROM的定制,多种机型会默认禁止应用对悬浮窗的创建,所以如果是没有显示,检查是否关闭了应用的权限。
- 安卓6.0添加了对权限的开关设置,悬浮窗权限默认是关闭的
- 一些国内定制的Rom6.0之前就可以设置权限的开关,悬浮窗权限默认关闭
问题解决
mLayoutParams.type=WindowManager.LayoutParams.TYPE_TOAST;
将type设置为TYPE_TOAST,源码中对TYPE_TOAST是没有任何限制的。
在国内定制的Rom上,只有少数机型会在设置TYPE_TOAST的时候,View的监听事件不能获取,显示都是可以的。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。