美文网首页
滴滴哆啦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梦开源组件调研

    滴滴哆啦A梦(DoreamonKit框架)调研 官方Android开发文档开源组件DoraemonKit之Andr...

  • 哆啦A梦睡着了

    熟睡的哆啦A梦 半醒的哆啦A梦 发胖的哆啦A梦 干瘪的哆啦A梦 健忘的哆啦A梦 偷懒的我 你的铃铛吊在化粪池 肿胀...

  • 致陪伴与爱

    哆啦A梦陪伴了大雄八十年,大雄临终前对哆啦A梦说“回到你的地方吧。”哆啦A梦答应了。后来,大雄走完一生,哆啦A梦对...

  • 哆啦A梦丢了时光机

    哆啦A梦是大雄最好的朋友。哆啦A梦可以给大雄能去任何地方的任意门,哆啦A梦可以给大雄能飞起来的竹蜻蜓,哆啦A梦...

  • 你相信平行世界吗?

    我喜欢看哆啦A梦。小时候喜欢五点半动画城里的哆啦A梦,长大些就喜欢看电影版的哆啦A梦。 在哆啦A梦的思想中,在和我...

  • 别走,蓝胖子!

    哆啦A梦和大熊的友情,我承认我酸了 哆啦A梦和大熊在一起总是会吵架,但是大雄对哆啦A梦说,我最喜欢哆啦A梦,要一直...

  • 以后用微博了

    微博:哆呀哆啦A梦

  • 哆啦A梦,伴你同行

    看到哆啦A梦里面有一集,是大雄通过时光胶囊给100年后的哆啦A梦诞生时写的话:哆啦A梦,生日快乐。 哆啦A梦说,那...

  • 哆啦A梦的口袋

    每个人 都想拥有哆啦A梦 那我 只想要哆啦A梦的口袋

  • 请陪我天长地久

    对于一个从小就看哆啦A梦,家里有机器猫 小叮当 哆啦A梦三个版本的书的忠实哆啦迷。这次的哆啦A梦电影怎么可以错过?...

网友评论

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

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