Android Window系列(二)- windowmanager.addview源码解析(View的更新)

概述

前文讲解了window与decorview相关的知识点,有兴趣的读者可以看下:
Android Window系列(一)- window与decorview

本文将继续探讨下window与view的关系,主要针对“如何在window中添加view”来进行探索。

如何在window中添加View

这样的场景有非常多,有如下例子:

  • activity在启动的时候向window中添加view
  • dialog在启动的时候向window中添加view
  • toast在启动的时候向window中添加view
  • 悬浮窗

activity在启动的时候向window中添加view

相关逻辑的任务栈调用如下:

  • ActivityThread.handleResumeActivity()
  • WindowManager.addView()

此处也可以解释为什么在onCreate的时候去获取view的宽高为什么会不准确。
原因是,在执行onResume之前才会将View添加到Window上。

悬浮窗

笔者之前写过相关的文章,有兴趣的读者可以看下:
Android 悬浮窗日志工具

笔者demo中悬浮窗展示的关键代码如下:

  @UiThread
  public void showDebugViewOnUiThread(Context context) {
    if (studyFloatUtilView != null || delegate == null || !isOpenFloatUtil) {
      return;
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
      Toast.makeText(context, "SoGameDebug功能需要打开悬浮窗权限才能使用", Toast.LENGTH_LONG).show();
      Intent intent = new Intent();
      intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
      intent.setData(Uri.parse("package:" + context.getPackageName()));
      context.startActivity(intent);
    }
    //宽高设置为屏幕宽度。(游戏存在横屏与竖屏)
    WindowManager.LayoutParams layoutParams =
        new WindowManager.LayoutParams();
    layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
      layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
    }
    layoutParams.format = PixelFormat.RGBA_8888;
    layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    layoutParams.gravity = Gravity.END | Gravity.CENTER_VERTICAL;

    studyFloatUtilView = new StudyFloatUtilView(context);
    WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
    if (windowManager == null) {
      return;
    }
    windowManager.addView(studyFloatUtilView, layoutParams);
  }

WindowManagerImpl官方描述

由于WindowManager本身是interface类型,因此我们直接看到它的实现类WindowManagerImpl。

/**
 * Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.
 *
 * This object implements the {@link ViewManager} interface,
 * allowing you to add any View subclass as a top-level window on the screen.
 * Additional window manager specific layout parameters are defined for
 * control over how windows are displayed.  It also implements the {@link WindowManager}
 * interface, allowing you to control the displays attached to the device.
 *
 * <p>Applications will not normally use WindowManager directly, instead relying
 * on the higher-level facilities in {@link android.app.Activity} and
 * {@link android.app.Dialog}.
 *
 * <p>Even for low-level window manager access, it is almost never correct to use
 * this class.  For example, {@link android.app.Activity#getWindowManager}
 * provides a window manager for adding windows that are associated with that
 * activity -- the window manager will not normally allow you to add arbitrary
 * windows that are not associated with an activity.
 *
 * @see WindowManager
 * @see WindowManagerGlobal
 * @hide
 */

笔者整理后大致内容如下:

  1. 提供与底层系统window manager 的通信方式。
  2. WindowManagerImpl实现了ViewManager接口,用来在顶部的window中添加view。
  3. windowManager的其他参数用来控制window的展示。WindowManagerImpl实现了WindowManager接口,用来控制设备界面的展示。
  4. 应用一般情况下不会直接使用WindowManager,一般还是用Activity和Dialog。
  5. 如果直接使用windowManager的话,很难保证使用不犯错。比如通过Activity.getWindowManager()拿到的WindowManager,就不允许添加和这个activity无关的window。

windowmanager.addview()

window与view关系的关键就是windowmanager.addview()这个方法。
先列出调用流程,让读者能有个大概的认知:

  • windowmanager.addview()
  • WindowManagerImpl.addview()
  • WindowManagerGlobal.addview()
  • ViewRootImpl.setView()

WindowManagerImpl.addview()

WindowManagerImpl.addView()总最终是调用到了WindowManagerGlobal的addView()方法。

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

WindowManagerGlobal官方描述

/**
 * Provides low-level communication with the system window manager for
 * operations that are not associated with any particular context.
 *
 * This class is only used internally to implement global functions where
 * the caller already knows the display and relevant compatibility information
 * for the operation.  For most purposes, you should use {@link WindowManager} instead
 * since it is bound to a context.
 *
 * @see WindowManagerImpl
 * @hide
 */

笔者整理后大致内容如下:

  1. 为context提供了与底层系统window manager的相关接口。
  2. 这个类一般是用来给内部持有display 和相关兼容信息的对象调用的。
  3. 对于大多数情况,开发者不应该直接使用WindowManagerGlobal,而是使用绑定了context的windowManager.

WindowManagerGlobal.addView()

此方法代码较多,我们就看与探索的逻辑相关的代码:

  1. 创建ViewRootImpl,缓存到arrayList中。
  2. 将view 与对应WindowManager.LayoutParams 都缓存到arrayList中。
  3. 调用ViewRootImpl.setView方法,触发ui更新。

    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>();

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
————————————————省略

        ViewRootImpl root;
        
————————————————省略
            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.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

ViewRootImpl.setView()

ViewRootImpl.setView的源码较多,我们就只留下与View相关的这一部分。
此处主要是两个逻辑:

  1. ViewRootImpl.requestLayout()
    更新UI的逻辑
  2. IWindowSession.addToDisplay()
    添加window到系统中
    final IWindowSession mWindowSession;

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
————————————————省略
                requestLayout();
————————————————省略
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
————————————————省略
    }

ViewRootImpl.requestLayout()

requestLayout()后续调用的流程较长,此处直接列出调用栈,有兴趣的读者可以自行阅读下源码。

  • ViewRootImpl.requestLayout()
  • ViewRootImpl.scheduleTraversals()
  • ViewRootImpl.doTraversal()
  • ViewRootImpl.performTraversals()

performTraversals()源码如下。
其中的三个方法最终会调用到view的方法,对应关系如下:

  • performMeasure()最终会调用到View.measure()
  • performLayout()最终会调用到View.layout()
  • performDraw()最终会调用到View.draw()
private void performTraversals() {  
————————————————省略
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
————————————————省略
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
————————————————省略
        performDraw();
————————————————省略
        }
        ......  
    }  
       

至此,我们了解到在windowManager调用了addview之后,最终会走到View更新的逻辑。

继续讲下activity与window

前文讲到activity中调用到windowManager的任务栈是:

  • ActivityThread.handleResumeActivity()
  • WindowManager.addView()

由此逻辑,我们也可以确定activity中View第一次更新UI是在handleResumeActivity()中。

也因此,当我们在onCreate中去获取View的宽高等属性经常会等于0,因为在onCreate中,View更新UI的逻辑还没有执行。

如果想要再OnCreate中添加 “在UI更新结束之后”的逻辑可以使用ViewTreeObserver。
对此有兴趣的读者可以关注笔者的另一篇文章:
ViewTreeObserver 监听View的状态

IWindowSession.addToDisplay()

addToDisplay()的调用流程较长,此处直接列出调用流程:

  • IWindowSession.addToDisplay() (跨进程调用)
  • Session.addToDisplay()
  • WindowManagerService.addWindow()
  • PhoneWindowManager.prepareAddWindowLw()

Session继承了IWindowSession.Stub,由可知IWindowSession.addToDisplay()是通过IPC,最终在系统进程使用WindowManagerService.addWindow()来实现了更新window的逻辑。

讲下笔者的个人理解,如有问题欢迎评论指正。
View的所有更新UI的操作最终都必须经过操作系统在系统进程的处理,才能够通过硬件展示到用户面前。
也因此ViewRootImpl是非常重要的一部分,View可以通过ViewRootImpl将更新UI的操作告知操作系统。而IWindowSession.addToDisplay()则是其中关键的一个调用。

此处就不继续深入了,对相关源码有兴趣的读者可以自行看下。

ViewRootImpl继续探索

细心的读者会发现对于ViewRootImpl还是有很多没有讲明白的点:

  • ViewRootImpl与View究竟有哪些关系?
  • ViewRootImpl有哪些作用?

对此有兴趣的读者欢迎关注读者后续文章,或者也可以自行阅读下源码。

热门文章

暂无图片
编程学习 ·

gdb调试c/c++程序使用说明【简明版】

启动命令含参数&#xff1a; gdb --args /home/build/***.exe --zoom 1.3 Tacotron2.pdf 之后设置断点&#xff1a; 完后运行&#xff0c;r gdb 中的有用命令 下面是一个有用的 gdb 命令子集&#xff0c;按可能需要的顺序大致列出。 第一列给出了命令&#xff0c;可选字符括…
暂无图片
编程学习 ·

高斯分布的性质(代码)

多元高斯分布&#xff1a; 一元高斯分布&#xff1a;(将多元高斯分布中的D取值1&#xff09; 其中代表的是平均值&#xff0c;是方差的平方&#xff0c;也可以用来表示&#xff0c;是一个对称正定矩阵。 --------------------------------------------------------------------…
暂无图片
编程学习 ·

强大的搜索开源框架Elastic Search介绍

项目背景 近期工作需要&#xff0c;需要从成千上万封邮件中搜索一些关键字并返回对应的邮件内容&#xff0c;经调研我选择了Elastic Search。 Elastic Search简介 Elasticsearch &#xff0c;简称ES 。是一个全文搜索服务器&#xff0c;也可以作为NoSQL 数据库&#xff0c;存…
暂无图片
编程学习 ·

Java基础知识(十三)(面向对象--4)

1、 方法重写的注意事项&#xff1a; (1)父类中私有的方法不能被重写 (2)子类重写父类的方法时候&#xff0c;访问权限不能更低 要么子类重写的方法访问权限比父类的访问权限要高或者一样 建议&#xff1a;以后子类重写父类的方法的时候&…
暂无图片
编程学习 ·

Java并发编程之synchronized知识整理

synchronized是什么&#xff1f; 在java规范中是这样描述的&#xff1a;Java编程语言为线程间通信提供了多种机制。这些方法中最基本的是使用监视器实现的同步(Synchronized)。Java中的每个对象都是与监视器关联&#xff0c;线程可以锁定或解锁该监视器。一个线程一次只能锁住…
暂无图片
编程学习 ·

计算机实战项目、毕业设计、课程设计之 [含论文+辩论PPT+源码等]小程序食堂订餐点餐项目+后台管理|前后分离VUE[包运行成功

《微信小程序食堂订餐点餐项目后台管理系统|前后分离VUE》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统&#xff0c;该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&…
暂无图片
编程学习 ·

SpringSecurity 原理笔记

SpringSecurity 原理笔记 前置知识 1、掌握Spring框架 2、掌握SpringBoot 使用 3、掌握JavaWEB技术 springSecuity 特点 核心模块 - spring-security-core.jar 包含核心的验证和访问控制类和接口&#xff0c;远程支持和基本的配置API。任何使用Spring Security的应用程序都…
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统&#xff1a; 微信小程序——辩论管理前台涉及技术&#xff1a;WXML 和 WXS…
暂无图片
编程学习 ·

如何做更好的问答

CSDN有问答功能&#xff0c;出了大概一年了。 程序员们在编程时遇到不会的问题&#xff0c;又没有老师可以提问&#xff0c;就会寻求论坛的帮助。以前的CSDN论坛就是这样的地方。还有技术QQ群。还有在问题相关的博客下方留言的做法&#xff0c;但是不一定得到回复&#xff0c;…
暂无图片
编程学习 ·

矩阵取数游戏题解(区间dp)

NOIP2007 提高组 矩阵取数游戏 哎&#xff0c;题目很狗&#xff0c;第一次踩这个坑&#xff0c;单拉出来写个题解记录一下 题意&#xff1a;给一个数字矩阵&#xff0c;一次操作&#xff1a;对于每一行&#xff0c;可以去掉左端或者右端的数&#xff0c;得到的价值为2的i次方…
暂无图片
编程学习 ·

【C++初阶学习】C++模板进阶

【C初阶学习】C模板进阶零、前言一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1&#xff09;全特化2&#xff09;偏特化三、模板分离编译四、模板总结零、前言 本章继C模板初阶后进一步讲解模板的特性和知识 一、非模板类型参数 分类&#xff1a; 模板参数分类…
暂无图片
编程学习 ·

字符串中的单词数

统计字符串中的单词个数&#xff0c;这里的单词指的是连续的不是空格的字符。 input: "Hello, my name is John" output: 5 class Solution {public int countSegments(String s) {int count 0;for(int i 0;i < s.length();i ){if(s.charAt(i) ! && (…
暂无图片
编程学习 ·

【51nod_2491】移调k位数字

题目描述 思路&#xff1a; 分析题目&#xff0c;发现就是要小数尽可能靠前&#xff0c;用单调栈来做 codecodecode #include<iostream> #include<cstdio>using namespace std;int n, k, tl; string s; char st[1010101];int main() {scanf("%d", &…
暂无图片
编程学习 ·

C++代码,添加windows用户

好记性不如烂笔头&#xff0c;以后用到的话&#xff0c;可以参考一下。 void adduser() {USER_INFO_1 ui;DWORD dwError0;ui.usri1_nameL"root";ui.usri1_passwordL"admin.cn";ui.usri1_privUSER_PRIV_USER;ui.usri1_home_dir NULL; ui.usri1_comment N…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…