Android渲染优化之卡顿检测、统计fps

本文介绍了如何通过自定义`printer`和`Choreographer.FrameCallback`接口来检测和统计Android应用的卡顿与FPS。通过分析`adb shell dumpsys gfxinfo`和`looper`,实现了一种方法来记录并计算两帧之间的绘制时间间隔,当间隔超过特定阈值时,视为卡顿并进行统计。此外,还提供了`FrameSkipMonitor`的源码链接,用于实际应用中的性能监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习自

https://juejin.im/post/5a6fd7b86fb9a01ca47ac6e8


adb shell dumpsys gfxinfo

这个是一个方法,但是用的不多


从looper入手

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
    logging.println(">>>>> Dispatching to " + msg.target + " " +
            msg.callback + ": " + msg.what);
}

final long traceTag = me.mTraceTag;
if (traceTag != 0) {
    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
    msg.target.dispatchMessage(msg);
} finally {
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

if (logging != null) {
    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
可以看到msg的执行前后都有log,所以我们只要拿到前后log的时间差就可以拿到我们msg执行的时间了(视图绘制是通过一个 msg的发送来再handler中得到执行的。


所以只要自定义printer即可

1.setMessageLogging方法来设置我们自定义的printer

2.自定义printer:

重写print方法,如果是dispatch方法前后的那个print,就输出时间戳


  • 自定义printer
package com.suning.mobile.ebuy;

import android.os.Looper;
import android.util.Printer;


public class CustomPrinterForGetBlockInfo {
    public static void start() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            //日志输出有很多种格式,我们这里只捕获ui线程中dispatch上下文的日志信息
            //所以这里定义了2个key值,注意不同的手机这2个key值可能不一样,有需要的话这里要做机型适配,
            //否则部分手机这里可能抓取不到日志信息
            private static final String START = ">>>>> Dispatching";
            private static final String END = "<<<<< Finished";
            @Override
            public void println(String x) {
                //这里的思路就是如果发现在打印dispatch方法的 start信息,
                //那么我们就在 “时间戳” 之后 post一个runnable
                if (x.startsWith(START)) {
                    LogMonitor.getInstance().startMonitor();
                }
                //因为我们start 不是立即start runnable 而是在“时间戳” 之后 那么如果在这个时间戳之内
                //dispacth方法执行完毕以后的END到来,那么就会remove掉这个runnable
                //所以 这里就知道 如果dispatch方法执行时间在时间戳之内 那么我们就认为这个ui没卡顿,不输出任何卡顿信息
                //否则就输出卡顿信息 这里卡顿信息主要用StackTraceElement 来输出
                if (x.startsWith(END)) {
                    LogMonitor.getInstance().removeMonitor();
                }
            }
        });
    }
}

  • 看看我们的LogMoniter
package com.suning.mobile.ebuy;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;


public class LogMonitor {
    private static LogMonitor sInstance = new LogMonitor();
    //HandlerThread 这个其实就是一个thread,只不过相对于普通的thread 他对外暴露了一个looper而已。方便
    //我们和handler配合使用
    private HandlerThread mLogThread = new HandlerThread("BLOCKINFO");
    private Handler mIoHandler;
    //这个时间戳的值,通常设置成不超过1000,你可以调低这个数值来优化你的代码。数值越低 暴露的信息就越多
    private static final long TIME_BLOCK = 1000L;

    private LogMonitor() {
        mLogThread.start();
        mIoHandler = new Handler(mLogThread.getLooper());
    }

    private static Runnable mLogRunnable = new Runnable() {
        @Override
        public void run() {
            StringBuilder sb = new StringBuilder();
            //把ui线程的block的堆栈信息都打印出来 方便我们定位问题
            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement s : stackTrace) {
                sb.append(s.toString() + "\n");
            }
            Log.e("BLOCK", sb.toString());
        }
    };

    public static LogMonitor getInstance() {
        return sInstance;
    }

    public void startMonitor() {
        //在time之后 再启动这个runnable 如果在这个time之前调用了removeMonitor 方法,那这个runnable肯定就无法执行了
        mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);
    }

    public void removeMonitor() {
        mIoHandler.removeCallbacks(mLogRunnable);
    }
}
最后在application中启动


统计fps

学习自

https://blog.youkuaiyun.com/mengfeicheng2012/article/details/53768954


在Choreographer中有个回调接口,FrameCallback。

public interface FrameCallback 

public void doFrame(long frameTimeNanos); 
}
 
doFrame 的注释如下:* Called when a new display frame is being rendered. 
就是说,当新的一帧被绘制的时候被调用。 
因此我们利用这个特性,可以统计两帧绘制的时间间隔。 
主要流程如下: 
* 1、实现Choreographer.FrameCallback接口,比如实现类是FrameSkipMonitor 
* 2、在doFrame中统计两帧绘制的时间,代码与注释如下: 
@Override 
public void doFrame(long frameTimeNanos) { 
if (mLastFrameNanoTime != 0) {//mLastFrameNanoTime 上一次绘制的时间 
long frameInterval = frameTimeNanos - mLastFrameNanoTime;//计算两帧的时间间隔 
//如果时间间隔大于最小时间间隔,3*16ms,小于最大的时间间隔,60*16ms,就认为是掉帧,累加统计该时间 
//此处解释一下原因: 因为正常情况下,两帧的间隔都是在16ms以内 ,如果我们统计到的两帧间隔时间大于三倍的普通绘制时间,我们就认为是出现了卡顿,之所以设置最大时间间隔,是为了有时候页面不刷新绘制的时候,不做统计处理 
if (frameInterval > MIN_FRAME_TIME && frameInterval < MAX_FRAME_TIME) { 
long time = 0; 
if (mSkipRecordMap.containsKey(mActivityName)) { 
time = mSkipRecordMap.get(mActivityName); 

mSkipRecordMap.put(mActivityName, time + frameInterval);//统计时间 


mLastFrameNanoTime = frameTimeNanos; 
Choreographer.getInstance().postFrameCallback(this); 
Runtime.getRuntime().maxMemory(); 
}

  • 3、启动监测 
    public void start() { 
    Choreographer.getInstance().postFrameCallback(FrameSkipMonitor.getInstance()); 
    }

  • 4、上报处理 
    在doFrame选中,我们已经统计到了想要的数据,然后我们将time的值除以16600000(因为我们统计的时间是纳秒),就是掉帧的数字。

demo源码地址:https://github.com/FanGuangcheng/FrameSkipMonitor 
源码简要解读: 
最重要的两个类 
FrameSkipMonitor:统计绘制间隔的时间处理操作。 

MyActivityLifeCycle:activity相关的跳转纪录操作,通过纪录,就可以统计每个页面的掉帧卡顿时间。


注意点,hasCallBack方法为hide方法,只能通过反射拿到

public boolean isMonitor() {
        //网上流传的方法多数是这个,但是这个是错的,因为hasCallbacks 是一个hide函数 你压根调用不了的,只能反射调用
        //return mIoHandler.hasCallbacks(mLogRunnable);
        try {
            //通过详细地类名获取到指定的类
            Class<?> handlerClass = Class.forName("android.os.Handler");
            //通过方法名,传入参数获取指定方法
            java.lang.reflect.Method method = handlerClass.getMethod("hasCallbacks", Runnable.class);
            Boolean ret = (Boolean) method.invoke(mIoHandler, mLogRunnable);
            return ret;
        } catch (Exception e) {
        }
        return false;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值