Android 流程度評(píng)測(cè)知多少

和你一起終身學(xué)習(xí),這里是程序員Android
經(jīng)典好文推薦,通過(guò)閱讀本文,您將收獲以下知識(shí)點(diǎn):
一、FPS評(píng)測(cè)應(yīng)用流暢度不準(zhǔn)確
二、Choreographer幀率檢測(cè)原理
三、如何檢測(cè)
一、FPS評(píng)測(cè)應(yīng)用流暢度不準(zhǔn)確
說(shuō)到應(yīng)用的流暢度,都會(huì)想到FPS,系統(tǒng)獲取FPS的原理是:手機(jī)屏幕顯示的內(nèi)容是通過(guò)Android系統(tǒng)的SurfaceFLinger類(lèi),把當(dāng)前系統(tǒng)里所有進(jìn)程需要顯示的信息合成一幀,然后提交到屏幕上進(jìn)行顯示,F(xiàn)PS就是1秒內(nèi)SurfaceFLinger提交到屏幕的幀數(shù)。用FPS來(lái)評(píng)測(cè)一個(gè)應(yīng)用是否真的卡頓存在兩個(gè)問(wèn)題。
有的時(shí)候FPS很低,APP看起來(lái)卻很流暢;
APP停止操作之后,F(xiàn)PS還是在一直變化,這種情況是否會(huì)影響到FPS的準(zhǔn)確度?
有的時(shí)候FPS很低,APP看起來(lái)卻很流暢,是因?yàn)楫?dāng)前界面在1秒內(nèi)只需要10幀的顯示需求,當(dāng)然不會(huì)卡頓,此時(shí)FPS只要高于10就可以了,如果屏幕根本沒(méi)有繪制需求,那FPS的值就是0。
Android性能優(yōu)化第(四)篇---Android渲染機(jī)制說(shuō)過(guò),Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI的渲染,16ms沒(méi)完成繪制就會(huì)卡頓。VSync機(jī)制就像是一臺(tái)轉(zhuǎn)速固定的發(fā)動(dòng)機(jī)(60轉(zhuǎn)/s)。每一轉(zhuǎn)會(huì)帶動(dòng)著去做一些UI相關(guān)的事情,但不是每一轉(zhuǎn)都會(huì)有工作去做(就像有時(shí)在空擋,有時(shí)在D檔)。有時(shí)候因?yàn)楦鞣N阻力某一圈工作量比較重超過(guò)了16.6ms,那么這臺(tái)發(fā)動(dòng)機(jī)這秒內(nèi)就不是60轉(zhuǎn)了,當(dāng)然也有可能被其他因素影響,比如給油不足(主線程里干的活太多)等等,就會(huì)出現(xiàn)轉(zhuǎn)速降低的狀況。我們把這個(gè)轉(zhuǎn)速叫做流暢度。當(dāng)流暢度越小的時(shí)候說(shuō)明當(dāng)前程序越卡頓。
二、Choreographer幀率檢測(cè)原理
我們有時(shí)候會(huì)看到這樣的log,系統(tǒng)幫助我們打印出了跳幀數(shù)。
02-07 19:47:04.333 17601-17604/zhangwan.wj.com.choreographertest D/dalvikvm: GC_CONCURRENT freed 143K, 3% free 9105K/9384K, paused 2ms+0ms, total 6ms
02-07 19:47:04.337 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 60 frames! The application may be doing too much work on its main thread.
02-07 19:47:11.685 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 85 frames! The application may be doing too much work on its main thread.
02-07 19:47:12.545 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 37 frames! The application may be doing too much work on its main thread.
02-07 19:47:14.893 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 37 frames! The application may be doing too much work on its main thread.
02-07 19:47:23.049 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 36 frames! The application may be doing too much work on its main thread.
02-07 19:47:23.929 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 37 frames! The application may be doing too much work on its main thread.
02-07 19:47:24.961 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 61 frames! The application may be doing too much work on its main thread.
02-07 19:47:25.817 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 36 frames! The application may be doing too much work on its main thread.
02-07 19:47:26.433 17601-17601/zhangwan.wj.com.choreographertest I/Choreographer: Skipped 36 frames! The application may be doing too much work on its main thread.
這個(gè)log就出自于Choreographer中(英[?k?r?'?ɡr?f?(r)] 美[?k?r?'?ɡr?f?(r)])。
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
}
}
其中SKIPPED_FRAME_WARNING_LIMIT是Choreographer的成員變量。
// Set a limit to warn about skipped frames.
// Skipped frames imply jank.
private static final int SKIPPED_FRAME_WARNING_LIMIT =SystemProperties.getInt( "debug.choreographer.skipwarning", 30);
也就是當(dāng)跳幀數(shù)大于設(shè)置的SKIPPED_FRAME_WARNING_LIMIT 值時(shí)會(huì)在當(dāng)前進(jìn)程輸出這個(gè)log。由于 SKIPPED_FRAME_WARNING_LIMIT 的值默認(rèn)為 30,所以上面的log并不是經(jīng)??吹剑绻覀冇梅瓷涞姆椒ò裇KIPPED_FRAME_WARNING_LIMIT的值設(shè)置成1,這樣可以保證只要有丟幀,就會(huì)有上面的log輸出來(lái)。
static {
try {
Field field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT");
field.setAccessible(true);
field.set(Choreographer.class,1);
} catch (Throwable e) {
e.printStackTrace();
}
}
注意,這個(gè)方案是 API 16 以上才支持。Choreographer就是一個(gè)消息處理器,根據(jù)vsync 信號(hào) 來(lái)計(jì)算frame,而計(jì)算frame的方式就是處理三種回調(diào),包括事件回調(diào)、動(dòng)畫(huà)回調(diào)、繪制回調(diào)。這三種事件在消息輸入、加入動(dòng)畫(huà)、準(zhǔn)備繪圖layout 等動(dòng)作時(shí)均會(huì)發(fā)給Choreographer。一句話,我們只要捕獲這個(gè)log提取出skippedFrames 就可以知道界面是否卡頓。
三、如何檢測(cè)
采用上面的方式就可以在App內(nèi)部觀測(cè)當(dāng)前App的流暢度了。并且在丟幀的地方打印,就可以知道丟幀的大概原因,大概位置,定位代碼問(wèn)題。
在Choreographer中有個(gè)回調(diào)接口,F(xiàn)rameCallback。
public interface FrameCallback {
//當(dāng)新的一幀被繪制的時(shí)候被調(diào)用。
public void doFrame(long frameTimeNanos);
}
根據(jù)上面的代碼,重寫(xiě)doFrame方法,所以照葫蘆畫(huà)瓢,自定義FrameCallback。我們可以在每一幀被渲染的時(shí)候記錄下它開(kāi)始渲染的時(shí)間,這樣在下一幀被處理時(shí),判斷上一幀在渲染過(guò)程中是否出現(xiàn)掉幀。
public class SMFrameCallback implements Choreographer.FrameCallback {
public static SMFrameCallback sInstance;
private String TAG="SMFrameCallback";
public static final float deviceRefreshRateMs=16.6f;
public static long lastFrameTimeNanos=0;//納秒為單位
public static long currentFrameTimeNanos=0;
public void start() {
Choreographer.getInstance().postFrameCallback(SMFrameCallback.getInstance());
}
public static SMFrameCallback getInstance() {
if (sInstance == null) {
sInstance = new SMFrameCallback();
}
return sInstance;
}
@Override
public void doFrame(long frameTimeNanos) {
if(lastFrameTimeNanos==0){
lastFrameTimeNanos=frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
return;
}
currentFrameTimeNanos=frameTimeNanos;
float value=(currentFrameTimeNanos-lastFrameTimeNanos)/1000000.0f;
final int skipFrameCount = skipFrameCount(lastFrameTimeNanos, currentFrameTimeNanos, deviceRefreshRateMs);
Log.e(TAG,"兩次繪制時(shí)間間隔value="+value+" frameTimeNanos="+frameTimeNanos+" currentFrameTimeNanos="+currentFrameTimeNanos+" skipFrameCount="+skipFrameCount+"");
lastFrameTimeNanos=currentFrameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
}
/**
*
*計(jì)算跳過(guò)多少幀
* @param start
* @param end
* @param devicefreshRate
* @return
*/
private int skipFrameCount(long start,long end,float devicefreshRate){
int count =0;
long diffNs=end-start;
long diffMs = Math.round(diffNs / 1000000.0f);
long dev=Math.round(devicefreshRate);
if(diffMs>dev){
long skipCount=diffMs/dev;
count=(int)skipCount;
}
return count;
}
}
在需要檢測(cè)的Activity中調(diào)用 SMFrameCallback.getInstance().start()即可。一般優(yōu)化一下,可以在BaseActivity去調(diào)用或者Activitylifecyclecallbacks中去調(diào)用.
正常情況下輸出的日志是:
02-07 20:18:52.605 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996166386820 currentFrameTimeNanos=6996166386820 skipFrameCount=0
02-07 20:18:52.621 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996183053486 currentFrameTimeNanos=6996183053486 skipFrameCount=0
02-07 20:18:52.637 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996199720152 currentFrameTimeNanos=6996199720152 skipFrameCount=0
02-07 20:18:52.657 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996216386818 currentFrameTimeNanos=6996216386818 skipFrameCount=0
02-07 20:18:52.673 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996233053484 currentFrameTimeNanos=6996233053484 skipFrameCount=0
02-07 20:18:52.689 6683-6683/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=6996249720150 currentFrameTimeNanos=6996249720150 skipFrameCount=0
有跳幀的時(shí)候輸出的日志是
02-07 20:21:53.909 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=7177466379568 currentFrameTimeNanos=7177466379568 skipFrameCount=0
02-07 20:21:53.925 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=7177483046234 currentFrameTimeNanos=7177483046234 skipFrameCount=0
02-07 20:21:54.133 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=200.0 frameTimeNanos=7177683046226 currentFrameTimeNanos=7177683046226 skipFrameCount=11
02-07 20:21:54.745 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=616.6666 frameTimeNanos=7178299712868 currentFrameTimeNanos=7178299712868 skipFrameCount=36
02-07 20:21:54.757 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=7178316379534 currentFrameTimeNanos=7178316379534 skipFrameCount=0
02-07 20:21:54.773 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=7178333046200 currentFrameTimeNanos=7178333046200 skipFrameCount=0
02-07 20:21:54.789 9530-9530/zhangwan.wj.com.choreographertest E/SMFrameCallback: 兩次繪制時(shí)間間隔value=16.666666 frameTimeNanos=7178349712866 currentFrameTimeNanos=7178349712866 skipFrameCount=0
看到兩次繪制的時(shí)間間隔相差616.6666毫秒,跳過(guò)了36幀,這個(gè)卡頓用戶是能夠明顯感知的
參考鏈接:https://www.jianshu.com/p/d126640eccb1
友情推薦:
至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺(jué)得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請(qǐng)聯(lián)系小編刪除,歡迎您的建議與指正。同時(shí)期待您的關(guān)注,感謝您的閱讀,謝謝!
點(diǎn)個(gè)在看,方便您使用時(shí)快速查找!
