1.ç¨åºåå¦çå¿
å¤çå
大软件
2.Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例
3.å缩å½å±åäºçç¨ä»ä¹è½¯ä»¶å缩
4.电脑ctrl+ alt+ s是屏软什么意思
5.手机什么软件能代替鼠标宏
6.androidå¼å设置å±è½å½å¶
ç¨åºåå¦çå¿ å¤çå 大软件
ç¨åºåå¦çå¿ å¤çå 大软件ç¨åºåå å¤§å¿ å¤è½¯ä»¶
%ç人é½æ²¡è§è¿
1.Giphy Capture
Giphy Cap tireæ¯ä¸ä¸ªä¸ä¸ºManç¨æ·åå¤çä¸æ¬¾GIFå¶
ä½å¨ï¼éåé常ç®åï¼é¤äºå¯ä»¥å½å±ä¹å¤ï¼è¿å¯ä»¥
对çæçGIFè¿è¡ç¼è¾ï¼ å大å°è°èãé¿åº¦æ§å¶ã
ææ¾ç顺åºçï¼å¦æä½ æ³åæç« æ¶ï¼æ³è¦å¨æç« æ
å ¥ä¸äºGIFå¨å¾æ¥è®©ä½ ç代ç åç¨åºæ´å¥½çè§£ï¼ è¿
æ¬¾å·¥å ·å°±å¾éå
2.json editor online
json editor onlineæ¯ä¸ä¸ªç®åãçµæ´»ãå¯è§åå¨çº¿ç
JSONç¼è¾å¨ï¼ æ¯æå·®å¼å对æ¯ï¼å¯æ¥çãç¼è¾å
æ ¼å¼åJSONæ°æ®ï¼ is onæ ¼å¼æ¯å¼åä¸æ¯è¾å¸¸è§çï¼
is oné 读åæ ¼å¼åå°±æ¯ä¸ä¸ªå¸¸è§çå·¥å ·ï¼ ç¨äºè¿ä¸ª
å·¥å ·ï¼å ¶ä»ççªç¶å°±ä¸é¦äº!
3.oh-my-zsh
må½ä»¤è¡æ¯ç¨åºåçæç±ï¼ æ以è¿æ¬¾æ¿ä»£bashå·¥å ·
å¿ é¡»å¾æ¿åºæ¥ï¼ ç´¢ç¶æ们çbash shellåè½ä¹å¾å¥½äº
ãä½è¿æ¯ç¥æ¾æ´ç´ ï¼æ²¡æé£ä¹çé ·ç«ï¼æ以è¿ä¸ª
oh-my-zshå°±å¼å¾ä¸æï¼ é½è¯´å®æ¯æ好ç¨çshellï¼
没æä¹ä¸ï¼å å«
+æ件ã+主é¢ãGit Hub ï¼ ææ°ï¼ ä»è¿
ä¸ç³»åçæ°åä¸å°±è½å¤æè§å°è¿ä¸ªshellå·¥å ·çå®å
以å强大ä¹å¤ï¼å°ç½ç¨åºåä¹å¯ä»¥ç¨ï¼é«ç«¯å¤§æ°ä¸
档次!
4.Process On
Process onæ¯ä¸æ¬¾å¨çº¿ä½å¾å·¥å ·ï¼ å¯å¶ä½æ维导å¾ã
æµç¨å¾ãUMLå¾ãçé¢åå设计ãç»ç»ç»æå¾çç
ä¸æä¹å¾ç®åï¼å¹¶ä¸å è´¹ãå å®è£ ï¼å¨çº¿å³å¯æ
ä½ï¼å¤§å®¶é½ç¥é身为ç¨åºåï¼å¿ é¡»æ¶å»ä¿ææè·¯æ¸
æ°ï¼è¿æ ·ä»£ç ååºæ¥å°±æ¯ååéçäºæ ï¼æ以ä½å¾
æ¯å¾æå¿ è¦çï¼åæ£è¿ä¸ªå·¥å ·æ¯çé¦!
5.Copy Translator
Copy Translatoræ¯ä¸æ¬¾é常åºè²çç¿»è¯è½¯ä»¶ï¼ å è´¹ä¸
å¼æºï¼æ¯æå åç§ä¸åè¯è¨çç¿»è¯ï¼ä¸ç®¡ä½ æ¯ç§ç
人åè¿æ¯ç®æ³å·¥ç¨å¸(å½ç¶è±æå¾å¥½çå¯ä»¥å¿½ç¥)è¿
款翻è¯è½¯ä»¶é½å¾éåï¼ç¸æ¯å ¶ä»ç¿»è¯è½¯ä»¶
Copy Transat orå¯ä»¥è§£å³ä¸äºå¤ä½çæå¥åæ¢è¡å¸¦æ¥
çä¹±ç é®é¢ï¼ä¹ä¼æ´é«æï¼
åªéå¤å¶ææ¬å°ç²è´´æ¿ï¼ä¸ä¸ç§å³å¯æ¥çç¿»è¯ç»æ
ï¼æé«å·¥ä½æçã
6.VisualStudiop Code
VisualStudio Codeæ¯ä¸ä¸ªè¿è¡ä¸MacOSXãWindows
åLinuxä¹ä¸çï¼ é对äºç¼åç°ä»£Webåäºåºç¨çè·¨
å¹³å°æºä»£ç ç¼è¾å¨ï¼å å«ææ主æµçå¼åè¯è¨çè¯
æ³é«äº®ãåªè½ä»£ç è¡¥å ¨ãèªå®ä¹çé®ãæ¬å·å¹é ã
代ç ç段ã代ç 对æ¯DiffãGitçç¹å¾ï¼ æ¯ææ件æ©
å±ï¼ å¯ä»¥éè¿å®è£ æ件æ¥æ¯æC++ãC#ãPythonã
PHPãJavaçè¯è¨ãåæ¶é对ç½é¡µå¼ååäºç«¯åºç¨å¼
åä¹åäºä¼åã
Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例
简介发现、定性与定位
总结
跟不上旋律节奏的屏软VSYNC
严重异常耗时的dequeueBuffer
VirtualDisplay合成耗时
结论
FPS
初步定位问题
定性问题
定位问题
成果展示
参考
简介本文记录一次Android图形性能问题的分析过程——发现、定性和定位图形性能问题,屏软以及探讨的屏软性能优化方案。
环境:Android Q + MTK + ARM Mali-G。屏软
所分析的屏软源码cp性能问题(下称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执行绘制指令耗时变长。
初步定位问题本节记录初步的分析思路和定位过程。首先我们完成实验(启停后台录屏并滑动屏幕触发渲染)、观测以及记录,拿到了后台录屏启停情况下的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动作的耗时,上屏耗时额外不到~1msHz下,上述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后由右边的消费者(图形栈)进行消费(合成、上屏显示),然后释放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的合成工作的消耗将显著降低。
结论本小节综合上述三个小节的分析,对节”定性问题“下一个结论。
耗时的本质已经被看透,录屏工具申请创建的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,而
å缩å½å±åäºçç¨ä»ä¹è½¯ä»¶å缩
ç¾åº¦å缩ã
ç¾åº¦ç§°ç¾åæ¯å ¬å¼æºä»£ç çä¸æ¬¾æ°¸ä¹ å è´¹çå缩软件ï¼ç¨æ·å¯ä»¥å¨å°ç¾åå®ç½ä¸è½½æºä»£ç ãæ¯æBAI/7Z/ZIP/TARçæ件çå缩ä¸è§£åï¼å¹¶è½è§£åRARãZIPã7ZãACEãARJãBZ2ãCABãGZãISOãJARãLAHãTARãZãUUEçæ ¼å¼æ件ãç¾åå ·ææ´å¿«çé度åæ´é«çå缩çï¼å¯ä»¥æ´å®å ¨çä¼ éæ件ã
电脑ctrl+ alt+ s是什么意思
ctrl+alt+s是QQ的录屏快捷键,并不是截屏快捷键,其保存位置是自定义的,可在保存录屏文件的时候自行设置保存位置。常用的截图快捷键有以下这些,其保存位置都可以进行自定义。
1、打印屏幕快照(PrtScn)键:这是最简单的截图方法。按下键盘上的“PrtScn”键,整个屏幕就会被截图到剪贴板,然后可以粘贴到Paint等图像编辑软件中进行编辑和保存。
2、Snipping Tool工具:Windows系统自带的Snipping Tool工具可以截取屏幕的特定区域。打开Snipping Tool,选择截图模式,然后拖动鼠标选择需要截图的区域,release鼠标左键完成截图,会自动保存并打开预览窗口,可以编辑、保存或共享。
3、Windows+Shift+S:这是Windows新增的快捷键组合,可以截取屏幕的选定区域,截图内容会直接保存到剪贴板,这种截图方式简单快捷。按下快捷键组合后鼠标指针会变为十字线,选择需要截图的区域后释放左键完成截图。
4、Ctrl+Alt+A:QQ的截图快捷键。
电脑截图工具推荐
1、Snipaste
《Snipaste》是一款免费截图软件,支持任何屏幕位置截图,支持窗口截图,支持快捷方式(F1)截图。内置强大易用的截图、贴图以及标注功能,具有丰富的自定义选项,能够实现你的各种截图需求。
2、ShareX
《ShareX》是一款免费开放源代码的高级截图工具,支持整个屏幕、区域、滚动,自动模式,甚至还支持屏幕录制以及GIF录制功能。还可以对截图进行OCR文字识别,注释,颜色拾取,哈希检查,尺子功能,水印,上传,打印等操作,以快速,高效的截图效率给你更加出色的截图体验。
3、Flameshot
《Flameshot》是一款功能强大的开源屏幕截图和注释工具,支持Windows、Linux和MAC。提供了多种可用的标记工具,包括手绘图、线条、箭头、方框、圆圈、突出显示和模糊。此外,您可以自定义许多这些图像注释工具的颜色、大小和/或厚度。
手机什么软件能代替鼠标宏
除了按键精灵,还有什么可以自动按键的软件?
可以自动按键的软件列举:
1、AutoHotkey
AutoHotkey是Windows平台下开放源代码的免费的热键脚本语言,属于自动按键的软件。可以通过发送键盘或鼠标的键击动作命令来实现操作的自动化,还可以通过命令调用系统接口及程序并创建执行程序。
2、绿点按键
绿点按键是一款模拟鼠标键盘系统的软件。可以把鼠标和键盘设置成自动点击后进行播放;可以根据点的颜色来判断是否运行按键文件;还可以查找屏幕上的点来运行按键文件。
3、脚本精灵
脚本精灵是一款模拟手机触摸和按键的免费的工具软件。可以模拟用户的操作,即通过运行各种自定义脚本来代替人手在手机上进行各种重复的操作。
4、TC脚本开发工具
TC脚本开发工具是一款多线程的图形界面脚本制作开发工具软件,支持中英文双语言编写脚本程序。配有TC教程视频和帮助文档,不需要编程知识就可以做出TC脚本;还可以模拟鼠标按键操作,TC可以在电脑前完成双手可以完成的动作。
扩展资料
下载这些自动按键的软件需要注意:
1、要注意软件的来源,一般来说软件需要到官方网站上下载,以确保下载的软件安全无毒。
2、在选择软件时,最好选择其最新版本的,而且下载完软件后可以使用安全杀毒软件来扫描以保障电脑的安全。
3、在安装路径选择上,除有些软件必须安装在C盘的以外,尽量不要选择C盘,因为C盘为系统盘,安装太多软件会使电脑变卡。
有什么手机软件能控制电脑的鼠标并开启鼠标的宏操作
前两天刚下的Monect很赞。不过只有安卓客户端,
iphone没法用,而且不是特别稳定。
不过功能还是很多很实用的。可以把手机当屏幕,摇杆,手柄,键盘等等等等。
iphone可以用xdisplay,不过功能没有前者丰富,优点是稳定。
机上代替按键精灵的软件是这么用的
1、点击打开手机上的按键精灵,可以看到这里有两种脚本开发方式:开发者脚本和录制脚本。所谓开发者脚本即用户可以自行编辑脚本程序,而录制脚本则是通过录屏记录用户操作生成脚本。
2、首先介绍比较简单和常用的脚本开发方式—录制脚本。点击录制脚本,进入到录制脚本界面,可以看到手机侧面音量+键为开始录制,音量-键为停止录制,记住即可;点击屏幕中间的马上录制。
3、退出手机按键精灵,转到需要录制脚本的界面(游戏界面或者刷帖界面),按下手机侧面的音量+键开始录制,然后在游戏界面操作,操作完成后,点击音量-键停止录制,最后输入录制脚本名字。这样在我的录制脚本目录下就可以找到该脚本了。
按键精灵是一款模拟鼠标键盘动作的软件。通过制作脚本,可以让按键精灵代替双手,自动执行一系列鼠标键盘动作。
手机可以当鼠标用吗?
可以的。
手机上安装《袋鼠输入》软件就可以让手机当鼠标用。可以通过手机在电脑端实现语音、手写输入,同时可让手机变为免费无线鼠标、电脑视频遥控器、PPT遥控器、游戏手柄,未来还有更多可能。
手机屏幕代替电脑触摸板,可以在手机屏幕上实现单指控制光标、双指滚动屏幕、三指拖动窗口,支持手机横屏使用。扔掉鼠标,手机变成视频遥控器,支持爱奇艺、腾讯视频、优酷、乐视视频、搜狐视频、芒果tv等在线视频,支持百度影音、暴风影音、迅雷看看、射手等播放器。
扩展资料:
手机可以当鼠标步骤:
1、在电脑端打开“袋鼠输入”软件。
2、在手机端点击左下角“鼠标”按钮,可以在上方控制面板进行操作。
3、下方是鼠标左右键,中间箭头是滚轮键,点击输入即可在电脑端用手机输入文字,点击语音或手写可以输入文字。
百度百科—袋鼠输入
有没有这样的app类似宏按一键就自动打开手机软件
那个专门的APP没有,你可以通过一些脚本工具软件来实现,我们很多做办公自动化辅助的都在用“阿冲点击王”软件,它是能给你自动打开特定软件程序,自动鼠标点击,自动输入录入等常用功能。你在阿冲点击王软件里只需依次设置好你的操作动作,以后它就可以自动帮你做这些繁琐的事情了。
什么软件可以代替鼠标宏
收到团队求助才过来的
可以肯定是可以的
问题是怎么做
说实话我还真没研究过这个问题~
某些高端鼠标自己就可以实现
比如我现在的MicrosoftSideWinder鼠标
自带宏录制按键,只要提前编辑好宏命令
到时候即可用你想要的键盘行为来替代鼠标按键
甚至可以按一个键实现预先设定好的一连串动作~~
还有就是Linux下可以通过修改系统配置文件从而改变鼠标按键的预定义动作~也许也可以实现~
Windows系统应该是没有这个功能的~
我也就能帮到这么多了~~
androidå¼å设置å±è½å½å¶
项ç®å¼åä¸ï¼ä¸ºäºç¨æ·ä¿¡æ¯çå®å ¨ï¼ä¼æç¦æ¢é¡µé¢è¢«æªå±ãå½å±çéæ±ãè¿ç±»èµæï¼å¨ç½ä¸æå¾å¤ï¼ä¸è¬é½æ¯éè¿è®¾ç½®ActivityçFlag解å³ï¼å¦ï¼
//ç¦æ¢é¡µé¢è¢«æªå±ãå½å±getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
è¿ç§è®¾ç½®å¯è§£å³ä¸è¬çé²æªå±ãå½å±çéæ±ã
å¦æ页é¢ä¸æå¼¹åºPopupwindowï¼å¨å½å±è§é¢ä¸çæææ¯ï¼
éPopupwindowåºå为é»è²
ä½Popupwindowåºåä»ç¶æ¯å¯ä»¥çå°ç
å¦ä¸é¢ä¸¤å¼ Gifå¾æ示ï¼
æªè®¾ç½®FLAG_SECUREï¼å½å±çææï¼å¦ä¸å¾ï¼gitå¾çä¸é´çæ°´å°å¿½ç¥ï¼ï¼
设置äºFLAG_SECUREä¹åï¼å½å±çææï¼å¦ä¸å¾ï¼gitå¾çä¸é´çæ°´å°å¿½ç¥ï¼ï¼
åå åæ
çå°äºä¸é¢çææï¼æ们å¯è½ä¼æçé®PopupWindowä¸åDialogæèªå·±çwindow对象ï¼èæ¯ä½¿ç¨WindowManager.addViewæ¹æ³å°Viewæ¾ç¤ºå¨Activityçªä½ä¸çãé£ä¹ï¼Activityå·²ç»è®¾ç½®äºFLAG_SECUREï¼ä¸ºä»ä¹å½å±æ¶è¿è½çå°PopupWindowï¼
æ们å éè¿getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);æ¥åæä¸æºç ï¼
1ãWindow.java
//windowå¸å±åæ°private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//æ·»å æ è¯public void addFlags(int flags) {
setFlags(flags, flags);
}//éè¿mWindowAttributes设置æ è¯public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}//è·å¾å¸å±åæ°å¯¹è±¡ï¼å³mWindowAttributespublic final WindowManager.LayoutParams getAttributes() { return mWindowAttributes;
}
éè¿æºç å¯ä»¥çå°ï¼è®¾ç½®windowå±æ§çæºç é常ç®åï¼å³ï¼éè¿windowéçå¸å±åæ°å¯¹è±¡mWindowAttributes设置æ è¯å³å¯ã
2ãPopupWindow.java
//æ¾ç¤ºPopupWindowpublic void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parent.getRootView());
showAtLocation(parent.getWindowToken(), gravity, x, y);
}//æ¾ç¤ºPopupWindowpublic void showAtLocation(IBinder token, int gravity, int x, int y) { if (isShowing() || mContentView == null) { return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
//å建Windowå¸å±åæ°å¯¹è±¡
final WindowManager.LayoutParams p =createPopupLayoutParams(token);
preparePopup(p);
p.x = x;
p.y = y;
invokePopup(p);
}//å建Windowå¸å±åæ°å¯¹è±¡protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource(); if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
} if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
} if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p;
}//å°PopupWindowæ·»å å°Windowä¸private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) {
p.packageName = mContext.getPackageName();
} final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p); if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
éè¿PopupWindowçæºç åæï¼æ们ä¸é¾çåºï¼å¨è°ç¨showAtLocationæ¶ï¼ä¼åç¬å建ä¸ä¸ªWindowManager.LayoutParamså¸å±åæ°å¯¹è±¡ï¼ç¨äºæ¾ç¤ºPopupWindowï¼è该å¸å±åæ°å¯¹è±¡ä¸å¹¶æªè®¾ç½®ä»»ä½é²æ¢æªå±Flagã
å¦ä½è§£å³
åå æ¢ç¶æ¾å°äºï¼é£ä¹å¦ä½å¤çå¢ï¼
åå头åæä¸Windowçå ³é®ä»£ç ï¼
//éè¿mWindowAttributes设置æ è¯public void setFlags(int flags, int mask) { final WindowManager.LayoutParams attrs = getAttributes();
attrs.flags = (attrs.flags&~mask) | (flags&mask);
mForcedWindowFlags |= mask;
dispatchWindowAttributesChanged(attrs);
}
å ¶å®åªéè¦è·å¾WindowManager.LayoutParams对象ï¼å设置ä¸flagå³å¯ã
ä½æ¯PopupWindow并没æåActivityä¸æ ·æç´æ¥è·å¾windowçæ¹æ³ï¼æ´å«è¯´è®¾ç½®Flagäºãæ们ååæä¸PopupWindowçæºç ï¼
//å°PopupWindowæ·»å å°Windowä¸private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor(); //æ·»å View
mWindowManager.addView(decorView, p); if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
æ们è°ç¨showAtLocationï¼æç»é½ä¼æ§è¡mWindowManager.addView(decorView, p);
é£ä¹æ¯å¦å¯ä»¥å¨addViewä¹åè·åå°WindowManager.LayoutParamså¢ï¼
çæ¡å¾ææ¾ï¼é»è®¤æ¯ä¸å¯ä»¥çãå 为PopupWindow并没æå ¬å¼è·åWindowManager.LayoutParamsçæ¹æ³ï¼èä¸mWindowManagerä¹æ¯ç§æçã
å¦ä½æè½è§£å³å¢ï¼
æ们å¯ä»¥éè¿hookçæ¹å¼è§£å³è¿ä¸ªé®é¢ãæ们å 使ç¨å¨æ代çæ¦æªPopupWindowç±»çaddViewæ¹æ³ï¼æ¿å°WindowManager.LayoutParams对象ï¼è®¾ç½®å¯¹åºFlagï¼ååå°è·å¾mWindowManager对象å»æ§è¡addViewæ¹æ³ã
é£é©åæï¼
ä¸è¿ï¼éè¿hookçæ¹å¼ä¹æä¸å®çé£é©ï¼å 为mWindowManageræ¯ç§æ对象ï¼ä¸åPublicçAPIï¼è°·æåç»å级Androidçæ¬ä¸ä¼èèå ¶å ¼å®¹æ§ï¼æ以æå¯è½åç»Androidçæ¬ä¸æ¹äºå ¶å称ï¼é£ä¹æ们éè¿åå°è·å¾mWindowManager对象ä¸å°±æé®é¢äºãä¸è¿ä»å代çæ¬çAndroidæºç å»çï¼mWindowManager被æ¹çå çä¸å¤§ï¼æ以hookä¹æ¯å¯ä»¥ç¨çï¼æ们尽éå代ç æ¶èèä¸è¿ç§é£é©ï¼é¿å 以ååºé®é¢ã
public class PopupWindow {
...... private WindowManager mWindowManager;
......
}
èaddViewæ¹æ³æ¯ViewMangeræ¥å£çå ¬å ±æ¹æ³ï¼æ们å¯ä»¥æ¾å¿ä½¿ç¨ã
public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);
}
åè½å®ç°
èèå°hookçå¯ç»´æ¤æ§åæ©å±æ§ï¼æ们å°ç¸å ³ä»£ç å°è£ æä¸ä¸ªç¬ç«çå·¥å ·ç±»å§ã
package com.ccc.ddd.testpopupwindow.utils;
import android.os.Handler;
import android.view.WindowManager;
import android.widget.PopupWindow;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class PopNoRecordProxy implements InvocationHandler { private Object mWindowManager;//PopupWindowç±»çmWindowManager对象
public static PopNoRecordProxy instance() { return new PopNoRecordProxy();
} public void noScreenRecord(PopupWindow popupWindow) { if (popupWindow == null) { return;
} try { //éè¿åå°è·å¾PopupWindowç±»çç§æ对象ï¼mWindowManager
Field windowManagerField = PopupWindow.class.getDeclaredField("mWindowManager");
windowManagerField.setAccessible(true);
mWindowManager = windowManagerField.get(popupWindow); if(mWindowManager == null){ return;
} //å建WindowManagerçå¨æ代ç对象proxy
Object proxy = Proxy.newProxyInstance(Handler.class.getClassLoader(), new Class[]{ WindowManager.class}, this); //æ³¨å ¥å¨æ代ç对象proxyï¼å³ï¼mWindowManager对象ç±proxy对象æ¥ä»£çï¼
windowManagerField.set(popupWindow, proxy);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //æ¦æªæ¹æ³mWindowManager.addView(View view, ViewGroup.LayoutParams params);
if (method != null && method.getName() != null && method.getName().equals("addView")
&& args != null && args.length == 2) { //è·åWindowManager.LayoutParamsï¼å³ï¼ViewGroup.LayoutParams
WindowManager.LayoutParams params = (WindowManager.LayoutParams) args[1]; //ç¦æ¢å½å±
setNoScreenRecord(params);
}
} catch (Exception ex) {
ex.printStackTrace();
} return method.invoke(mWindowManager, args);
} /
*** ç¦æ¢å½å±
*/
private void setNoScreenRecord(WindowManager.LayoutParams params) {
setFlags(params, WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
} /
*** å 许å½å±
*/
private void setAllowScreenRecord(WindowManager.LayoutParams params) {
setFlags(params, 0, WindowManager.LayoutParams.FLAG_SECURE);
} /
*** 设置WindowManager.LayoutParams flagå±æ§ï¼åèç³»ç»ç±»Window.setFlags(int flags, int mask)ï¼
*
* @param params WindowManager.LayoutParams
* @param flags The new window flags (see WindowManager.LayoutParams).
* @param mask Which of the window flag bits to modify.
*/
private void setFlags(WindowManager.LayoutParams params, int flags, int mask) { try { if (params == null) { return;
} params.flags = (params.flags & ~mask) | (flags & mask);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Popwindowç¦æ¢å½å±å·¥å ·ç±»ç使ç¨ï¼ä»£ç 示ä¾ï¼
//å建PopupWindow
//æ£å¸¸é¡¹ç®ä¸ï¼è¯¥æ¹æ³å¯æ¹æå·¥åç±»
//æ£å¸¸é¡¹ç®ä¸ï¼ä¹å¯èªå®ä¹PopupWindowï¼å¨å ¶ç±»ä¸è®¾ç½®ç¦æ¢å½å±
private PopupWindow createPopupWindow(View view, int width, int height) {
PopupWindow popupWindow = new PopupWindow(view, width, height); //PopupWindowç¦æ¢å½å±
PopNoRecordProxy.instance().noScreenRecord(popupWindow); return popupWindow;
} //æ¾ç¤ºPopupwindow
private void showPm() {
View view = LayoutInflater.from(this).inflate(R.layout.pm1, null);
PopupWindow pw = createPopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
pw1.setFocusable(false);
pw1.showAtLocation(this.getWindow().getDecorView(), Gravity.BOTTOM | Gravity.RIGHT, PopConst.PopOffsetX, PopConst.PopOffsetY);
}
å½å±ææå¾ï¼
怎样把微博上的视频保存到电脑?
借助录屏辅助工具,用这个方法就可以保存原画质无水印的微博视频,Windows系统和Mac系统,苹果手机系统等都可以使用!
嗨格式录屏大师
首先,软件的使用是非常简单的,核心功能是录屏,还可以对录制的视频文件进行剪辑,可谓“一举两得”。从软件的首界面。
我们可以看到,可以根据自己录屏的内容场景,选择对应的录屏模式。
比方说录制微博上的视频,直接选择“全屏录制”或者“区域录制”模式,就能录制微博上的视频啦!
比方对于游戏爱好者来说,可以直接选“游戏模式”。紧接着,进入到软件的参数设置界面,各类功能是很齐全的。例如:视频格式、视频清晰度、帧率、声音来源、声音来源等等。最后,设置参数全部操作完成之后,点击右下角“开始录制-REC”按钮即可进行录屏了。录屏完成之后,也可以在视频列表页,对想要编辑的视频进行编辑,也就是剪去片头、片尾以及中间多余的部分。