皮皮网

【lon源码】【usb hid c 源码】【鹿鼎记多开源码】invalidate源码

2024-11-19 10:30:47 来源:Mala源码

1.Android ViewRootImpl
2.C#将图片像素变低
3.View 绘制流程源码分析
4.Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例
5.svelte响应式原理
6.android中Invalidate和postInvalidate的区别

invalidate源码

Android ViewRootImpl

       æœ¬æ–‡ä¸»è¦åˆ†æžä¸¤ä¸ªé—®é¢˜ï¼š

        1、为什么View 的绘制流程是从 ViewRootImpl 的performTraversals()方法开始的?

        2、View 的invalidate方法是怎么触发到ViewRootImpl 的performTraversals()方法的。

        在阅读本文前,最好先了解window的添加过程,Android消息处理机制 和 View 的绘制流程。推荐先阅读以下文章:

        Android Window和WindowManager

        Android-消息机制

        Android View 的绘制流程

       android 源码注释的意思是:ViewRootImpl是视图层次结构的顶部,实现 View 和 WindowManager 之间所需的协议。是 WindowManager Global 的内部实现中重要的组成部分。

        View 的绘制流程是从 ViewRootImpl 的performTraversals()方法开始的,那到底是哪里调用了performTraversals()方法呢,下面我们分析一下:

        1.私有属性的performTraversals()方法肯定是在内部调用起来的,经过搜索找到是doTraversal()方法调用了。

        2.接着找到了,调用了doTraversal() 的TraversalRunnable ç±»

        3.内部只有一个地方实例化了TraversalRunnable 的实例mTraversalRunnable ,查到到两个方法内都调用了mTraversalRunnable ,明显 scheduleTraversals 是主动触发这个 Runnable 。这就表明调用了scheduleTraversals ()函数的地方都主动触发了view的刷新。

        4.接着我们看一下 mChoreographer.postCallback 做了什么

        可以看到,最后后走进了scheduleVsyncLocked()方法内。

        5.mDisplayEventReceiver 的类 是 FrameDisplayEventReceiver,继承自

        DisplayEventReceiver 。

        最后走到这里就没了,那么这个方法是做了什么呢,这个方法的注释是这个意思:安排在下一个显示帧开始时传送单个垂直同步脉冲。意思就是,调用了这个方法可以收到系统传送过来的垂直同步脉冲信号。Android系统每隔ms就会发送一个VSYNC信号(VSYNC:vertical synchronization 垂直同步,帧同步),触发对UI进行渲染。这个垂直同步信对于应用来说了,只有了订阅了监听,才能收到。而且是订阅一次,收到一次。

        6.既然是在这个类里面订阅垂直同步信号的,那回调也应该在这里。于是找到了以下方法。

        native code 调用到 onVsync,这个方法的注释解释如下:当接收到垂直同步脉冲时调用。接收者应该渲染一个帧,然后调用 { @link scheduleVsync} 来安排下一个垂直同步脉冲。

        这个方法的具体实现在前面分析到的FrameDisplayEventReceiver 类里面。

        这里可以看到,其实mHandler就是当前主线程的handler,当接收到onVsync信号的时候,将自己封装到Message中,等到Looper处理,最后Looper处理消息的时候就会调用run方法,这里是Handler的机制,不做解释。

        7.最后,如下图调用所示,最终从mCallbackQueues取回之前添加的任务再执行run方法,也就是TraservalRunnable的run方法。

        总结上面的分析,调用流程如下图所示如下:

        上面我们分析到只要调用了ViewRootImpl 的scheduleTraversals ()方法,最终就能调用了ViewRootImpl 的performTraversals()来开始绘制。那肯定是我们常调用的view刷新的接口,经过一系列的方法调用,最终调用了ViewRootImpl 的scheduleTraversals ()方法。下面我们分析一下常用的View 的 invalidate()接口是怎么调用到了ViewRootImpl 的scheduleTraversals ()方法。

        可以看出,invalidate有多个重载方法,但最终都会调用invalidateInternal方法,在这个方法内部,进行了一系列的判断,判断View是否需要重绘,接着为该View设置标记位,然后把需要重绘的区域传递给父容器,即调用父容器的invalidateChild方法。

        接着我们看ViewGroup#invalidateChild:

        由于不断向上调用父容器的方法,到最后会调用到ViewRootImpl的invalidateChildInParent方法,我们来看看它的源码,ViewRootImpl#invalidateChildInParent:

        最后调用了scheduleTraversals方法,触发View的工作流程。至此,我们已经完整地分析了一次调用View 的 invalidate()方法到触发ViewRootImpl 的scheduleTraversals()方法。

C#将图片像素变低

       å°±æ˜¯æ”¹å˜ä¸€å¼ å›¾ç‰‡çš„大小嘛:定义全局的变量: private Image curImage = null;

        private string ImageFileName = null;下面的定义在一个按钮里面,用来打开图片。//当然你要打开图片才行哈。 OpenFileDialog OpenDlg = new OpenFileDialog(); if (OpenDlg.ShowDialog() == DialogResult.OK)

        {

        ImageFileName = OpenDlg.FileName;

        curImage = Image.FromFile(ImageFileName);

        }再在另外的按钮上面创建一张图片,用来保存新图片。Bitmap bitmap=new Bitmap(,源码);bitmap.Save("xx.jpg",ImageFormat.Jpeg); //这样保存在当前目录,并且指定了只能是这个格式的,当然可以设置其他格式的,下面有完整代码。//////////关于变化图片大小。///////using System;

       using System.Collections.Generic;

       using System.ComponentModel;

       using System.Data;

       using System.Drawing;

       using System.Text;

       using System.Windows.Forms;

       using System.Drawing.Imaging;namespace 使用不同大小保存图片1

       {

        public partial class Form1 : Form

        {

        public Form1()

        {

        InitializeComponent();

        } private Image curImage = null;

        private string ImageFileName = null;

        private void openFileToolStripMenuItem_Click(object sender, EventArgs e)

        {

        OpenFileDialog OpenDlg = new OpenFileDialog();

        OpenDlg.Filter = "All ImageFile|*.jpg;*.bmp;*.gif;*.png|(*.bmp)|*.bmp|(*.jpg)|*.jpg|(*.png)|*.png|(*.gif)|*.gif";

        OpenDlg.Title = "Open Image File"; if (OpenDlg.ShowDialog() == DialogResult.OK)

        {

        ImageFileName = OpenDlg.FileName;

        curImage = Image.FromFile(ImageFileName);

        }

        this.Invalidate();

        } private void Form1_Paint(object sender, PaintEventArgs e)

        {

        if (curImage != null)

        {

        e.Graphics.DrawImage(curImage, 0, 0);

        }

        } private void button1_Click(object sender, EventArgs e)

        {

        try

        {

        if (this.textBox1.Text == "" || this.textBox2.Text == "")

        {

        MessageBox.Show("请输入大小完整");

        }

        else

        {

        int width = Convert.ToInt(textBox1.Text);

        int height = Convert.ToInt(textBox2.Text); SaveFileDialog saveDlg = new SaveFileDialog();

        saveDlg.Filter = "(All ImageFile)|*.jpg;*.png;*.gif;*.bmp";

        saveDlg.Title = "Save ImageFile";

        saveDlg.OverwritePrompt = true;

        if (saveDlg.ShowDialog() == DialogResult.OK)

        {

        string filename=saveDlg.FileName;

        string extn = filename.Remove(0, saveDlg.FileName.Length - 3); Bitmap newBitmap = new Bitmap(curImage, new Size(width, height)); if (extn.Equals("jpg"))

        newBitmap.Save(saveDlg.FileName, ImageFormat.Jpeg);

        else if (extn.Equals("png"))

        newBitmap.Save(saveDlg.FileName, ImageFormat.Png);

        else if (extn.Equals("bmp"))

        newBitmap.Save(saveDlg.FileName, ImageFormat.Bmp);

        else if (extn.Equals("gif"))

        newBitmap.Save(saveDlg.FileName, ImageFormat.Gif);

        }

        }

        }

        catch (Exception ex)

        {

        MessageBox.Show(ex.Message);

        }

        }

        }

       }、、、、、、、、、、、、、、、上面设置的大小是通过使用了,两个TextBox来设置的。设计代码(略)。

View 绘制流程源码分析

       在View的绘制流程中,ViewRootImpl的源码setView主流程涉及的关键步骤包括设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED。这一步骤在执行时,源码触发了View的源码重绘逻辑。

       接下来,源码当View收到需要重绘的源码lon源码信号后,会执行invalidate方法。源码这个方法首先计算出需要重绘的源码dirty区域,然后从下向上,源码最终调用到ViewRootImpl的源码scheduleTraversals方法。这个过程中,源码脏区域的源码范围逐步扩大,直至整个View需要进行重绘。源码

       在View的源码绘制流程中,PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED的源码使用至关重要。它们的设置触发了视图的重绘和布局过程,保证了UI在用户操作或其他事件触发时能够及时响应和更新。通过这种方式,系统确保了用户界面的实时性和交互性。

       具体来说,当View收到布局或尺寸变化的信号时,会调用requestLayout方法,同时设置PFLAG_FORCE_LAYOUT标志。这个标志告诉系统,当前布局需要强制执行,即使布局尚未完成,也应立即进行更新。同时,invalidate方法的调用,会触发PFLAG_INVALIDATED标志的设置,表明视图需要重绘。

       在ViewRootImpl中,scheduleTraversals方法是负责组织和执行视图层级中所有视图的重绘和布局的。它会根据脏区域和布局标志的设置,合理安排视图的更新顺序,确保系统的性能和用户体验。

       总结整个流程,View的绘制和布局机制通过一系列的标志(如PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED)和方法(如requestLayout和invalidate)来协调和控制。这些机制使得系统能够高效地响应用户操作,实现流畅的UI交互。通过深入理解这些源码细节,开发者能够更好地优化UI性能,提高用户体验。

Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例

       简介

       发现、定性与定位

       总结

       跟不上旋律节奏的VSYNC

       严重异常耗时的dequeueBuffer

       VirtualDisplay合成耗时

       结论

       FPS

       初步定位问题

       定性问题

       定位问题

       成果展示

       参考

简介

       本文记录一次Android图形性能问题的分析过程——发现、定性和定位图形性能问题,以及探讨的性能优化方案。

       环境:Android Q + MTK + ARM Mali-G。

       所分析的性能问题(下称case):打开录屏应用并启动后台录屏,滑动前台应用(滑屏)。性能表现差:CPU、GPU负载显著升高、掉帧、用户明显卡顿感,帧率不足帧,帧渲染、合成耗时急剧飙升(渲染耗时平均为ms左右)。

       经过优化后,相同环境和条件下,渲染帧率稳定在帧(提升一倍),渲染耗时平均为8.ms左右(为优化前的不到三分之一的消耗)。

       关键词 Keywords: Screen Recording; Frame rate; FPS; GPU utilization; Jank; MediaProjection; VirtualDisplay; MediaCodec; Perfetto; Inferno; Surface; SurfaceTexture; VSYNC; SurfaceFlinger; HWC; Hardware composer; GPU; OpenGL;

发现、定性与定位FPS

       计算FPS的方法和工具 Android框架层通过hwui配合底层完成渲染。该框架本身提供了逐帧渲染分段耗时记录。通过dumpsys gfxinfo可以获取。

io.microshow.screenrecorder/io.microshow.screenrecorder.activity.MainActivity/android.view.ViewRootImpl@6b9b8a9?(visibility=0)DrawPrepare?Process?Execute3...................1................

       使用工具统计帧率与平均耗时(同时打印GPU负载),在开启后台录屏的情况下滑动屏幕,平均渲染耗时高达~ms,超出.ms一倍,导致帧率仅帧,显著低于帧。

Average?elapsed?.?msFPS:??│?9.?0.?.?2.#?GPU负载?LOADING?BLOCKING?IDLE?0?#?case的对比——未开启后台录屏Average?elapsed?9.?msFPS:??│?1.?0.?5.?1.

       通过gfx柱状图直观感受性能数据 直观地感受图形渲染性能,除了帧率感受、触控延时外,还可以通过将gfxinfo的分段耗时通过柱状图展示在屏幕上。

       这是case性能问题的gfxinfo柱状图,可以看到红柱和绿柱都非常高,远远超越了流畅标准。其中,绿柱异常放大表明两个Vsync之间耗时显著增长,红柱异常放大表明应用层应用加速使用的DisplayLists大量增长、或图形层使用GLES调用GPU耗时显著增多导致的GPU执行绘制指令耗时变长。

初步定位问题

       本节记录初步的分析思路和定位过程。首先我们完成实验(启停后台录屏并滑动屏幕触发渲染)、usb hid c 源码观测以及记录,拿到了后台录屏启停情况下的FPS、分阶段耗时以及GPU负载(相关数据位于FPS小节)。

       开发的工具输出的统计数据计算结果非常直观,一眼可见,后台录屏为Draw阶段带来额外的~8倍或~8ms耗时,给Process阶段带来额外的~2倍或~ms耗时。帧率从帧坠落到~帧。

       耗时分析 可以看到,主要的额外耗时来自Draw和Process。接下来重点围绕着两part定位问题问题。

StageDescriptionCompDraw创建DisplayLists的耗时。Android的View如果支持硬件加速,绘制工作均通过DisplayLists由GPU绘制,可以处理为onDraw的耗时额外~8ms或~8倍Prepare准备没有额外耗时ProcessDisplayLists执行耗时。即硬件加速机制下提交给GPU绘制的工作耗时额外~ms或~2倍ExecuteFramebuffer前后缓冲区flip动作的耗时,上屏耗时额外不到~1ms

       Hz下,上述4个步骤合计耗时小于.ms为正常情况。case为~ms。主要增量来自Draw和Process。

       经过上述初步分析、观测后,接下来的分析可以围绕Draw和Process开展。由于Android Draw部分涉及较广,包含App 渲染线程(DisplayLists)、UI线程(onDraw方法创建DisplayLists),以及图形栈耗时如SurfaceFlinger、RenderEngine等都可能增加Draw耗时。

       这里一个技巧可以初步判断耗时来自App进程(渲染线程和UI线程)还是来自图形栈。如果能判断耗时来自App或图形栈,那么可以缩小分析范围、减少分析工作量。上述四大阶段的耗时统计分类比较宽,实际上还有更详细的分阶段耗时,它呈现在前文描述过的gfx统计信息柱状图上。gfx柱状图会以蓝色(RGB(,,))呈现onDraw方法创建和更新DisplayLists的耗时。如果case与正常情况对比后,这部分耗时(蓝柱大小对比)差异很小,即可说明额外的Draw耗时不是来自App的,极可能来自图形栈。Besides,结合过度绘制分析,判断case与正常情况下是否有更多的额外绘制次数可以协同判断。

       ——根据上述指导思想,排查出了case的额外Draw耗时与App onDraw无关,多出来的DisplayLists来自App以外的进程,可能是图形栈如SurfaceFlinger。

定性问题

       本小节介绍问题追踪过程,通过一些方法定位到各阶段的耗时原因,并定性地得出case性能问题的性质。从本小节开始,围绕Perfetto进行分析。这里贴出perfetto的总览,我将关键的信息排序到顶部。前四行分别为SF负责图形的线程、提交到GPU等待完成的工作、Vsync-App、Vsync-sf,最后两行为case中出现卡顿掉帧的App的主线程(UI)和渲染线程(RenderThread)。

跟不上旋律节奏的VSYNC

       容易看到,Vsync-sf非常不规律。Vsync-sf是触发SurfaceFlinger一次合成工作的基于Hardware VSYNC虚拟出来的一个信号。它相对于真实硬件信号(HW_VSYNC)一个规律的偏移(在case设备上,Vsync-app与Vsync-sf都被配置为8.3ms,即硬件VSYNC到达后,虚拟的Vsync-app和Vsync-sf延时8.3ms后发出,分别触发App绘制、SurfaceFlinger合成。

       而case的Vsync-sf交错、残次、不齐、无规律,显然工况不佳。它将导致SurfaceFlinger不能按照预期的时间间隔将合成的帧提交到Framebuffer(经过Flip后,被提交的Framebuffer将上屏成为显示器的下一帧图像),出现掉帧/丢帧。

       As we can see,case的VSYNC-sf出现严重的漂移(见图,第二行的VSYNC-sf残次不齐、跟不上规律、难看且混乱),这导致了丢帧。(但VSYNC-sf的鹿鼎记多开源码失控仅表示与丢帧的相关性,并不直接表明因果性。)

       VSYNC-sf为什么会出现偏差? 出于功耗的考虑,VSYNC-sf合VSYNC-app并不是一定会触发的。如果app或sf并没有更新画面的需求,那么死板固定地调度它们进行绘制和合成是不必的。编程上,负责触发VSYNC-sf和VSYNC-app的两个EventThread会在requestNextVsync调用后才会将下一个VSYNC-sf或VSYNC-app发出。因此,当(各自EventThread的)requestNextVsync没有调用时,VSYNC-app和VSYNC-sf也就出现漂移。BufferQueueLayer::onFrameAvailable会在应用提交后调用,该方法通过调用SF的signalLayerUpdate触发产生下一个VSYNC-sf。

       换而言之,出于功耗,或别的什么原因(比如耗时导致的延期,人家是线程实现的消息队列),SurfaceFlinger的SFEventThread有可能不调用requestNextVsync,这将导致Vsync-sf在窗口期内短暂消失——但是也不会出现参差不齐的情况。结合case的VSYNC信号报告来看,VSYNC-sf信号异常切实地提示了性能问题——它的不规律现象表明前后Vsync之间有异常耗时,而非低功耗机制被激活或无屏幕刷新(case性能问题复现时一直在滑前台应用的屏,它每ms都有画面更新的需求)。

       VSYNC-sf虽然出现了偏差,但是它与卡顿问题仅有相关性(或者说它是性能问题的结果),并非因果关系。猜测是其他卡顿问题导致了SF延缓了对VSYNC的request,导致其信号出现漂移。VSYNC-sf信号偏差实质上指导意义重大,因为它能提示我们,问题发生在比App更底层的地方(前文分析的结论),且比SurfaceFlinger提交到Framebuffer更上层的位置(VSYNC-sf用于触发合成,合成完成后提交到屏幕双缓冲区)。

       这样,将case性能问题的上下界都确定了,问题分析范围从原先的整个图形栈,有效的缩小到了SurfaceFlinger渲染和合成阶段了。

严重异常耗时的dequeueBuffer

       通读Perfetto,可以看到,出了难看的Vsync-sf以外,还可以看到刺眼的超长耗时的draw(App UI线程)以及耗时变态长的dequeueBuffer(App 渲染线程)调用。相对于正常情况,perfetto报告提示的case的draw方法成倍增长的耗时非常容易被误认为耗时“居然来自一开始就排除掉的App进程",这与前文提出的”问题范围“是不能自洽的——它们是相反的结论,肯定哪里不对。仔细分析才能发现,draw方法确实是消耗了更多墙上时间(但是不意味着消耗了更多CPU时间,因为等待过程是sleep的),但是draw方法是因为等待渲染线程的dequeueBuffer造成的耗时,而dequeueBuffer的严重异常耗时却是被底层的图形栈拖累的。

       我们看到,draw严重耗时,渲染线程dequeueBuffer消耗掉~ms的时间。As we all known,Android的Graphics buffer是生产者消费者模型,当作为消费者的SF来不及处理buffer并释放,渲染线程也就需要额外耗时等待buffer就绪。上面还有一段"Waiting GPU Completion"的trace没有贴上来(下图),这段耗时比不开启后台录屏的case下高得多(~3ms对比~ms),说明了一定的GPU性能问题或SF的性能问题,甚至有可能是Display有问题(HWC release耗时过长也会导致SF释放buf、生产者渲染线程dequeueBuffer额外等待)。

       这里的机制比较复杂,不熟悉底层Graphics buffer的流水线模型就不好理解。In one world, dequeueBuffer申请的buffer不是凭空new出来的,而是在App-SurfaceFlinger-Framebuffer这一流水线中循环使用的。流水线中的buffer不是无限的,而是有穷的几个。当底层的伙计,如SF和HWC,使用了buffer但是没有来得及释放时(它们的工作没做完之前不会释放buffer),流水线(可以理解成头尾相接的单向队列(ring buffer))没有可用的buffer,此时dequeueBuffer就不得不进入等待,出现耗时看上去很长的问题。实际上,dequeueBuffer耗时的唯一原因几乎仅仅只有一个:底层消费太慢了,流水线没有剩余buffer,因此需要等待。

       这个模型抽象理解非常简单。下图,右边消费者是底层图形栈——它每消费完一个buffer就会释放掉,每释放一个buffer应用层能用的buffer就加1。左边生产者是App渲染线程——它调用dequeueBuffer申请一个buffer以将它的画面绘制到这个buffer上。buffer送入BufferQueue后由右边的qt串口编程源码消费者(图形栈)进行消费(合成、上屏显示),然后释放buffer。当图形栈来不及release buffer时,dequeueBuffer的调用者(App渲染线程)将由于无可用buffer,就必须挂起等待了,在perfetto上就留下长长的一段”耗时“(实际上是墙上时间,大部分都没有占用CPU)。

       以上,这就是为什么说App渲染线程dequeueBuffer严重耗时中的耗时为什么要打引号,为什么要说是被图形层拖累了。

       下图可以看到,刨去dequeueBuffer的严重异常耗时,执行渲染的部分耗时相对于正常的case几乎没有差异,这可以断言渲染线程的惨烈耗时主要就是被dequeueBuffer浪费了。

       从GPU Completion来看,此时GPU正在为SF工作,因为在图中看到(不好意思没有截全,下图你是看不出来的),dequeueBuffer总是在SF的GPU Completion结束之后结束的,这就表明SF正在通过GPU消费buffer(调用GPU进行合成后提交,然后标记buffer允许被渲染线程dequeue)。dequeueBuffer获取到就绪的buffer此时此刻取决于SF的消费能力——因为case中它是短板。(当然图形层的buffer可用不止SurfaceFlinger需要释放,因为SF释放后buffer实质上流转到更底层的HWC,等它将Buffer提交到屏幕后才会释放,这里释放后才能给App再次使用(上面哪个模型图把SF和HWC合并为流水线的图形层buffer消费者)。

       从perfetto报告看HWC release非常及时、余量充足,SF的GPU Completion则较紧密地接着dequeueBuffer返回,基本断言是SF太慢了——排除HWC的责任。(下图看不出来,当时没有截图到HWC的release情况。)

       到这里,除了再次确认排除了前台App的问题外,还可以断言问题来自SurfaceFlinger过分耗时。此外将问题范围的下界从整个SF合成流程(上文的Vsync-sf)缩小到了排除HWC的范围。

       结论:渲染耗时一切正常,问题出现在SF消费buffer(合成图形)失速了,导致没有可用的buffer供渲染线程使用。从下图的SF的工况(第三列)来看,情况确实如此。

       既然一口咬定是SF的锅,那就瞧瞧SF。先看SF的INVALIDATE,这没啥好看的,异常case和正常case都是~2.5ms。主要看refresh,正常case ~6.8ms,异常case ~.8ms。refresh包含SF的合成四件套,包括rebuildLayerStack、CalcuateWorkingSet、Prepare、doComposition。Perfetto报告直接表明,case的后台录屏导致的额外一次合成和配套工作是主要的耗时增量。

       之所以会执行两次合成,是因为后台录屏工具编程上通过Android SDK提供的MediaProjection配合VirtualDisplay实现一个虚拟的镜像的屏幕。SurfaceFlinger会将画面输送一份到这个虚拟的Display以实现屏幕图像传送到录屏工具,虚拟的屏幕要求额外的一次合成。从上图可以直接得出结论,case带来的额外工作消耗就是对该录屏用的VirtualDisplay的合成工作(doComposition)带来的。

VirtualDisplay合成耗时

       由于问题范围已经缩小到了很小的一个范围,在SurfaceFlinger的Refresh过程中,case相对正常应用有巨大的差异耗时,几乎完全来自于对VirtualDisplay的合成耗时(doComposition)。同时也可以看到,两次合成(一次是设备的物理屏幕,一次是case的后台录屏工具创建的虚拟屏幕)中,虚拟屏幕的耗时远远高于物理屏幕(4倍以上)。

       通过查看ATRACE的tag(上图,Perfetto中SurfaceFlinger中主线程的各个trace point都是用ATRACE打的tag),结合dumpsys SurfaceFlinger,能直接看到的线索是:

       虚拟屏显著耗时,且合成工作通过GLES调用GPU完成

       物理屏合成耗时很小,它通过HWC合成

       结合图中提示的trace tag、耗时,可以得出结论,使用GPU合成的虚拟屏中因GPU合成耗时很长,导致它显著高于物理屏HWC合成耗时。如果GPU合成能够和HWC合成一样快,或者干脆让虚拟屏也使用HWC合成,那么可以预期SurfaceFlinger的java 桌面项目源码合成工作的消耗将显著降低。

结论

       本小节综合上述三个小节的分析,对节”定性问题“下一个结论。

       耗时的本质已经被看透,录屏工具申请创建的VirtualDisplay没有通过HWC进行合成,而是通过GPU进行合成,它耗时很长导致界面卡顿。In one word,case使用的VirtualDisplay的合成方式不够高效。

       HWC是Hardward Composer。它接收图形数据,类似于往桌面(真的桌面,不是电脑和手机的桌面)上面叠放照片和纸张——即合成过程。这个工作能将界面上几个窗口叠加在一起后送到屏幕上显示。通过GLES调动GPU也能干这活,不过HWC执行合成的动作是纯硬件的——它很快,比GPU快几倍。

定位问题

       前面虽然定性了问题原因是合成方式不够高效,但是没有得出其中的原理——为什么虚拟屏不使用高效的HWC进行合成。本节通过介绍HWC的原理、SurfaceFlinger控制合成方式、虚拟屏Surface特性等来介绍图形栈中合成方式的处理模式。掌握了相关管理后,探讨一些尽量通用的共性的解决方案实现性能优化。最后着重介绍多套优化方案中的一种直面根本原因的解决方法——MediaCodec.MediaFormat创建的支持HWC合成的Surface方案。

       SurfaceFlinger如何决定使用HWC还是GPU合成? SurfaceFlinger合成主要可以依靠两条路径。其中之一是”纯硬“的HWC合成(在dumpsys SurfaceFlinger中可以看到Composition type为DEVICE),另一个是通过OpenGL让GPU进行合成(Composition type为CLIENT)。

       除非是功耗上的设计,否则SurfaceFlinger总是会优先检查本次合成是否支持使用HWC。编程上,在合成阶段之一的prepare过程中,SurfaceFlinger通过prepareFrame在RenderSurface与Hardware Composer(即HWC)的HIDL服务通信,完成hwc layer的创建。但是,layer能够成功创建不意味着一定支持HWC合成。SurfaceFlinger通过getChangedCompositionTypes向HWC查询不支持HWC合成的Layer。该方法返回的layer如果被标记为CLIENT合成,那么这部分Layer无法由HWC进行合成,而只能通过GPU进行合成——case的VirtualDisplay就是这个情况。

       部分layer可能不能由HWC合成的原因(除功耗策略、其他软件策略外):

       HWC layer达到上限 Hardware Composer支持的layer数量是有限的。查阅公开资料可知,HWC合成动作属于硬件提供的能力,它们的合成能力受到硬件本身的限制。Google官方资料对Android设备的要求是,HWC最少应该支持4个Layer,分别用于一个常规页面上最常见的4个层:壁纸、状态栏、导航栏和应用窗口。 在case设备中,经过测试,该平台的HWC最多支持7个能进行HWC合成的layer,从第8个layer开始,完完全全只能使用CLIENT合成亦即SurfaceFlinger调用RenderEngine通过OpenGL调动GPU进行合成。 正是由于HWC合成layer有上限,因此在弹出多个弹窗、叠加过于复杂时,即使界面简单也有可能出现比较明显的卡顿。

       VirtualDisplay的Surface格式不受HWC支持 HWC的硬件合成能力对buffer(Surface封装)内保存的图像的格式有要求。比如,HWC不能处理缩放,仅支持一部分的格式,大多数都还有其他因素会导致不支持,如旋转、部分Alpha等等。In one word,图像格式的数量是远远多于HWC支持的类型数的。当HWC碰到不支持合成的Surface时,就会在前文提过的getChangedCompositionTypes中通知SurfaceFlinger,由SurfaceFlinger转为使用GPU合成。

       结合上述几种情况,设计实验验证。其中通过在物理屏上弹窗来增加Layer以获取HWC Layer上限。确认case无法使用HWC合成不是Layer上限导致的问题后,通过对比来验证Surface格式问题。Surface是对native层的buffer的封装,其类型广泛、实现复杂,一个一个试是不现实的。通过对比性能强劲的类似实现可以一探究竟。Android adb提供一个出厂自带的录屏命令screenrecord、用于测试双屏显示功能的虚拟辅助屏幕(开发者模式-模拟辅助屏)、著名远程窥屏工具scrcpy等三个工具是一系列重要参考。

       经过测试,screenrecord和scrcpy创建的VirtualDisplay支持HWC合成——这是优化目标。首先看看它们的实现。

       编程上,虚拟辅助屏幕采用了与case一模一样的实现——通过创建VirtualDisplay让图形层额外合成一次屏幕到该虚拟屏幕中。虚拟屏幕本质上将画面发送给录屏功能实现,而非进行显示来完成录屏。

       通读screenrecord源码,逻辑上,它与虚拟辅助屏、case录屏应用是相同的——VirtualDisplay录屏。但是编程上略有差异:

       screenrecord直接通过binder与SurfaceFlinger通信,获取了raw VirtualDisplay,而

svelte响应式原理

       Svelte的响应式实现原理主要体现在其编译后的js代码结构中。源代码中的控制逻辑被转化为高效的运行时机制,以确保组件状态的实时更新。当点击"change name"按钮,改动firstName和lastName的值时,init方法中的invalidate函数扮演关键角色,它在初始化时作为instance的参数传递。

       invalidate函数内部的make_dirty方法负责标记组件状态的变化。在数组component.$$.dirty中,每位对应个变量的脏状态。通过(i / ) | 0计算索引,比如i=0时,索引为0;i=时,索引为1。而(1 << (i % ))则是向左移动的位数,根据i和的余数。例如,当i=0时,标记的是从右边数第一个变量,i=1时则标记第二个变量。

       create_fragment函数中的update方法进一步应用了这些标记。当firstName或lastName的值改变时,对应的dirty数组位会被设置,if条件检查这部分变化。如firstName的值修改,i=0,执行set_data_dev(t1, /*firstName*/ ctx[0]),将新值反映到对应的DOM元素t1上。

       set_data_dev方法相当直接,检查并更新DOM的data属性,确保内容与组件状态保持同步。这样,Svelte以简洁的代码实现响应式,使得用户界面实时反映出数据的变化。

android中Invalidate和postInvalidate的区别

       Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。

       Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。

       ã€€ã€€Android程序中可以使用的界面刷新方法有两种,分别是利用Handler和利用postInvalidate()来实现在线程中刷新界面。

       1,利用invalidate()刷新界面

       ã€€ã€€å®žä¾‹åŒ–一个Handler对象,并重写handleMessage方法调用invalidate()实现界面刷新;而在线程中通过sendMessage发送界面更新消息。

       // 在onCreate()中开启线程

       new Thread(new GameThread()).start();、

       // 实例化一个handler

       Handler myHandler = new Handler() {

       // 接收到消息后处理

       public void handleMessage(Message msg) {

       switch (msg.what) {

       case Activity.REFRESH:

       mGameView.invalidate(); // 刷新界面

       break;

       }

       super.handleMessage(msg);

       }

       };

       class GameThread implements Runnable {

       public void run() {

       while (!Thread.currentThread().isInterrupted()) {

       Message message = new Message();

       message.what = Activity.REFRESH;

       // 发送消息

       Activity.this.myHandler.sendMessage(message);

       try {

       Thread.sleep();

       } catch (InterruptedException e) {

       Thread.currentThread().interrupt();

       }

       }

       }

       }

       2,使用postInvalidate()刷新界面

       ä½¿ç”¨postInvalidate则比较简单,不需要handler,直接在线程中调用postInvalidate即可。

       class GameThread implements Runnable {

       public void run() {

       while (!Thread.currentThread().isInterrupted()) {

       try {

       Thread.sleep();

       } catch (InterruptedException e) {

       Thread.currentThread().interrupt();

       }

       // 使用postInvalidate可以直接在线程中更新界面

       mGameView.postInvalidate();

       }

       }

       }

       View 类中postInvalidate()方法源码如下,可见它也是用到了handler的:

       public void postInvalidate() {

        postInvalidateDelayed(0);

       }

       public void postInvalidateDelayed(long delayMilliseconds) {

        // We try only with the AttachInfo because there's no point in invalidating

        // if we are not attached to our window

        if (mAttachInfo != null) {

        Message msg = Message.obtain();

        msg.what = AttachInfo.INVALIDATE_MSG;

        msg.obj = this;

        mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);

        }

        }

       é™¤äº†onCreate()是运行在UI线程上的,其实其他大部分方法都是运行在UI线程上的,其实其实只要你没有开启新的线程,你的代码基本上都运行在UI线程上。

什么是vc?

       维生素c

       维生素C参加体内的氧化还原过程,促进人体的生长发育,增强人体对疾病的抵抗能力,促进细胞间质中胶原的形成,维持牙齿、骨骼、血管和肌肉的正常功能,增强肝脏的解毒能力。当人体中缺少维生素C时,就会出现牙龈出血、牙齿松动、骨骼脆弱、粘膜及皮下易出血、伤口不易愈合等症状。近年来,科学家们还发现,维生素C能阻止亚硝酸盐和仲胺在胃内结合成致癌物质——亚脱胺,从而减低癌的发病率。

       维生素C主要生理功能:

       1、 促进骨胶原的生物合成。利于组织创伤口的更快愈合;

       2、 促进氨基酸中酪氨酸和色氨酸的代谢,延长肌体寿命。

       3、 改善铁、钙和叶酸的利用。

       4、 改善脂肪和类脂特别是胆固醇的代谢,预防心血管病。

       5、 促进牙齿和骨骼的生长,防止牙床出血。;

       6、 增强肌体对外界环境的抗应激能力和免疫力。

       维生素C缺乏和疾病:

       维生素C缺乏时,其症状表现为:

       1. 牙龈肿胀出血,牙床溃烂、牙齿松动。

       2. 骨骼畸形、易骨折。

       3. 伤口难愈合等。进一步则引起坏血症、贫血。

       4. 大出血和心脏衰竭,严重时有猝死的危险。

       5. 肌肉纤维衰退,包括心肌衰退。

       含维生素C的食物很多,猕猴桃和辣椒中含量最丰富.

       VC

       一、VC是什么?学VC是学什么?

       首先VC是一个软件(IDE集成开发环境)(编译、编辑、调试)

       C和C++。但C++中的有些特性是不用的,例如I/O流,多态继承

       WindowsSDK(软件开发工具)

       VC的灵魂:MFC(微软基础类库)

       ATL(ActiveX模板类库)

       其他的SDK,如OpenGL,DirectX,ActiveMoive,DrawDib(WinG)

       VC是许许多多技术的综合,就好像少林般绝技,一般在应用中学会其中很少一部分就够了。我们的现在目的,其实不过是学一套少林长拳。这套长拳应包括:

       1、养成一种严谨的软件开发习惯,熟悉软件工程的基本原则。

       2、C语言基础

       3、C++最基本内容:类、继承性、封装性、多态性的概念

       4、如何用向导建立一个程序框架

       5、设计菜单

       6、设计工具条

       7、设计和使用对话框,熟悉最常用的对话框控件:按钮、静态文本、编辑框等

       8、知道怎样新建类、成员函数、成员变量、消息处理函数。

       9、了解最常用的Windows消息:如WM_PAINT(重绘窗口),鼠标按下、弹起、移动,初始化对话框,等。

       、了解最简单的GDI(图形设备接口):写文字、画框、画点、画线、画圆,设置画笔、画刷。

       、熟悉最常用的MFC类:

       CWnd

       CDocument

       CView

       CDC

       CDialog

       CWinApp

       CGdiObject及子类

       CString、CPoint、CRect、CSize等简单数据类型

       CFile

       以上提到的这些内容,是每个人都会用到的内容。

       二、MFC简介

       上述主要内容都是与实际操作关系非常密切的,在此不可能讲清楚;而且,这些在一般的书里都会讲到。我主要将最重要的MFC介绍一下。

       MFC是对WindowsAPI的封装,大大简化了我们的工作;学VC主要就是要学MFC,大约有多个类,但常用的也就二三十个。应该象背4级单词一样将这些常用类搞懂;当然不要死记,要通过看帮助、看例子、动手练习来学会它们;而且,并非每个类的内部的所有函数都要学会,要日积月累。如果真的想成为高手,做个笔记本把自己认为重要的类、函数记下来,随时学习,也是很好的突击方法。

       下面介绍最重要的MFC。

       CWnd:窗口,它是大多数“看得见的东西”的父类(Windows里几乎所有看得见的东西都是一个窗口,大窗口里有许多小窗口),比如视图CView、框架窗口CFrameWnd、工具条CToolBar、对话框CDialog、按钮CButton,etc;一个例外是菜单(CMenu)不是从窗口派生的。该类很大,一开始也不必学,知道就行了。

       CDocument文档,负责内存数据与磁盘的交互。最重要的是OnOpenDocument(读入),OnSaveDocument(写盘),Serialize(读写)

       CView视图,负责内存数据与用户的交互。包括数据的显示、用户操作的响应(如菜单的选取、鼠标的响应)。最重要的是OnDraw(重画窗口),通常用CWnd::Invalidate()来启动它。另外,它通过消息映射表处理菜单、工具条、快捷键和其他用户消息。你自己的许多功能都要加在里面,你打交道最多的就是它。

       CDC设备文本。无论是显示器还是打印机,都是画图给用户看。这图就抽象为CDC。CDC与其他GDI(图形设备接口)一起,完成文字和图形、图像的显示工作。把CDC想象成一张纸,每个窗口都有一个CDC相联系,负责画窗口。CDC有个常用子类CClientDC(窗口客户区),画图通常通过CClientDC完成。

       CDialog对话框

       CWinApp应用程序类。似于C中的main函数,是程序执行的入口和管理者,负责程序建立、消灭,主窗口和文档模板的建立。最常用函数InitInstance():初始化。

       CGdiObject及子类,用于向设备文本画图。它们都需要在使用前选进DC。

       CPen笔,画线

       CBrush刷子,填充

       CFont字体,控制文字输出的字体

       CBitmap位图

       CPalette调色板

       CRgn区域,指定一块区域可以用于做特殊处理。

       CFile文件。最重要的不外是Open(打开),Read(读入),Write(写)

       CString字符串。封装了C中的字符数组,非常实用。

       CPoint点,就是(x,y)对

       CRect矩形,就是(left,top,right,bottom)

       CSize大小,就是(cx,cy)对(宽、高)

       三、用好MSDN和例子

       作为提高,推荐看的例子有:

       nHello最简单的程序框架

       nScribble鼠标绘图。教程:编程基础,GDI

       nDiblook图像处理最简单范例

       nMFCIE因特网浏览器

       nEnroll数据库

       nDaoviewDao(数据访问对象)范例。树型和列视控件

       nCubeOpenGL范例,画一个不断旋转的方块

       nHierSvrOLE服务器

       nOclientOLE包容器

       nDrawcli鼠标绘图。也包括OLE。

       nWordPad写字板。OLE

       四、几点经验

       A.为什么要用Windows而不是DOS编程

       Windows编程并不比DOS编程难很多,只是在刚刚接触的时候感到头绪繁多,不知从何下手。实际上,如果你会C语言,你会发现Windows编程的难度并不比当年学C语言时大。DOS编程要想成为高手,也需要学习几百个函数(无论是TC、BC、MicrosoftC),并要深入了解汇编语言级的DOS功能调用。而现在在Windows下,付出相同的工作量,你却可以获得几倍的收益。如果你以前熟悉DOS编程,现在再搞Windows编程,只要两个星期肯定会感到巨大的优越性:在DOS下,每一件事都要自己干,而且还有K内存、显示精度等的限制,实在是事倍功半了。

       B.为什么要用VC而不是其他Windows编程语言

       n大多数大型软件(包括Windows自己)都是用C、C++编的,所以可以利用的源代码特别多。例如Photoshop,就有VC的编程接口。的其他的语言都不如VC有历史优势。而且VC的开发工具特别多,从控件公司到硬件开发商,其产品很少敢不提供VC接口的。

       nVC适合组织大工程(VB就不行)

       nBorlandC++或C++Builder也很不错,并不比VC差。但BC的版本兼容性不好,从3.1到4.0就出现“代沟”,而且VC提供了极为丰富的文档和范例,VC的参考资料也远比BC多。并且VC与其他的VisualStudio成员可以协调工作,对软件工程有利。

       n

       C.开始不要学WinAPI编程,但以后最好学学WinAPI

       特别要注意,开始不要买讲WindowsSDK编程的书。一个臭名昭著的例子就是“HelloWorld”。各种计算机语言在入门课中往往都给出一个例子,显示一行文字“HelloWorld”,无论是在Basic、C、Pascal,甚至汇编中,这个最简单的小程序都几行就可以写完。但在早期的Windows编程中,刚才讲到的“HelloWin”这个有名的例子就要组织一个工程、写上几百行代码才能实现。这就吓跑了很多想学Windows编程的人。实际上,在Windows下显示“Helloworld”,最简单的方法只要5行就够了:

       #include“windows.h”

       voidWinMain()

       {

       MessageBox(NULL,”HelloWorld!”,”MyFirstWindowsApplication”,MB_OK);

       }

       当然这个程序只建立了一个最简单的对话框,但这已经够了。

       如果你没有SDK编程的经验,学VC就是要利用MFC绕过Windows编程的许多细节。有好的工具为什么不用呢?现在你在VC写一个”HelloWorld”,只要写一行代码就可以了。

       voidCYourView::OnDraw(CDC*pDC)

       {

       //…….其他的代码

       pDC->TextOut(,,”HelloWorld!”);

       }

       但MFC不是万能的,WinAPI和VC中有很多功能在MFC中并不能反映出来。在入门后,要想发挥VC的完全能力,WindowsAPI还是非常重要的。而且,这些API在其他Windows语言中也用得到。

       学语言,与练武道理也差不多。VC、VB、Dlphi、VFP,以及其他的Windows编程语言,都属于同一个门派:Windows门,虽然招式不同,但内功都是一样的,这就是WindowsAPI(应用编程接口),大概是两千个函数。任何武功,招式即使再复杂,通常也能在很短时间内记下来;但内功就不是一朝一夕的事。而且,各门派的内功不同,Unix、Linux门有Uinx的内功,Apple(苹果)门有Mac的内功,Java更是自成一派。我们这里说的学VC,只是学招式,学招式一个月差不多就够了,聪明的两个星期就可以掌握。光学招式,吓唬人是够了,但和高手一过招,非出丑不可。要想精通VC,必须熟悉WindowsAPI。

       D.高级语言与自然语言

       一门高级语言与一门自然语言是类似的。关键要素有三:

       语法:了解C和C++基本语法,这是比较容易的。

       修辞:编程的艺术和风格。通常,这是与软件工程联系起来的。算法上的小伎俩并非是关键的。严谨,严格按照规范去做,同时又能发挥出极大的创造性,才是语言学家的本色。

       词汇:如何利用别人的工作?如何利用自己以前的工作?这是一个软件工程师成功的关键。如果词汇量不丰富,就写不出好文章,就会有想法表达不出。一个人的语法在上小学之前就基本上学成了,修辞水平也在年轻时就确定了,惟有词汇量需要根据需要不断学习、不断扩充。VC编程中,拥有大的词汇量就意味着要掌握较多的类、函数和其他编程组件。如:WindowsSDK,MFC,以及你所专攻的领域的现成的SDK,比如3D编程中的OpenGL,多媒体编程中的DirectX,图像处理中的Victor库。另外,ActiveX控件的掌握往往也是成败的关键。我们这几年的工作也总结成了一个类库,叫IPX(图像处理框架),大大简化了图像处理的工作;不过现在还有很多细节问题要解决,如果有同学感兴趣可以参与这方面的工作。

       学习VC,千万不要用我们在学校里学英语的方法,而要用小孩学说话的方法。所谓学英语的方法,就是我们在本科时学C语言的那种模式:详细搞懂每一个细节,背大量的规则,很少上机实践,没有实际问题供解决,最后由考试来断定学习成果。这种模式的后果大家都清楚:我们学了十几年英语,可有不少人还只会四五千个单词,写百把字的文章,不会说,不能听;尤其要命的是,看不懂VC的帮助文档。我们要象婴儿一样,在使用中学会语法、修辞和词汇;一开始肯定只能编出最糟糕的,就好像小孩开始只会说:“爸爸坐车车”;但“爸爸”“车车”这些词对小孩是最需要的,学习的效率反而最高。学VC的时候,应从我的需要出发,先做一个最简单的东西,然后一点一点复杂起来,象滚雪球一样。

       学习VC尤其要和别人交流,你苦苦思考不明白的,也许别人早就解决了。一定要善于学习:从书店找出有用的书;向身边的高手学习;寻找可以利用的源代码、类库、组件、控件、库;如果能上互连网,那就更加能解决问题了。

       ================================================

       Lchrennew:

       以下内容由于曾经进行编辑替换(Replace)不让通过,所以采用附加(Append)的方式,在下面把我的版本加上,请大家对比:

       VC是英文的缩写形式,其完整形式基本上包括如下几方面:

       1. 医学、药品学、生物化学:Vitamins C,简写VC,音译“维他命C”,中文意译为维生素C,又被称为抗坏血酸。