【16】Android基础知识之Window(二) - ViewRootImpl

概述

开始将ViewRootImpl了,总会担心讲不好,讲不好的点有两个,一个是自己理解不够,第二个则是自己表达不够,不能讲明白这里面的东西,不能讲到大家想要了解的东西。不过我会放下心来,慢慢讲,让大家对ViewRootImpl有个自己的了解。

ViewRootImpl

这是什么东西?如果已经看过源码的同学,亦或者有多年开发经验的人,自然不必多说。但如果没有,这里建议先看一下Android基础知识之Window(一)。

WindowManagerGlobal

从上篇文章末尾可以知道,View的添加、更新、删除操作是WindowManagerGlobal来实现的。那么我们就看看这三个方法到底做了什么吧!我们一段一段分析

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {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, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}ViewRootImpl root;View panelParentView = null;/** 省略  **/if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display,windowlessSession, new WindowlessWindowLayout());}// (1)view设置layoutparams参数view.setLayoutParams(wparams);// (2)添加view、root、wparams到它们各自的集合中去mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {// (3)root(ViewRootImpl)调用它的setView方法root.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);// BadTokenException or InvalidDisplayException, clean up.if (viewIndex >= 0) {removeViewLocked(viewIndex, true);}throw e;}}

先看添加view,平常使用ViewGroup#addView会传递View和params两个参数,在这里的addView方法中,我们也可以看到(1),把传入的layoutParams设置给传入的view。接着(2)管理了三个集合,用来存储添加的view、root(ViewRootImpl)、params。最后(3)调用创建的ViewRootImpl的setView方法,把addView接收的大部分的参数传递给它。这里我们第一次接触到了ViewRootImpl这个类。

通过上述源码分析可以看出,每次Windwo#addView(常见的是Activity#setContentView)的时候,都会创建一个ViewRootImpl对象,并且把它缓存起来。通过使用三个ArrayList集合缓存View、Root、Params,可以在其他操作的时候,从缓存中快速取出与之对应的对象进行操作。

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}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);}}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)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 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);}}

根据updateViewLayout和removeView两个方法,也确实证实了,从mRoots取出对应的ViewRootImpl进行的操作。

setView

总算进来了ViewRootImpl,这里只跟踪setView的流程,关于updateViewLayout和removeView可以自行分析一下。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {/**  省略  **/// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.requestLayout();InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}mForceDecorViewVisibility = (mWindowAttributes.privateFlags& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;if (mView instanceof RootViewSurfaceTaker) {PendingInsetsController pendingInsetsController =((RootViewSurfaceTaker) mView).providePendingInsetsController();if (pendingInsetsController != null) {pendingInsetsController.replayAndAttach(mInsetsController);}}try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();adjustLayoutParamsForCompatibility(mWindowAttributes);controlInsetsForCompatibility(mWindowAttributes);Rect attachedFrame = new Rect();final float[] compatScale = { 1f };res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,mTempControls, attachedFrame, compatScale);if (!attachedFrame.isValid()) {attachedFrame = null;}if (mTranslator != null) {mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());mTranslator.translateRectInScreenToAppWindow(attachedFrame);}mTmpFrames.attachedFrame = attachedFrame;mTmpFrames.compatScale = compatScale[0];mInvCompatScale = 1f / compatScale[0];} catch (RemoteException | RuntimeException e) {mAdded = false;mView = null;mAttachInfo.mRootView = null;mFallbackEventHandler.setView(null);unscheduleTraversals();setAccessibilityFocus(null, null);throw new RuntimeException("Adding window failed", e);} finally {if (restore) {attrs.restore();}}/**  省略  **/
}

这里的代码篇幅有点长,所以省略了很多相对不是重要的内容。上述代码需要关心两个地方,其一是requestLayout这个方法;这其二是mWindowSession.addToDisplayAsUser这个调用。

首先说一下第二点,因为这次只讲ViewRootImpl,不会讲到WMS去,这个调用会一笔带过,重点会讲一下requestLayout这个函数。

mWindowSession.addToDisplayAsUser是一个binder通信,如果还有不了解的,可以去补一下binder相关的知识。刚刚在讲WindowManagerGlobal的时候,漏了一个东西就是它持有了WMS(WindowManagerService)的代理对象。这是一个静态方法,它不仅仅是WindowManagerGlobal自己使用,还提供给外部,比如ViewRootImpl使用,通过getWindowSession就可以拿到WMS的代理对象进行binder通信。

// WindowManagerGlobal.java@UnsupportedAppUsagepublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {// Emulate the legacy behavior.  The global instance of InputMethodManager// was instantiated here.// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsageInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}});} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}

显然addToDisplayAsUser就是调用WMS的addToDisplay相关的方法,传入了window,window属性,displayId,InputChannel等信息下去。相关内容在下一篇文章会讲到。这里用黑体加粗了InputChannel,也是一个很重要的概念,之后写相关事件输入的文章会好好讲讲。

接着我们来看requestLayout的源码是怎样实现的

// ViewRootImpl.java@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}

这两段代码乍得一看很短,但实际上看也的确很短,可是能讲的内容确实很多的。我们慢慢分析,首先看到checkThread,这个有点熟悉。

    void checkThread() {Thread current = Thread.currentThread();if (mThread != current) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."+ " Expected: " + mThread.getName()+ " Calling: " + current.getName());}}

关于这个打印,我们是不是很熟悉,获取当前线程,判断当前线程会创建View的线程是不是同一个,否则抛出异常。这个打印应该不少见到过。

接着执行scheduleTraversals(安排遍历),这里的遍历是遍历什么呢【1】?这里我们先不表。继续看它里面做了什么。设置状态我们先不管,可以看到Handler先获取了Looper,再获取了Queue,然后调用了postSyncBarrier(插入同步屏障),这个Handler是主线程创建的Handler,所以默认使用的是主线程的Looper,通过消息队列插入了一个同步屏障,也就是说拦截了所有的同步消息,接下来会优先处理异步消息。这是为什么呢【2】?(这里有对同步屏障不了解的可以去看看Handler有关的内容)

再然后通过Choreography#postCallback发送一个回调,传入mTraversalRunnable来接收这个回调。这里有个重点,Choreography#postCallback可以理解为是应用层去请求Vsync信号的过程,我们知道Vsync信号在16.67ms会上来一次,也就是说这个回调每16.67ms period会上来一次。这里的Vsync信号概念以后会详细的讲解一下,这里暂时可以这么去理解。

最后通知render thread(渲染线程)去等待处理。单单这很短一段代码,我们已经有很多疑问了。我们来一一揭开。

doTraversals
    final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}

刚刚上面有提到,这个runnable每16.67ms周期上来一次,每次都会执行doTraversal(开始遍历)。在遍历执行前,会移除以前添加的同步屏障,这短短一个16.67ms周期,就先添加屏障,再移除屏障,这里就解释了之前的第二点问题,同步屏障是为了处理异步消息的,而postCallback回调的就是Vsync信号,那么由此可见Vsync信号上来的方式是异步消息的方式发送上来的。然后开始执行performTraversals(执行遍历)

 private void performTraversals() {/**  省略  **/if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()|| dispatchApplyInsets || updatedConfiguration) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,lp.privateFlags);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,lp.privateFlags);if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="+ mWidth + " measuredWidth=" + host.getMeasuredWidth()+ " mHeight=" + mHeight+ " measuredHeight=" + host.getMeasuredHeight()+ " dispatchApplyInsets=" + dispatchApplyInsets);// Ask host how big it wants to beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}/**  省略  **/final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout|| mAttachInfo.mRecomputeGlobalAttributes;if (didLayout) {performLayout(lp, mWidth, mHeight);  }    /**  省略  **/  if (!performDraw() && mActiveSurfaceSyncGroup != null) {mActiveSurfaceSyncGroup.markSyncReady();} 
}

由于这个方法实在是长,因此省略了很多细节的东西。但根据源码来看,主要函数就这三个performMeasure - performLayout - performDraw。这里解开了之前说的第一个问题,为什么是安排遍历,performMeasure、performLayout 、performDraw都会根据ViewTree递归调用它们的子View,依次循环的执行它们的measure、layout、draw。

  • 首先执行测量,根据官方给的注解也可以看出来,测量的是视图大小,传入高宽的MeasureSpec,这个值有32位,高两位是表示View的约束模式,低三十位是表示测量的大小数值,执行View#onMeasure的测量逻辑
  • 接着执行布局,可以理解为摆放位置,根据起始坐标和高宽,确定整个view的位置,执行view#onLayout的布局逻辑
  • 最后执行绘制,判断是否开启了硬件支持,决定使用硬件绘制还是软件绘制,执行view#onDraw里的逻辑开始绘制
void performDraw() {if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if (isHardwareEnabled()) {// 硬件加速绘制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else {// 软件绘制drawSoftware();}}
}

我们在分析一下这段代码,通过硬件支持的判断,执行硬件加速绘制和软件绘制。先说软件绘制,ViewRootImpl管理了一个Surface和SurfaceControl,经过软件绘制,View会绘制到Canvas上面,这个Canvas是Surface提供的,也就是说明绘制到了Surface上面。再说说硬件绘制,由ThreadedRenderer#draw进行绘制,通过view获取displayList,一个显示列表,由显示列表获取Canvas执行View#onDraw,最后将绘制命令提交给GPU,执行render开始绘制。

软件绘制
View#onDraw - Canvas - Surface - ViewRootImpl
硬件绘制
View#onDraw - Canvas - Surface - displayList - ThreadedRenderer

总结

1、WIndowManagerGlobal的添加、更新、删除由ViewRootImpl来负责
2、每个Window都对应了一个ViewRootImpl的实例对象
3、ViewRootImpl通过WMS代理对象把Window对象传递给了WMS
4、requestLayout开始周期性的请求Vsync信号并执行doTraversals
5、performTraversals开始了之后的measure、layout、draw操作

至此,我希望讲的ViewRootImpl就讲完了,这里还留了很多谜题,在一下篇文章中,会开始讲WMS和ViewRootImpl通信的流程,也就是我们addToDisplay没说完的部分。其中ViewRootImpl可能也会涉及两个重要的对象Surface和SurfaceControl。


AOSP14源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/872707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式面试高频八股文面试题及参考答案

目录 什么是嵌入式系统?请简要描述其特点。 请解释实时操作系统(RTOS)的概念。 请列举几种常见的嵌入式操作系统。 请解释中断、异常和竞态条件在嵌入式系统中的作用。 什么是死锁?请举例说明如何避免死锁的发生。 请解释进程和线程的区别。 请解释同步和互斥的概念…

美式键盘 QWERTY 布局的来历

注:机翻,未校对。 The QWERTY Keyboard Is Tech’s Biggest Unsolved Mystery QWERTY 键盘是科技界最大的未解之谜 It’s on your computer keyboard and your smartphone screen: QWERTY, the first six letters of the top row of the standard keybo…

Linux热键,shell含义及权限介绍

君子忧道不忧贫。 —— 孔丘 Linux操作系统的权限 1、几个常用的热键介绍1、1、[Tab]键1、2、[ctrl]-c1、3、[ctrl]-d1、4、[ctrl]-r 2、shell命令以及运行原理3、权限3、1、什么是权限3、2、权限的本质3、3、Linux中的用户3、4、Linux中文件的权限3、4、1、快速掌握修改权限的…

vue引用js html页面 vue引用js动态效果

要引用的index.html页面&#xff1a;&#xff08;资源来自网络&#xff09;在pubilc下建一个static文件放入js文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>数字翻转</title><meta con…

Flutter实战小案例

(实战)点不到的按钮 // 主要实现效果类 class _MyHomePageState extends State<MyHomePage> {// 1.定义要使用的变量double btnLeft 0;double btnTop 0;int timeDuration 500;String textButton "点我呀";// 2.获得当前设备屏幕尺⼨&#xff0c;需要impor…

常用的设计模式有哪些

设计模式是软件工程中用来解决常见设计问题的一些通用解决方案。常见的设计模式可以分为三大类&#xff1a;创建型模式、结构型模式和行为型模式。以下是每类设计模式的具体介绍&#xff1a; 创建型模式 这些模式主要用于对象创建&#xff0c;避免程序中的硬编码&#xff0c;…

速部署 HBase 测试环境

快速部署 HBase 测试环境 第一步&#xff1a;下载软件&#xff0c;在HBase官网下载最新版&#xff0c; 找到 bin&#xff0c;点击下载&#xff0c;比如我这里下载的是 hbase-2.5.6-bin.tar.gz 第二步&#xff1a;解压软件 $ tar -zxvf hbase-2.5.6-bin.tar.gz $ cd hbase-2.…

Lora模型训练的参数-学习笔记

任何一个lora都会有三重属性&#xff0c;易调用性、泛化性和还原性&#xff0c;任何一个lora只能完美满足其中的两项&#xff1b; 易调用性&#xff1a;在已调用lora后&#xff0c;还需要多少提示词才能让该lora完全生效&#xff1b; 泛化性&#xff1a;能不能还原lora训练素…

杜甫很 忙

我 我希望大家别再乱搞了

Windows终端远程登陆Linux服务器(SSH+VScode)

W i n d o w s 终端远程登陆 L i n u x 服务器&#xff08; S S H V S c o d e &#xff09; \huge{Windows终端远程登陆Linux服务器&#xff08;SSHVScode&#xff09;} Windows终端远程登陆Linux服务器&#xff08;SSHVScode&#xff09; 文章目录 写在前面通过SSH远程连接L…

golang程序性能提升改进篇之文件的读写---第一篇

背景&#xff1a;接手的项目是golang开发的&#xff08;本人初次接触golang&#xff09;经常出现oom。这个程序是计算和io密集型&#xff0c;调用流量属于明显有波峰波谷&#xff0c;但是因为各种原因&#xff0c;当前无法快速通过serverless或者动态在高峰时段调整资源&#x…

JS之数组中的reduce方法

文章目录 基本语法&#xff1a;callbackFn 的参数:例子1. 数组求和2. 数组求积3. 扁平化数组4. 数组元素计数5. 使用对象解构和展开运算符合并数组中的对象6. 求最大值和最小值 函数组合异步操作中的 reduce总结 reduce 是 JavaScript 中 Array 对象的一个方法&#xff0c;非常…

MySQL InnoDB【事务模型】之【事务隔离级别】 全攻略

快速导航 事务隔离级别可重复读&#xff08;REPEATABLE READ&#xff09;读提交内容&#xff08;READ COMMITTED&#xff09;读未提交内容&#xff08;READ UNCOMMITTED&#xff09;可串行化&#xff08;SERIALIZABLE&#xff09; 事务隔离级别 事务隔离是数据库处理的基础之一…

「邀您参会」首个中国可观测日即将盛大开幕

在云计算领域不断探索与创新的背景下&#xff0c;亚马逊云科技与观测云今日宣布&#xff0c;将联合举办中国可观测日&#xff08;Observability Day&#xff09;活动&#xff0c;旨在深化双方合作&#xff0c;共同推动中国可观测性的发展。 中国站首站&#xff0c;选址上海&am…

软件测试——非功能测试

工作职责&#xff1a; 1.负责产品系统测试&#xff0c;包括功能测试、性能测试、稳定性测试、用户场景测试、可靠性测试等。 2.负责测试相关文档的编写&#xff0c;包括测试计划、测试用例、测试报告等。 3.负责自动化测试框架、用例的维护。 岗位要求&#xff1a; 1.熟练…

使用html2canvas实现图片或者dom元素的样式展示

html或者.vue.tsx模板 <div class"tan1" id"tan1"><div class"jiang" id"jiangImg1" style"margin: 1rem auto 0;width: 75%;height: 10rem;position: relative;background-color: transparent;"><img id&q…

Laravel数据库的魔法棒:深入探索数据库迁移(Migrations)

Laravel数据库的魔法棒&#xff1a;深入探索数据库迁移&#xff08;Migrations&#xff09; 在Laravel的世界中&#xff0c;数据库迁移&#xff08;Migrations&#xff09;是一种强大的工具&#xff0c;它允许开发者以版本控制的方式管理数据库结构的变化。通过迁移&#xff0…

中级java每日一道面试题-2024年7月17日

面试官: 操作字符串都有哪些类?它们之间有什么区别? 我回答: String 描述&#xff1a;String是最基本的字符串类&#xff0c;用于表示不可变的字符序列。一旦创建了一个String对象&#xff0c;其内容就不能被改变。特性&#xff1a; 不可变性&#xff08;Immutability&#…

【启明智显方案分享】工业级HMI芯片MODEL3\MODEL4应用于电梯系统多媒体

一、方案概述 本方案采用工业级HMI芯片MODEL3或MODEL4作为核心处理器&#xff0c;结合7寸以上高清显示屏&#xff0c;为电梯系统提供多媒体解决方案。该方案不仅能够显示日期、时间、楼层信息等基础信息&#xff0c;还能播放广告、通知、视频等多媒体内容&#xff0c;增强电梯…

HTTPS请求头缺少HttpOnly和Secure属性解决方案

问题描述&#xff1a; 建立Filter拦截器类 package com.ruoyi.framework.security.filter;import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.…