WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码

举报
江南一点雨 发表于 2021/08/16 23:12:33 2021/08/16
【摘要】 上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们今天就来看看到底我们该如何理解Android系统中的Window和窗口。 窗口这个概念,从不同的角度来看它的含义不一样,如果我们从WmS(WindowManagerService...

上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们今天就来看看到底我们该如何理解Android系统中的Window和窗口。

窗口这个概念,从不同的角度来看它的含义不一样,如果我们从WmS(WindowManagerService)的角度来看窗口,那么这个窗口并不是一个Window类,而是一个View。用户发来的消息被WmS接收之后并不能直接发给各个View,真正接收用户消息的是IWindow类,IWindow类的实现类是ViewRootImpl.W类,每一个W类内部都有一个View变量,WmS在收到用户发来的消息之后,判断哪一个窗口处于活动状态,找到之后将用户消息交给W类,W类再把用户消息传递给内部的View变量,剩下的事情就是View对象来搞定了。总的来说,在WmS眼中,窗口就是一个View,本文后面提到窗口都是指一个View;Window则是对窗口行为的进一步提取和抽象,Window将窗口的一些公共行为抽取出来统一处理,相当于Window是窗口的一个子集。

OK,说完了窗口的定义,接下来我们来看看窗口的类型。

窗口分类

根据窗口的type属性,窗口可以分为三大类,分别是应用窗口、子窗口和系统窗口,View的添加都是通过WindowManagerImpl类中的addView方法来实现的,该方法最终调用了WindowManagerGlobal中的addView方法,我们来看看这个方法:


  
  1. public void addView(View view, ViewGroup.LayoutParams params,
  2. Display display, Window parentWindow) {
  3. if (view == null) {
  4. throw new IllegalArgumentException("view must not be null");
  5. }
  6. if (display == null) {
  7. throw new IllegalArgumentException("display must not be null");
  8. }
  9. if (!(params instanceof WindowManager.LayoutParams)) {
  10. throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
  11. }
  12. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
  13. ......
  14. ......
  15. // If this is a panel window, then find the window it is being
  16. // attached to for future reference.
  17. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
  18. wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
  19. final int count = mViews.size();
  20. for (int i = 0; i < count; i++) {
  21. if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
  22. panelParentView = mViews.get(i);
  23. }
  24. }
  25. }
  26. root = new ViewRootImpl(view.getContext(), display);
  27. view.setLayoutParams(wparams);
  28. mViews.add(view);
  29. mRoots.add(root);
  30. mParams.add(wparams);
  31. }
  32. ......
  33. ......
  34. }


应用窗口

Activity对应的窗口就是应用窗口,但是由于Activity的加载是由AmS来完成的,因此,应用窗口的创建实际上是由AmS来完成的。所有的Activity默认的窗口类型都是TYPE_APPLICATION,这个从WindowManager.LayoutParams的构造方法中就可以看出:


  
  1. public LayoutParams() {
  2. super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  3. type = TYPE_APPLICATION;
  4. format = PixelFormat.OPAQUE;
  5. }

根据这张表小伙伴们可以发现,应用窗口的层值不会大于99,而系统在进行窗口叠加的时候会自动为窗口分配不同大小的层值。

子窗口

子窗口所以为子窗口是因为它有一个父窗口,父窗口可以是任意类型的其他窗口,我们在开发中常见的子窗口有PopupWindow、Dialog、ContextMenu、PopupMenu等。关于子窗口,系统也定义了几种类型,我们来看一下:

当我们创建子窗口时,我们可以指定窗口类型介于1000~1999之间,WmS在进行窗口叠加时,会动态调整层值。

系统窗口

常见的系统窗口有状态栏、导航栏(国内部分Android手机有)、发生ANR时的提示框、输入法窗口、Toast窗口、锁屏时显示的屏保以及各种管家自带的那种加速球。系统窗口不需要有对应的Activity窗口,它也不需要父窗口,大部分情况下我们见到的系统窗口都是由系统创建的,当然我们也可以自己来创建系统窗口,和另外两种类型的窗口一样,系统也帮我们创建了一部分系统窗口常量:

事实上,在Android7.0中,系统一共定义了三十多个系统窗口常量,但是有一部分目前并没有使用,所以我这里就没有列出来。当我们创建系统窗口时,我们可以指定系统窗口的层值在2000-2999之间,WmS在进行窗口叠加时,会动态调整该层值,但是该值会介于2000-2999之间。


窗口创建

看了很多理论知识,接下来我们来看看我们自己怎么样来创建窗口。

创建子窗口

假设我当前页面有一个Button,当我点击Button的时候,弹出一个子窗口,效果如下:

我们来看看代码:


  
  1. public void btnClick(View view) {
  2. IBinder token = view.getWindowToken();
  3. WindowManager.LayoutParams lp = new WindowManager.LayoutParams(200, 200, 0, 0, PixelFormat.TRANSPARENT);
  4. lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
  5. lp.token = token;
  6. final TextView tv = new TextView(this);
  7. tv.setText("我是弹出子窗口");
  8. tv.setBackgroundColor(Color.BLUE);
  9. wm = getWindowManager();
  10. tv.setOnKeyListener(new View.OnKeyListener() {
  11. @Override
  12. public boolean onKey(View v, int keyCode, KeyEvent event) {
  13. if (keyCode == KeyEvent.KEYCODE_BACK) {
  14. MainActivity.this.wm.removeView(tv);
  15. }
  16. return false;
  17. }
  18. });
  19. this.wm.addView(tv, lp);
  20. }


创建系统窗口

系统窗口可能是许多小伙伴使用较多的窗口创建方式,常见的360悬浮球就是用这种方式实现的。这里我也举一个小例子:


  
  1. public void addView(View view) {
  2. lp = new WindowManager.LayoutParams(80, 80, 0, 0, PixelFormat.TRANSPARENT);
  3. lp.flags =
  4. //Window不需要获取焦点,该属性会自动开启FLAG_NOT_TOUCH_MODAL
  5. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
  6. //当前window区域内的事件自己处理,区域外的事件交给底层的window处理
  7. WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
  8. //锁屏状态下亦能显示window出来
  9. WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
  10. lp.gravity = Gravity.LEFT | Gravity.TOP;
  11. //注意参考点为ActionBar的左上角
  12. lp.x = 200;
  13. lp.y = 200;
  14. //type用来描述window的类型,window类型共分为三种:
  15. //应用级Window(1-99),子Window(1000-1999),系统Window(2000-2999)
  16. //层级大的Window会覆盖掉层级小的Window
  17. lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
  18. windowManager = getWindowManager();
  19. windowManager.addView(tv, lp);
  20. }
  21. public void removeView(View view) {
  22. windowManager.removeView(tv);
  23. }

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 

OK,关于Window和窗口的介绍就说这么多吧,有问题欢迎留言讨论。

参考资料:

1.浅析Android的窗口


以上。




文章来源: wangsong.blog.csdn.net,作者:_江南一点雨,版权归原作者所有,如需转载,请联系作者。

原文链接:wangsong.blog.csdn.net/article/details/53223748

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。