adb shell 中的 dumpsys 命令调用过程 探究 (16/04/05)

《代码里的世界》原理篇

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao
http://www.jb51.cc/article/p-gtxxakbe-bps.html

  之前在研究png 和svg在绘制过程中的内存占用和绘制效率对比问题的时候,使用了比较便捷的adb shell 命令 adb shell dumpsys gfxinfo来查看具体数据。由于使用需要,就稍微跟进了一下代码,这里记录下命令行调用输出的详细过程。
  

1.场景举例

  以com.xxx.demo举例,在terminal终端调用 adb shell dumpsys gfxinfo com.xxx.demo,得到的信息大概有两部分:
- Recent DisplayList operations
- Caches
详细日志如下:

•   >adb shell dumpsys com.xxx.demo
•   Applications Graphics Acceleration Info:
•   Uptime: 24363222 Realtime: 26646172
•   
•   ** Graphics info for pid 30503 [com.xxx.demo] **
•   
•   Recent DisplayList operations
•   Save
•   DrawRenderNode
•     DrawRenderNode
•       DrawRect
•     DrawRenderNode
•       Save
•       ClipRect
•       DrawRenderNode
•         DrawRenderNode
•           Save
•           ClipRect
•           DrawPath
•           DrawPath
•           RestoreToCount
•           DrawBitmapRect
•       RestoreToCount
•     DrawRenderNode
•       DrawRenderNode
•         DrawRect
•   RestoreToCount
•   
•   Caches:
•   Current memory usage / total memory usage (bytes):
•     TextureCache            65536 / 50331648
•     LayerCache                  0 / 33554432 (numLayers = 0)
•     Layers total          0 (numLayers = 0)
•     RenderBufferCache           0 /  8388608
•     GradientCache               0 /  3145728
•     PathCache               14850 / 33554432
•     TessellationCache           0 /  1048576
•     TextDropShadowCache         0 /  6291456
•     PatchCache                  0 /   131072
•     FontRenderer 0 A8           0 /        0
•     FontRenderer 0 RGBA         0 /        0
•     FontRenderer 0 total        0 /        0
•   Other:
•     FboCache                    0 /       25
•   Total memory usage:
•     80386 bytes,0.08 MB
•   
•   Profile data in ms:
•   
•           com.xxx.demo/com.xxx.demo.MainActivity/android.view.ViewRootImpl@3719de6c (visibility=0)
View hierarchy:

  这里从android-4.0.1_r1源码跟进一下dumpsys gfxinfo 的调用过程。
  

源码探究

  首先从入口 \cmds\dumpsys\dumpsys.cpp文件入手。
通过遍历ServiceManager来获取对应参数的Service,并且调用其dump(FileDescriptor fd,PrintWriter pw,String[] args)方法.

int main(int argc,char* const argv[])
{
    //...

     const size_t N = services.size();

        if (N > 1) {
            // first print a list of the current services
            aout << "Currently running services:" << endl;

            for (size_t i=0; i<N; i++) {
                sp<IBinder> service = sm->checkService(services[i]);
                if (service != NULL) {
                    aout << " " << services[i] << endl;
                }
            }
        }

        for (size_t i=0; i<N; i++) {
            sp<IBinder> service = sm->checkService(services[i]); //获取service
            if (service != NULL) {
                if (N > 1) {
                    aout << "------------------------------------------------------------"
                            "-------------------" << endl;
                    aout << "DUMP OF SERVICE " << services[i] << ":" << endl;
                }
                int err = service->dump(STDOUT_FILENO,args); //调用dump
                if (err != 0) {
                    aerr << "Error dumping service info: (" << strerror(err)
                            << ") " << services[i] << endl;
                }
            } else {
                aerr << "Can't find service: " << services[i] << endl;
            }
        }

        return 0;
}

  而这些service是什么时候注册的呢,可以看一下services\java\com\android\server\am\ActivityManagerService.java文件。
有个static setSystemProcess()方法:

public static void setSystemProcess() {
        try {
            ActivityManagerService m = mSelf;

            ServiceManager.addService("activity",m);
            ServiceManager.addService("meminfo",new MemBinder(m));
            ServiceManager.addService("gfxinfo",new GraphicsBinder(m));
            if (MONITOR_CPU_USAGE) {
                ServiceManager.addService("cpuinfo",new CpuBinder(m));
            }
            ServiceManager.addService("permission",new PermissionController(m));

           //...
        } catch (PackageManager.NameNotFoundException e) {
            //...
        }
    }

  可以看到对应 gfxinfo所对应的service(Binder)GraphicsBinder,它是ActivityManagerService 的一个内部类。
其实现:

static class GraphicsBinder extends Binder {
        ActivityManagerService mActivityManagerService;
        GraphicsBinder(ActivityManagerService activityManagerService) {
            mActivityManagerService = activityManagerService;
        }

        @Override
        protected void dump(FileDescriptor fd,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,args);
        }
    }

前面调用的dump最终会调用ActivityManagerService.dumpGraphicsHardwareUsage方法。
  从代码中跟进这个方法:

final void dumpGraphicsHardwareUsage(FileDescriptor fd,String[] args) {
        //...

        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
            ProcessRecord r = procs.get(i);
            if (r.thread != null) {
               //..
                try {
                    TransferPipe tp = new TransferPipe();
                    try {

                        //这里r是processRecord,即最后调用ApplicationThread的dumpGfxInfo方法
                        r.thread.dumpGfxInfo(tp.getWriteFd().getFileDescriptor(),args);

                        tp.go(fd);
                    } finally {
                        tp.kill();
                    }
                } catch (IOException e) {
                    //..
                }
            }
        }
    }

  继续跟进,查看ApplicationThread实现。该类是ActivtyThread的一个内部类,该ActivtyThread.java文件路径core\java\android\app\ActivityThread.java
ApplicationThread集成自ApplicationThreadNative,这里主要看dumpGfxInfo方法。

private class ApplicationThread extends ApplicationThreadNative {
        //...

        @Override
        public void dumpGfxInfo(FileDescriptor fd,String[] args) {
            dumpGraphicsInfo(fd);
            WindowManagerImpl.getDefault().dumpGfxInfo(fd);
        }        

    }

  它先调用了ApplicationThreaddumpGraphicsInfo()方法,然后又调用了默认WindowManagerImpl实例的dumpGfxInfo()
  先看ApplicationThread.dumpGraphicsInfo方法:

private native void dumpGraphicsInfo(FileDescriptor fd);

就一行代码,调用了native方法,其实现实在core\jni\android_view_GLES20Canvas.cpp文件中。(注:最新的源码版本在core/jni/android_view_DisplayListCanvas.cpp)

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

执行的是DisplayListoutputLogBuffer()。代码在libs\hwui\DisplayListRenderer.cpp

void DisplayList::outputLogBuffer(int fd) {
    DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
    if (logBuffer.isEmpty()) {
        return;
    }

    FILE *file = fdopen(fd,"a");

    fprintf(file,"\nRecent DisplayList operations\n");
    logBuffer.outputCommands(file,OP_NAMES); //输出DisplayList

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

    fflush(file);
}

  可以看到如图两个关键部分,就是输出DisplayListCaches 的两个调用。
OP_NAMES是对于不同的DrawRecderNode所做相应的操作。
这里跟进Cache.dumpMemoryUsage()方法。位于libs\hwui\Caches.cpp

void Caches::dumpMemoryUsage(String8 &log) {
    log.appendFormat("Current memory usage / total memory usage (bytes):\n");
    log.appendFormat(" TextureCache %8d / %8d\n",textureCache.getSize(),textureCache.getMaxSize());
    log.appendFormat(" LayerCache %8d / %8d\n",layerCache.getSize(),layerCache.getMaxSize());
    log.appendFormat(" GradientCache %8d / %8d\n",gradientCache.getSize(),gradientCache.getMaxSize());
    log.appendFormat(" PathCache %8d / %8d\n",pathCache.getSize(),pathCache.getMaxSize());
    log.appendFormat(" CircleShapeCache %8d / %8d\n",circleShapeCache.getSize(),circleShapeCache.getMaxSize());
    log.appendFormat(" OvalShapeCache %8d / %8d\n",ovalShapeCache.getSize(),ovalShapeCache.getMaxSize());
    log.appendFormat(" RoundRectShapeCache %8d / %8d\n",roundRectShapeCache.getSize(),roundRectShapeCache.getMaxSize());
    log.appendFormat(" RectShapeCache %8d / %8d\n",rectShapeCache.getSize(),rectShapeCache.getMaxSize());
    log.appendFormat(" ArcShapeCache %8d / %8d\n",arcShapeCache.getSize(),arcShapeCache.getMaxSize());
    log.appendFormat(" TextDropShadowCache %8d / %8d\n",dropShadowCache.getSize(),dropShadowCache.getMaxSize());
    for (uint32_t i = 0; i < fontRenderer.getFontRendererCount(); i++) {
        const uint32_t size = fontRenderer.getFontRendererSize(i);
        log.appendFormat(" FontRenderer %d %8d / %8d\n",size);
    }
    log.appendFormat("Other:\n");
    log.appendFormat(" FboCache %8d / %8d\n",fboCache.getSize(),fboCache.getMaxSize());
    log.appendFormat(" PatchCache %8d / %8d\n",patchCache.getSize(),patchCache.getMaxSize());

    uint32_t total = 0;
    total += textureCache.getSize();
    total += layerCache.getSize();
    total += gradientCache.getSize();
    total += pathCache.getSize();
    total += dropShadowCache.getSize();
    total += roundRectShapeCache.getSize();
    total += circleShapeCache.getSize();
    total += ovalShapeCache.getSize();
    total += rectShapeCache.getSize();
    total += arcShapeCache.getSize();
    for (uint32_t i = 0; i < fontRenderer.getFontRendererCount(); i++) {
        total += fontRenderer.getFontRendererSize(i);
    }

    log.appendFormat("Total memory usage:\n");
    log.appendFormat(" %d bytes,%.2f MB\n",total / 1024.0f / 1024.0f);
}

  及相关的打印信息。
  回到前面 ApplicationThread的第二个调用WindowManagerImpl.getDefault().dumpGfxInfo(fd);。在拿到WindowManagerImpl默认实例后执行的动作,参见core\java\android\view\WindowManagerImpl.java

public void dumpGfxInfo(FileDescriptor fd) {
        FileOutputStream fout = new FileOutputStream(fd);
        PrintWriter pw = new PrintWriter(fout);
        try {
            synchronized (this) {
                if (mViews != null) {
                    pw.println("View hierarchy:");

                    final int count = mViews.length;

                    int viewsCount = 0;
                    int displayListsSize = 0;
                    int[] info = new int[2];

                    for (int i = 0; i < count; i++) {
                        ViewRootImpl root = mRoots[i];
                        root.dumpGfxInfo(pw,info);

                        String name = root.getClass().getName() + '@' +
                                Integer.toHexString(hashCode());                        
                        pw.printf(" %s: %d views,%.2f kB (display lists)\n",info[0],info[1] / 1024.0f);

                        viewsCount += info[0];
                        displayListsSize += info[1];
                    }

                    pw.printf("\nTotal ViewRootImpl: %d\n",count);
                    pw.printf("Total Views: %d\n",viewsCount);                    
                    pw.printf("Total DisplayList: %.2f kB\n\n",displayListsSize / 1024.0f);                    
                }
            }
        } finally {
            pw.flush();
        }        
    }

通过ViewRootImpl的dumpGfxInfo方法,将View数量和占用大小记录并输出。
    其中,core\java\android\view\ViewRootImpl.java的实现,加入了递归调用

public void dumpGfxInfo(PrintWriter pw,int[] info) {
        if (mView != null) {
            getGfxInfo(mView,info);
        } else {
            info[0] = info[1] = 0;
        }
    }

    private void getGfxInfo(View view,int[] info) {
        DisplayList displayList = view.mDisplayList;
        info[0]++;
        if (displayList != null) {
            info[1] += displayList.getSize();
        }

        if (view instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) view;

            int count = group.getChildCount();
            for (int i = 0; i < count; i++) {
                getGfxInfo(group.getChildAt(i),info);
            }
        }
    }

  以上,就是关于 gfxinfo 的dumpsys 命令实现。其他命令也是相同方式,不再赘述。感兴趣的可以自行查看相关内容。

最后,附上一个 利用 dumpsys 输出数据到界面的小例子。

相关文章

用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2...
#!/bin/bashcommand1&command2&wait从Shell脚本并行...
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/ph...
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如...
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexa...
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全...