滴滴哆啦A梦开源组件调研

2019-12-13  本文已影响0人  DoneWillianm

滴滴哆啦A梦(DoreamonKit框架)调研

官方Android开发文档
开源组件DoraemonKit之Android版本技术实现(一)

开源组件DoraemonKit之Android版本技术实现(二)
统计Activity跳转耗时

DoKit支持Activity启动耗时统计方案

哆啦A梦功能集锦.jpg 布局边框.jpg

帧率检测

利用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 找到卡顿的元凶

官方guide

大图检测

上一篇下一篇

猜你喜欢

热点阅读