Android机能测评与优化-流通度测评
流通度应该是终端用户感知最显著的机能目标了,提拔流通度是提拔用户体验性价比最高的体式格局之一,我们先来看看在体系层面上Google为了优化流通度做了哪些勤奋
Vsync(垂直同步)
垂直同步是一个游戏中很罕见的观点,它的涌现是为了处置惩罚如下图的画面扯破的题目
究其原因是屏幕的革新并非瞬时完成的,而GPU发作一帧新画面的速率和屏幕革新速率差异步,当GPU速率又大于显现器的革新速率,在显现器从上到下扫描显现的历程当中图象缓冲就被更新了,显现器并不知道这个变化照样继承扫描,就发作了画面扯破
android 4.1的黄油设计引入垂直同步今后,只要在吸收到Vsync信号,体系才会让CPU/GPU最先下一帧的衬着事变,即每一个屏幕革新周期之间最多只会发作一帧画面,以此防止画面扯破
一旦收到VSync信号,马上就最先实行下一帧的绘制事变。如许也能够大大下降Jank涌现的几率。只须要保证衬着一帧画面的时候在1/60s(16ms)就好了
Triple Buffer
先看看双缓冲的模子
两个缓存辨别别为 Back Buffer 和 Frame Buffer。GPU 向 Back Buffer 中写数据,屏幕从 Frame Buffer 中读数据。VSync 信号担任调理从 Back Buffer 到 Frame Buffer 的复制操纵,可以为该复制操纵在霎时完成(只是交换了内存地址)。
假如一切衬着操纵都在16ms以内完成,两重缓冲能够很好的事变,然则衬着耗时凌驾16ms呢
第一个B画面的衬着凌驾了16ms,由于此时B画面占有了Back Buffer,所以当吸收到下一帧的VSync信号时体系没有最先衬着事变,致使jank的发作
而三重缓冲增添了一个Back Buffer
在吸收到VSync信号,B画面还在衬着,由于CPU已余暇了,而且有另一块缓冲区,所以同时最先了下一帧的衬着事变
三重缓冲能够更充足的应用CPU/GPU提拔画面显现的流通度
- 为何不继承增添缓冲区来提拔流通度呢?
硬件加快就是依靠GPU完成图形绘制加快。
能够看出GPU的ALU(算术逻辑单元)比CPU多的多,而图形处置惩罚和栅格化操纵现实就是大批的数学盘算,所以用GPU去衬着图形比CPU快的多
android 3.0引入了硬件加快,android4.0今后默许开启了硬件加快
当启动硬件加快后,Android 运用 “DisplayList” 组件举行绘制而非直接运用 CPU 绘制每一帧。DisplayList 是一系列绘制操纵的纪录,笼统为 RenderNode 类。
如许间接的举行绘制操纵的长处许多:
- DisplayList 建立时并非真正的绘制,只是纪录了绘制的操纵,所以对DisplayList的修正开支比较小。
- 特定的View属性变化(如 translation, scale 等)只是修正了View对应的DisplayList的属性,而不须要从新天生新的DisplayList。
- 当晓得了一切绘制操纵后,能够针对其举行优化:比方,一切的文本能够一同举行绘制一次。
- 能够将对 DisplayList 的处置惩罚转移至另一个线程(非 UI 线程)。
RenderThread是Android 5.0引入的功用。
衬着事变的真正实行者是 GPU,而 GPU是不知道什么是动画的:实行动画的唯一门路就是将每一帧的差异绘制操纵分发给 GPU,但该逻辑自身不能在 GPU 上实行。
而假如在 UI 线程实行该操纵,恣意的重操纵都将壅塞新的绘制指令及时分发,动画就很轻易涌现耽误和卡顿。
增加一个RenderThread特地用来处置惩罚衬着的相干操纵,UI线程尽管盘算天生一个DisplayList,剩下的衬着相干的事变就交给RenderThread,如许减轻了UI线程的累赘,也提拔了动画的流通度
- Android 6.0差异场景的软/硬件绘制剖析
衬着场景 | 纯软件绘制 | 硬件加快 | 加快效果剖析 |
---|---|---|---|
页面初始化 | 绘制一切View | 建立一切DisplayList | GPU分管了庞杂盘算使命 |
在一个庞杂页面挪用背景通明TextView的setText,且挪用后其尺寸位置稳定 | 重绘脏区一切View | TextView及每一级父View重修DisplayList | 堆叠的兄弟节点不需CPU重绘,GPU会自行处置惩罚 |
TextView逐帧播放Alpha / Translation / Scale动画 | 每帧都要重绘脏区一切View | 除第一帧同场景2,今后每帧只更新TextView对应RenderNode的属性 | 革新一帧机能极大进步,动画流通度进步 |
修正TextView通明度 | 重绘脏区一切View | 直接挪用RenderNode.setAlpha更新 | 加快前需全页面遍历,并重绘许多View;加快后只触发DecorView.updateDisplayListIfDirty,不再往下遍历,CPU实行时候可疏忽不计 |
体系层面关于UI流通度的优化步伐
- VSync(处置惩罚画面扯破)
- 三重缓冲(提拔CPU/GPU应用率)
- 硬件加快(运用GPU加快画面衬着)
- RenderThread(减轻UI线程累赘)
google为了画面的流通度可谓是用心良苦,作为一个有寻求的开发者,我们固然也要朝着如丝般顺滑勤奋,起首先从数据网络最先
开启GPU显现形式剖析
这个就是传说中的玄学曲线了,能够经由历程它可视化的直观控制当前界面是不是流通
绿线是16ms的分界线,每一个竖条代表衬着一帧的耗时,要保证流通理论上须要每一条都在绿线之下
有几个症结点须要注重一下
- 表中的色彩跟现实的真机色彩有一些差异
- 虽然叫GPU显现形式剖析,然则表中的一切阶段都发作在CPU中
长处:
- 及时
- 直观
瑕玷:
- 没法量化
android 6.0以上装备运用adb shell dumpsys gfxinfo <PACKAGE_NAME>能够猎取到Aggregate frame stats
Stats since: 752958278148ns
Total frames rendered: 82189
Janky frames: 35335( 42.99%)
90th percentile: 34ms
95th percentile: 42ms
99th percentile: 69ms
NumberMissed Vsync: 4706//垂直同步失利
NumberHigh input latency: 142//由于处置惩罚输入耗时
NumberSlow UI thread: 17270//UI线程使命太重形成的超时
NumberSlow bitmap uploads: 1542//加载bitmap致使的超时
NumberSlow draw: 23342//绘制太慢致使的超时
运用adb shell dumpsys gfxinfo <PACKAGE_NAME> reset能够重置数据,完毕历程不会重置Aggregate frame stats
运用adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats 能够猎取上120帧的细致耗时,这个数据跟GPU显现形式的条形图是对应的
数据的单元是纳秒,经由历程统计盘算能够获得每一帧的各阶段耗时
详细每一列数据代表什么能够看下面的链接
Framestats data format
长处:
- 数据细致
- 有团体的统计
瑕玷:
- 不是及时数据
- 只要120帧
- framestats数据须要进一步处置惩罚
从 7.0(API 24)最先,安卓 SDK 新增 OnFrameMetricsAvailableListener 接口用于供应帧绘制各阶段的耗时,数据源与 GPU Profile 雷同。
publicvoidstartFrameMetrics(View view){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
finalString activityName = getClass.getSimpleName;
listener = newWindow.OnFrameMetricsAvailableListener {
privateintallFrames = 0;
privateintjankyFrames = 0;
@Override
publicvoidonFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
intdropCountSinceLastInvocation){
FrameMetrics frameMetricsCopy = newFrameMetrics(frameMetrics);
allFrames ;
floattotalDurationMs = ( float) ( 0.000001* frameMetricsCopy.getMetric(
FrameMetrics.TOTAL_DURATION));
if(totalDurationMs > 17) {
jankyFrames ;
String msg = String.format( "Janky frame detected on %s with total duration: %.2fmsn",
activityName, totalDurationMs);
floatlayoutMeasureDurationMs = ( float) ( 0.000001* frameMetricsCopy.getMetric(
FrameMetrics.LAYOUT_MEASURE_DURATION));
floatdrawDurationMs = ( float) ( 0.000001* frameMetricsCopy.getMetric(
FrameMetrics.DRAW_DURATION));
floatgpuCommandMs = ( float) ( 0.000001* frameMetricsCopy.getMetric(
FrameMetrics.COMMAND_ISSUE_DURATION));
floatothersMs = totalDurationMs - layoutMeasureDurationMs - drawDurationMs - gpuCommandMs;
floatjankyPercent = ( float) jankyFrames / allFrames * 100;
msg = String.format( "Layout/measure: %.2fms, draw:%.2fms, gpuCommand:%.2fms others:%.2fmsn",
layoutMeasureDurationMs, drawDurationMs, gpuCommandMs, othersMs);
msg = "Janky frames: " jankyFrames "/" allFrames "(" jankyPercent "%)"
dropCountSinceLastInvocation;
Log.e( "FrameMetrics", msg);
}
}
};
getWindow.addOnFrameMetricsAvailableListener(listener, newHandler);
} else{
Log.w( "FrameMetrics", "FrameMetrics can work only with Android SDK 24 (Nougat) and higher");
}
}
FrameMetrics中包含了衬着一帧各个阶段的耗时数据
长处:
- 及时
- 数据周全
- 直接给出了每一个阶段的耗时,不必再算一遍了
瑕玷:
- 只支撑7.0及以上体系
这类检测流通度的要领起源于FaceBook的一次关于UI流通度的手艺分享The Road to 60FPS
Choreographer是一个吸收VSync信号并分发的组件,Choreographer 收到关照顺次处置惩罚 Input、Animation、Draw,这三个历程都是经由历程 FrameCallback 回调的体式格局完成的。
经由历程Choreographer.FrameCallback能够猎取到VSync信号最先被处置惩罚的时候戳,减去上一个时候戳,能够近似为上一帧的衬着耗时(只盘算了UI线程的耗时,盘算不到衬着线程和GPU耗时)
publicvoidpostFrameCallback(View view){
lastTime = System.nanoTime;
Choreographer.getInstance.postFrameCallback( this);
}
@Override
publicvoiddoFrame(longframeTimeNanos){
//每一个FrameCallback都只会回调一次,所以须要在回调中注册下一帧VSync信号的回调
Choreographer.getInstance.postFrameCallback( this);
longjitterNanos = frameTimeNanos - lastTime;
if(jitterNanos > FRAME_INTERVAL_NANOS) {
Log.i(TAG,
"doFrame: lastTime:" lastTime " frameTimeNanos:" frameTimeNanos
" frame:" jitterNanos);
lastTime = frameTimeNanos;
}
Choreographer.FrameCallback和之前的gfxinfo的数据有一些差异
- Choreographer的回调是基于VSync信号的,而gfxinfo的数据是基于每一帧的衬着。
- 当画面没有发作变化,画面是不须要从新衬着的,此时在GPU显现形式上的条形图也不会挪动,framestats也不会纪录。
- 而VSync信号总是会发出来,所以Choreographer.FrameCallback在画面没有从新衬着时也会被回调到。
长处:
- 及时
- 基于VSync信号
瑕玷:
- 小于VSync信号距离的衬着时候不知道准确值
- 数据不够周全,不知道每一个阶段的详细耗时
- 对VSync信号的相应只依靠于UI线程余暇与否,衬着线程和GPU的壅塞没法推断
这个是网上比较多引见的猎取数据盘算fps的体式格局,然则本身实验猎取不到有用数据,所以盘算fps只能经由历程gfxinfo中猎取的数据了
小结
流通度相干数据的网络
- 每一帧的衬着时候
- GPU显现形式(直观,没法量化)
- adb shell gfxinfo(能够猎取到总的统计数据,和近来120帧的细致衬着数据)
- VSync信号被相应的时候
- Choreographer(没法准确盘算每一帧的耗时)
经由历程上面的引见,我们网络到了两种与流通度相干的中心数据
- 衬着每一帧的耗时
- VSync信号的相应时候点 那末这些数据怎样和流通度对应起来呢?
最直观的数据就是每一帧的衬着耗时了,当一帧耗时大于1/60s,纵然只凌驾1ms,这一帧依旧会错过一个VSync,到下一个VSync信号发作时才显现到屏幕上。看上去这个值是和流通度严密相干的,然则会不会有什么题目呢?
先来看一个极度状况
这里每一帧衬着都是凌驾16ms的,然则由于三重缓冲和RenderThread的存在,这里是有60fps的,只不过显现涌现了1/30s的耽误,并不会让用户发觉有什么异常。
所以纯真用单元时候衬着超时的帧数目来权衡流通度实在并不太合理,甚至于google做的那些底层优化就是为了让我们的应用在衬着凌驾16ms时依旧有优越的流通度表现。
帧率
FPS这是最经常使用的权衡画面流通度的目标。
不过在Android体系顶用FPS用fps来权衡流通度却有些缺点和不方便
- 起首假如画面没有变化,现实上是没有画面革新的,此时FPS为0,然则这类场景下并没有发作卡顿。
- 第二在android 7.0以下的体系中只能经由历程adb shell 来猎取120帧的数据,限定比较大
- 第三framestats数据虽然周全,然则盘算比较庞杂
- 有脏数据(flags不为0,测试如许的数据不多,影响有限,能够直接剔除)
- 两帧之间可能有堆叠(三重缓冲和RenderThread)
- 两帧之间可能有距离(画面不须要更新)
- 每一帧的时候不确定,按牢固帧数盘算FPS偏差比较大,按单元时候盘算须要本身处置惩罚时候周期
起首先申明下什么是丢帧,理论上屏幕的革新率是60hz,到场垂直同步机制今后,每秒衬着的画面上限就是60,由于某一个VSync信号发作时由于UI线程卡顿或许图象缓冲悉数被占有等状况致使这一个VSync没有被相应,这类状况就是丢帧。而在画面没有更新的状况下没有新的帧须要衬着,这类状况并非丢帧。
注重一下丢帧与衬着超时的区分
- 衬着超时涌现时,这一帧会被显现在屏幕上,不过会有耽误;而丢帧发作时这个周期的UI状况不会被显现到屏幕上
- 丢帧发作时肯定存在衬着超时;而由于RenderThread和三重缓冲机制的存在,发作了衬着超时也不肯定就会形成丢帧
由于丢帧的盘算现实是依靠于对VSync信号的相应,天然得用到Choreographer.FrameCallback
publicvoidpostFrameCallback(View view){
lastTime = System.nanoTime;
Choreographer.getInstance.postFrameCallback( this);
disposable = Flowable.interval( 200, TimeUnit.MILLISECONDS)
.map( newFunction<Long, Integer> {
@Override
publicInteger apply(Long aLong){
Log.i(TAG, "apply: " aLong);
intsm = (frameCount - lastFrameCount) * 1000/ 200;
lastFrameCount = frameCount;
returnsm;
}
})
.subscribe( newConsumer<Integer> {
@Override
publicvoidaccept(Integer sm)throwsException {
StringBuilder builder = newStringBuilder;
builder.append( "Smoothness :").append(sm);
for( inti = 0; i < skipCount.length; i ) {
if(i == 0) {
builder.append( " normal: ");
} else{
builder.append( " skip ").append(i).append( " frames: ");
}
builder.append(skipCount[i]);
}
Log.i(TAG, builder.toString);
}
}, newConsumer<Throwable> {
@Override
publicvoidaccept(Throwable throwable)throwsException {
}
});
}
@Override
publicvoiddoFrame(longframeTimeNanos){
Choreographer.getInstance.postFrameCallback( this);
longjitterNanos = frameTimeNanos - lastTime;
frameCount ;
//index示意两个VSync信号之间被疏忽的信号数目,即丢帧
//FRAME_INTERVAL_NANOS取了17ms,由于取1/60s转换成纳秒举行比较的话数值太接近了,稍有偏差都邑比较大的影响效果
intindex = ( int) (jitterNanos / FRAME_INTERVAL_NANOS);
if(index > 7) {
index = 7;
}
skipCount[index] ;
lastTime = frameTimeNanos;
}
- 丢帧凌驾50%
- 一连丢帧2 凌驾30%
- 丢帧18 示意发作了300ms以上的卡顿,申明有场景会形成严峻卡顿 …
版权声明
本文仅代表作者观点,不代表本站立场。如有侵权,请邮件248745074@qq.com删除
本文系作者授权发表,未经许可,不得转载。
本文地址:https://www.ishunhua.com/shuma/2299.html