dumpsys实现原理以及gfxinfo实例讲解

本文介绍了Android中dumpsys工具的工作原理及其使用方法,特别聚焦于gfxinfo命令的实现细节,包括权限检查、服务注册和底层数据抓取过程。

转载请标明出处:http://www.jianshu.com/users/183339cdc7ae/latest_articles

概述

dumpsys是一个android手机里面的可执行文件。
从名字就可以看出,主要是用于dump 当前android system的一些信息。比如

activity(当前系统中所有activity的堆栈关系) 
alarm(当前系统中所有的Alarms)

等等,是一项分析手机问题,运行状态,使用情况等十分有效的手段。
查看所支持的dump选项

adb shell dumpsys -l

会列出所有可以dump的选项,比如想获取所有window相关的信息,可以使用命令

adb shell dumpsys window

实现逻辑

既然是一个可执行文件,必然是先找到其mk文件:/frameworks/native/cmds/dumpsys/Android.mk

...
LOCAL_SRC_FILES:= \
    dumpsys.cpp
...
LOCAL_MODULE:= dumpsys
include $(BUILD_EXECUTABLE)

dumpsys的源码结构其实很简单,只有一个dumpsys.cpp
/frameworks/native/cmds/dumpsys/dumpsys.cpp

int main(int argc, char* const argv[])
{
    ...
    sp<IServiceManager> sm = defaultServiceManager();
    ...
    Vector<String16> services;
    ...
    services = sm->listServices();
    ...
    const size_t N = services.size();

    for (size_t i=0; i<N; i++) {
        sp<IBinder> service = sm->checkService(services[i]);
        ...
        int err = service->dump(STDOUT_FILENO, args);
        ...
    }

    return 0;
}

先通过defaultServiceManager()函数获得ServiceManager对象,然后根据dumpsys传进来的参数通过函数checkService来找到具体的service, 并执行该service的dump方法,达到dump service的目的。

gfxinfo实例讲解

因为笔者最近在研究手机跑2D/3D场景的性能评测,所以这里以dumpsys gfxinfo为例,说下它的大致流程。

具体服务

由上文可以知道,dumpsys的实现其实是根据参数来找到某个具体的service,然后执行其dump方法。
我们熟悉的系统service有ActivityManagerSerice(activity),WindowManagerService(window)等,那gfxinfo具体对应的是哪个service呢?
在文件:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java中有下面这段代码

 public void setSystemProcess() {
      ...
      ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
       ...
}

在ams里面,通过ServiceManager添加了一个name为gfxinfo的serivce叫GraphicsBinder,该service就是gfxinfo对应的service.

    static class GraphicsBinder extends Binder {
        ...
        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP)
                    != PackageManager.PERMISSION_GRANTED) {
                pw.println("Permission Denial: can't dump gfxinfo from from pid="
                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
                        + " without permission " + android.Manifest.permission.DUMP);
                return;
            }

            mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
        }
    }

这里有一个权限检查,如果app想要dump系统信息必须要配置 android.Manifest.permission.DUMP权限,而该权限是三方app没办法使用的。

那作为一个三方app,我们有办法绕过该权限检查吗?这里按下不表,接着往后看。

好吧,其实可以绕过。 详情

ActivityThread

当我们执行adb shell dumpsys gfxinfo的时候,其实最后执行的是ams中的dumpGraphicsHardwareUsage该方法。

    final void dumpGraphicsHardwareUsage(FileDescriptor fd,
            PrintWriter pw, String[] args) {
        ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args);
        ...
       for (int i = procs.size() - 1 ; i >= 0 ; i--) {
            ProcessRecord r = procs.get(i);
            if (r.thread != null) {
                ...
                r.thread.dumpGfxInfo(tp.getWriteFd().getFileDescriptor(), args);
                ...
            }
        }
    }

可以看出,最后真正做dump动作的,其实是r.thread这个对象,这里直接给出r.thread其实就是ActivityThread中的内部类:ApplicationThread,省略代码的追查,让主线更加清晰。

collectProcesses函数的主要作用就是:根据参数找到对应的进程信息

该参数就是命令行中gfxinfo后面传进来的参数。
如果gfxinfo后面没有跟参数,则表示获取所有的Process信息
如果gfxinfo后面跟 包名, 则只获取指定报名的Process信息

文件:/frameworks/base/core/java/android/app/ActivityThread.java

public final class ActivityThread {
  ...
  private class ApplicationThread extends ApplicationThreadNative {
        ...
        @Override
        public void dumpGfxInfo(FileDescriptor fd, String[] args) {
            dumpGraphicsInfo(fd);
            WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
        }
        ...
  }
}
Native-dumpGraphicsInfo实现

dumpGraphicsInfo是一个native函数实现在
/frameworks/base/core/jni/android_view_GLES20Canvas.cpp

static void
android_app_ActivityThread_dumpGraphics(JNIEnv* env, jobject clazz, jobject javaFileDescriptor) {
#ifdef USE_OPENGL_RENDERER
    int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
    android::uirenderer::RenderNode::outputLogBuffer(fd);
#endif // USE_OPENGL_RENDERER
}

/frameworks/base/libs/hwui/RenderNode.cpp

void RenderNode::outputLogBuffer(int fd) {
    DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
    ...
    FILE *file = fdopen(fd, "a");

    fprintf(file, "\nRecent DisplayList operations\n");
    logBuffer.outputCommands(file);

    String8 cachesLog;
    Caches::getInstance().dumpMemoryUsage(cachesLog);
    fprintf(file, "\nCaches:\n%s", cachesLog.string());
    ...
}

可以看出是打印了一些RenderNode的信息,包括memory和cache。

Native-ThreadedRenderer实现

/frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void dumpGfxInfo(FileDescriptor fd) {
    ...
    HardwareRenderer renderer =
                  root.getView().mAttachInfo.mHardwareRenderer;
    if (renderer != null) {
           renderer.dumpGfxInfo(pw, fd);
     }
    ...
}

HardwareRenderer是一个抽象类,具体的实现是在
/frameworks/base/core/java/android/view/ThreadedRenderer.java

    @Override
    void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
        pw.flush();
        nDumpProfileInfo(mNativeProxy, fd);
    }

nDumpProfileInfo也是一个native函数,也是去抓取一些graphic信息,不再做详细讲解。
到这里,dumpsys gfxinfo的流程就基本讲解完了。

总结

dumpsys的实现其实是通过serviceManager拿到对应的service信息,然后执行该service的dump函数。
需要注意的是:每个service都有一个权限检查,需要系统app才可以dump。

### Android dumpsys gfxinfo 计算帧率 方法 在 Android 设备上,使用 `dumpsys gfxinfo` 命令可以获取与 UI 渲染性能相关的信息,其中包括帧率(FPS)和丢帧率的计算。这种方法是基于系统记录每一帧在渲染过程中不同阶段所花费的时间,并根据这些数据进行分析。 #### 1. 开启 GPU 呈现模式分析 首先,需要在设备的 **开发者选项** 中开启“GPU 呈现模式分析”功能,并选择“在 `adb shell dumpsys gfxinfo` 中”作为输出方式。这样,系统会记录每个应用界面最后 **128 帧** 的渲染时间信息 [^3]。 对于华为或荣耀设备,则应选择“在屏幕上显示为线型图”,因为它们可能对 `gfxinfo` 的支持方式有所不同 [^1]。 #### 2. 获取 gfxinfo 数据 执行以下 ADB 命令来获取指定应用的 gfxinfo 数据: ```bash adb shell dumpsys gfxinfo <package_name> ``` 其中 `<package_name>` 是你要测试的应用包名。命令执行后,会输出类似如下的数据: ``` Profile data in ms: 0.000: 16.670: 33.330: 50.000: 66.670: 83.330: 100.000: 116.670: 133.330: 150.000 (ms) Draw Prepare Process Execute 5.23 1.12 7.65 2.34 6.12 1.01 8.01 2.45 ... ``` 每一行代表一帧的渲染时间,分为 **Draw、Prepare、Process、Execute** 四个阶段 [^1]。 #### 3. 计算帧率(FPS) FPS(Frames Per Second)的计算公式为: $$ \text{FPS} = \frac{\text{总帧数}}{\text{总时间(秒)}} $$ - **总帧数**:从 gfxinfo 输出中统计实际渲染的帧数(最多 128 帧)。 - **总时间**:从第一帧开始到最后一个帧结束所花费的时间(单位为毫秒),需转换为秒。 例如,如果有 120 帧,总时间为 2000ms(2秒),则: $$ \text{FPS} = \frac{120}{2} = 60 $$ #### 4. 判断是否丢帧 每一帧的 **Draw + Prepare + Process + Execute** 时间总和如果超过 **16.67ms**,则认为该帧为 **丢帧**。虽然 Android 6.0 以前的标准是 16.67ms,但现代设备由于三层缓冲机制和硬件进步,即使超过该阈值也不一定会卡顿 [^5]。 因此,丢帧率计算公式为: $$ \text{丢帧率} = \frac{\text{丢帧数}}{\text{总帧数}} \times 100\% $$ #### 5. 使用脚本自动化分析 由于手动计算效率低且容易出错,推荐使用脚本自动解析 `gfxinfo` 数据并输出 FPS 和丢帧率。Python 是一种常用的选择,脚本可以: - 执行 ADB 命令获取数据 - 解析每一帧的四个阶段时间 - 统计总帧数、丢帧数、总耗时 - 输出 FPS 和丢帧率 以下是一个 Python 示例脚本片段: ```python import subprocess def get_gfxinfo(package_name): cmd = f"adb shell dumpsys gfxinfo {package_name}" result = subprocess.run(cmd.split(), stdout=subprocess.PIPE) return result.stdout.decode('utf-8') def parse_gfx_data(data): lines = data.splitlines() frames = [] for line in lines: if line.strip() and not line.startswith("Flags"): try: parts = list(map(float, line.strip().split())) if len(parts) == 4: total_time = sum(parts) frames.append(total_time) except: continue return frames def calculate_fps(frames, total_time_ms): total_time_sec = total_time_ms / 1000 return len(frames) / total_time_sec if total_time_sec > 0 else 0 def calculate_jank(frames): jank_count = sum(1 for frame in frames if frame > 16.67) return jank_count, len(frames) # 示例使用 package_name = "com.example.app" data = get_gfxinfo(package_name) frames = parse_gfx_data(data) fps = calculate_fps(frames, 2000) # 假设测试时间为 2000ms jank_count, total_frames = calculate_jank(frames) print(f"FPS: {fps:.2f}") print(f"丢帧数: {jank_count}/{total_frames}") ``` #### 6. 注意事项 - **数据限制**:`gfxinfo` 仅保留最近 128 帧的数据,因此测试时应确保操作足够密集。 - **Unity 项目限制**:部分 Unity 项目可能无法通过 `gfxinfo` 获取到准确的帧率信息,因为其渲染机制与原生 Android 不同 [^4]。 - **设备差异**:不同品牌或 Android 版本的设备可能在 `gfxinfo` 输出格式或支持程度上有所不同,需注意兼容性。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值