美文网首页
滴滴哆啦A梦开源组件调研

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

作者: DoneWillianm | 来源:发表于2019-12-13 19:17 被阅读0次

    滴滴哆啦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

    大图检测

    相关文章

      网友评论

          本文标题:滴滴哆啦A梦开源组件调研

          本文链接:https://www.haomeiwen.com/subject/sqbfnctx.html