Android性能指標(biāo)大致分為以下幾點(diǎn)
FPS、CPU使用率、內(nèi)存占用、頁(yè)面加載時(shí)間、網(wǎng)絡(luò)請(qǐng)求
內(nèi)存使用
在加載圖片、視頻、聲音等這些比較耗費(fèi)內(nèi)存的資源的時(shí)候去獲取一次應(yīng)用內(nèi)存占用及系統(tǒng)剩余內(nèi)存保存到日志中。
Android提供了API可以獲取系統(tǒng)總內(nèi)存大小及當(dāng)前可用內(nèi)存大小,以及獲取應(yīng)用中內(nèi)存的使用情況。
Runtime runtime = Runtime.getRuntime();
long totalSize = runtime.maxMemory() >> 10;
this.memoryUsage = (runtime.totalMemory() - runtime.freeMemory()) >> 10;
this.memoryUsageRate = this.memoryUsage * 100 / totalSize;
CPU使用
可以每隔一段時(shí)間去獲取APP進(jìn)程CPU時(shí)間,也可以針對(duì)一些場(chǎng)景手動(dòng)獲取。可以設(shè)定一個(gè)閾值,比如大于50%的時(shí)候,將各個(gè)線程名及線程占用的CPU時(shí)間一起保存到日志中。
系統(tǒng)沒(méi)有提供API接口,可以直接在應(yīng)用中讀取/proc/stat,/proc/pid/stat,/proc/pid/task/tid/stat這幾個(gè)文件來(lái)獲取,不需要root權(quán)限
int mPid;
long mLastCpuTime;//當(dāng)前手機(jī)的CPU總時(shí)間
long mLastAppTime;//當(dāng)前app的CPU耗時(shí)
RandomAccessFile procStatFile;
static final int RATE_LIMIT = 50;//CPU占比閾值
if (mPid == 0) {
mPid = android.os.Process.myPid();
}
try {
if (procStatFile == null || appStatFile == null) {
procStatFile = new RandomAccessFile("/proc/stat", "r");
appStatFile = new RandomAccessFile("/proc/" + mPid + "/stat", "r");
}
//文件開(kāi)頭
procStatFile.seek(0);
appStatFile.seek(0);
String proStatSrc = procStatFile.readLine();
String appStatSrc = appStatFile.readLine();
String[] proStats = null;
String[] appStats = null;
if (proStatSrc != null || appStatSrc != null) {
proStats = proStatSrc.trim().split(" ");
appStats = appStatSrc.trim().split(" ");
}
long cpuTime = 0L;
long appTime = 0L;
if (proStats != null && proStats.length >= 9) {
for (int i = 2; i <= 8; i++) {
cpuTime += Long.valueOf(proStats[i]);
}
}
if (appStats != null && appStats.length >= 15) {
appTime = Long.valueOf(appStats[13]) + Long.valueOf(appStats[14]);
}
if (mLastCpuTime == 0 || mLastAppTime == 0) {
mLastCpuTime = cpuTime;
mLastAppTime = appTime;
return;
}
double cpuRate = (double) (appTime - mLastAppTime) / (double) (cpuTime - mLastCpuTime);
mLastCpuTime = cpuTime;
mLastAppTime = appTime;
//CPU使用率
int cpuRateInt = (int) (cpuRate * 100);
//TODO
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.d(TAG, "init randomfile failed");
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "read file failed");
}
}
FPS(卡頓)
Android系統(tǒng)從4.1(API 16)開(kāi)始加入Choreographer這個(gè)類來(lái)控制同步處理輸入(Input)、動(dòng)畫(huà)(Animation)、繪制(Draw)三個(gè)操作。其實(shí)UI顯示的時(shí)候每一幀要完成的事情只有這三種。Choreographer接收顯示系統(tǒng)的時(shí)間脈沖(垂直同步信號(hào)-VSync信號(hào)),在下一個(gè)幀渲染時(shí)控制執(zhí)行這些操作。收到VSync信號(hào)后,順序執(zhí)行3個(gè)操作,然后等待下一個(gè)信號(hào),再次順序執(zhí)行3個(gè)操作。假設(shè)在第二個(gè)信號(hào)到來(lái)之前,所有的操作都執(zhí)行完成了,即Draw操作完成了,那么第二個(gè)信號(hào)來(lái)到時(shí),此時(shí)界面將會(huì)更新為第一幀的內(nèi)容,因?yàn)镈raw操作已經(jīng)完成了。否則界面將不會(huì)更新,還是現(xiàn)實(shí)上一個(gè)幀的內(nèi)容,表示丟幀了。丟幀是造成卡頓的原因。
通過(guò) Choreographer 類設(shè)置它的 FrameCallback,可以在每一幀被渲染的時(shí)候記錄下它開(kāi)始渲染的時(shí)間,每一次同步的周期為16ms,代表一幀的刷新頻率,一次界面渲染會(huì)回調(diào) FrameCallback的doFrame(longframeTimeNanos)方法,如果兩次 doFrame 之間的間隔大于16ms說(shuō)明丟幀了。用這種方法,可以實(shí)時(shí)監(jiān)控應(yīng)用的丟幀情況。
static final int FPS_LIMIT = 30;//最低幀率,低于這個(gè)幀代表頁(yè)面不流暢
static final int INTERVAL = 300;//Frame采樣時(shí)間
@Override
public void doFrame(long frameTimeNanos) {
long currentTimeMillis = TimeUnit.NANOSECONDS.toMillis(frameTimeNanos);
if (mLastFrameStartTime > 0) {
//兩幀的時(shí)間間隔
final long timeSpace = currentTimeMillis - mLastFrameStartTime;
mFrameRendered++;
if (timeSpace >= INTERVAL) {
//計(jì)算幀率
mFPS = (int) (mFrameRendered * 1000 / timeSpace);
mLastFrameStartTime = currentTimeMillis;
mFrameRendered = 0;
}
} else {
mLastFrameStartTime = currentTimeMillis;
}
mChoreographer.postFrameCallback(this);
}
使用:
if (mFPS < FPS_LIMIT) {
...
} else {
...
}
頁(yè)面加載時(shí)間
如何判定UI渲染完成?
在Activity的rootView中插入一個(gè)FrameLayout,并且監(jiān)聽(tīng)這個(gè)FrameLayout是否調(diào)用了dispatchDraw方法實(shí)現(xiàn)的
class AutoSpeedFrameLayout extends FrameLayout {
public static View wrap(int pageObjectKey, @NonNull View child) {
...
//將頁(yè)面根View作為子View,其他參數(shù)保持不變
ViewGroup vg = new AutoSpeedFrameLayout(child.getContext(), pageObjectKey);
if (child.getLayoutParams() != null) {
vg.setLayoutParams(child.getLayoutParams());
}
vg.addView(child, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return vg;
}
private final int pageObjectKey;//關(guān)聯(lián)的頁(yè)面key
private AutoSpeedFrameLayout(@NonNull Context context, int pageObjectKey) {
super(context);
this.pageObjectKey = pageObjectKey;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
AutoSpeed.getInstance().onPageDrawEnd(pageObjectKey);
}
}
自定義了一層 FrameLayout 作為所有頁(yè)面根View的父View,其 dispatchDraw() 方法執(zhí)行super后,記錄相關(guān)頁(yè)面繪制結(jié)束的時(shí)間點(diǎn)。
網(wǎng)絡(luò)請(qǐng)求
一,流量
具體方案:后臺(tái)定時(shí)任務(wù)->獲取間隔內(nèi)流量->記錄前后臺(tái)->分別計(jì)算->上報(bào) 后臺(tái)->流量治理依據(jù)
實(shí)現(xiàn)方法:
1.TrafficStats
2.可使用okhttp的interceptor中監(jiān)控每個(gè)api的流量,但無(wú)法獲知其他如webview流量
3.stetho
二,成功率
返回狀態(tài)碼
三,速度
計(jì)算每次request和response之間時(shí)間間隔