滴滴哆啦A梦开源组件调研
滴滴哆啦A梦(DoreamonKit框架)调研
官方Android开发文档
开源组件DoraemonKit之Android版本技术实现(一)
开源组件DoraemonKit之Android版本技术实现(二)
统计Activity跳转耗时
帧率检测
利用Android系统的Choreographer.FrameCallback接口每一帧渲染的回调进行计数,而时间则由handler来控制,这样就能得出每一秒渲染的帧数。
可参考文献
Systrace
缺点是handler作为计时器并不准。这里使用子线程looper来调用,稍微能准一点点
////com.didichuxing.doraemonkit.kit.common.PerformanceDataManager Line205
/**
* Implement this interface to receive a callback when a new display frame is
* being rendered. The callback is invoked on the {@link Looper} thread to
* which the {@link Choreographer} is attached.
*/
public interface FrameCallback {
/**
* Called when a new display frame is being rendered.
* <p>
* This method provides the time in nanoseconds when the frame started being rendered.
* The frame time provides a stable time base for synchronizing animations
* and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
* or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
* time helps to reduce inter-frame jitter because the frame time is fixed at the time
* the frame was scheduled to start, regardless of when the animations or drawing
* callback actually runs. All callbacks that run as part of rendering a frame will
* observe the same frame time so using the frame time also helps to synchronize effects
* that are performed by different callbacks.
* </p><p>
* Please note that the framework already takes care to process animations and
* drawing using the frame time as a stable time base. Most applications should
* not need to use the frame time information directly.
* </p>
*
* @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
* to convert it to the {@link SystemClock#uptimeMillis()} time base.
*/
public void doFrame(long frameTimeNanos);
}
...
@Override
public void run() {
mLastFrameCount = frameCount;
if (mLastFrameCount > MAX_FRAME) {
mLastFrameCount = MAX_FRAME;
}
frameCount = 0;
postHandleDelay(this, DELAY_TIME);
LogHelper.d("帧率:" + mLastFrameCount);
if (mCallback != null) {
mCallback.onRender(mLastFrameCount);
}
}
CPU使用率
Android 8.0以后CPU使用率的方案研究
Android性能测试
滴滴的cpu监控主要是两种,但是核心都是监控/proc/stat和/proc/pid/stat文件,Android O 上的TOP命令源码也是读取的该文件
//com.didichuxing.doraemonkit.kit.common.PerformanceDataManager Line78
private float getCPUData() {
long cpuTime;
long appTime;
float value = 0.0f;
try {
if (mProcStatFile == null || mAppStatFile == null) {
mProcStatFile = new RandomAccessFile("/proc/stat", "r");
mAppStatFile = new RandomAccessFile("/proc/" + android.os.Process.myPid() + "/stat", "r");
} else {
mProcStatFile.seek(0L);
mAppStatFile.seek(0L);
}
String procStatString = mProcStatFile.readLine();
String appStatString = mAppStatFile.readLine();
String procStats[] = procStatString.split(" ");
String appStats[] = appStatString.split(" ");
cpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3])
+ Long.parseLong(procStats[4]) + Long.parseLong(procStats[5])
+ Long.parseLong(procStats[6]) + Long.parseLong(procStats[7])
+ Long.parseLong(procStats[8]);
appTime = Long.parseLong(appStats[13]) + Long.parseLong(appStats[14]);
if (mLastCpuTime == null && mLastAppCpuTime == null) {
mLastCpuTime = cpuTime;
mLastAppCpuTime = appTime;
return value;
}
value = ((float) (appTime - mLastAppCpuTime) / (float) (cpuTime - mLastCpuTime)) * 100f;
mLastCpuTime = cpuTime;
mLastAppCpuTime = appTime;
} catch (Exception e) {
LogHelper.e(TAG,"getCPUData fail: "+e.toString());
}
return value;
}
RAM检测
利用activityManager获取当前进程的内存使用情况
/**
public Debug.MemoryInfo getProcessMemoryInfo(int[ ] pids
说明:获取每个进程ID(集合)占用的内存大小(集合), pid和MemoryInfo是一一对应的。
参数: pids 进程ID的集合
PS :我们可以通过调用Debug.MemoryInfo 的dalvikPrivateDirty字段获取进程占用的内存大小(单位为KB)
*/
private float getMemoryData() {
float mem = 0.0F;
try {
// 统计进程的内存信息 totalPss
final Debug.MemoryInfo[] memInfo = mActivityManager.getProcessMemoryInfo(new int[]{Process.myPid()});
if (memInfo.length > 0) {
// TotalPss = dalvikPss + nativePss + otherPss, in KB
final int totalPss = memInfo[0].getTotalPss();
if (totalPss >= 0) {
// Mem in MB
mem = totalPss / 1024.0F;
}
}
} catch (Exception e) {
LogHelper.e(TAG,"getMemoryData fail: "+e.toString());
}
return mem;
}
卡顿检测
利用Android主线程消息机制,主线程Choreographer(Android 4.1及以后)每16ms请求一个vsync信号,当信号到来时触发doFrame操作,它内部又依次进行了input、Animation、Traversal过程,参考Looper.loop()源码
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
可以看到主线程分发消息,交由对应的handler进行处理,监控主线程的卡顿,其实监控dispatchMessage方法的耗时情况即可,而在达到卡顿阈值的情况下,记录下此刻主线程的调用堆栈
@Override
public void println(String x) {
if (!mPrintingStarted) {
mStartTime = System.currentTimeMillis();
mStartThreadTime = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
mStackSampler.startDump();
} else {
final long endTime = System.currentTimeMillis();
long endThreadTime = SystemClock.currentThreadTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
final ArrayList<String> entries = mStackSampler.getThreadStackEntries(mStartTime, endTime);
if (entries.size() > 0) {
final BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(mStartTime, endTime, mStartThreadTime, endThreadTime)
.setThreadStackEntries(entries)
.flushString();
BlockMonitorManager.getInstance().notifyBlockEvent(blockInfo);
}
}
mStackSampler.stopDump();
}
}
参考文章:
Android Choreographer 源码分析
Android卡顿检测(一)BlockCanary
耗时
UI渲染性能
这里哆啦A梦中主要用到的计算是直接调用view.draw(canvas)中进行计时。说白了就是取decorView,然后遍历decorView中的所有子view的绘制耗时
private void traverseViews(View view, List<ViewInfo> infos, int layerNum) {
if (view == null) {
return;
}
layerNum++;
if (view instanceof ViewGroup) {
int childCount = ((ViewGroup) view).getChildCount();
if (childCount != 0) {
for (int index = childCount - 1; index >= 0; index--) {
traverseViews(((ViewGroup) view).getChildAt(index), infos, layerNum);
}
}
} else {
long startTime = System.nanoTime();
view.draw(mPerformanceCanvas);
long endTime = System.nanoTime();
float time = (endTime - startTime) / 10_000 / 100f;
LogHelper.d(TAG, "drawTime: " + time + " ms");
ViewInfo viewInfo = new ViewInfo(view);
viewInfo.drawTime = time;
viewInfo.layerNum = layerNum;
infos.add(viewInfo);
}
}
Activity跳转耗时
函数耗时
利用SDK提供的内置方法进行函数耗时统计
Debug.startMethodTracing
Debug.stopMethodTracing();
可参考文章
Android 性能优化:使用 TraceView 找到卡顿的元凶