The Android Developer Challenge is back! Submit your idea before December 2.

为 Systrace 定义自定义事件

Systrace 仅会在系统级别显示进程的相关信息,因此有时很难知道应用或游戏的哪些方法是在给定时间相对于系统事件执行的。

Android 平台提供了一个跟踪 API,可用于为特定的代码段添加标签。如果您捕获应用的“调试”版本的新系统跟踪并添加 -a 选项(如以下代码段所示),这些自定义事件便会显示在 Systrace 报告中:

    python systrace.py -a com.example.myapp -b 16384 \
      -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal \
      dalvik camera input res
    

本指南介绍了如何在托管代码和原生代码中定义自定义事件。

托管代码

在 Android 4.3(API 级别 18)及更高版本中,您可以使用代码中的 Trace 类来定义随后出现在 Systrace 报告中的自定义事件,如以下代码段所示。

注意:当您调用 beginSection() 多次时,调用 endSection() 只会结束最近调用的那个 beginSection() 方法。因此,对于嵌套调用(如以下代码段中的调用),请务必将每次对 beginSection() 的调用与对 endSection() 的调用正确匹配

此外,您不能在一个线程上调用 beginSection(),而从另一个线程将其结束;您必须在同一个线程中调用这两个方法。

Kotlin

    class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup,
                viewType: Int): MyViewHolder {
            return try {
                Trace.beginSection("MyAdapter.onCreateViewHolder")
                MyViewHolder.newInstance(parent)
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection()
            }
        }

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            Trace.beginSection("MyAdapter.onBindViewHolder")
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase")
                    val rowItem = queryDatabase(position)
                    dataset.add(rowItem)
                } finally {
                    Trace.endSection()
                }
                holder.bind(dataset[position])
            } finally {
                Trace.endSection()
            }
        }
    }
    

Java

    public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            Trace.beginSection("MyAdapter.onCreateViewHolder");
            MyViewHolder myViewHolder;
            try {
                myViewHolder = MyViewHolder.newInstance(parent);
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection();
            }
            return myViewHolder;
        }

       @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            Trace.beginSection("MyAdapter.onBindViewHolder");
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase");
                    RowItem rowItem = queryDatabase(position);
                    dataset.add(rowItem);
                } finally {
                    Trace.endSection();
                }
                holder.bind(dataset.get(position));
            } finally {
                Trace.endSection();
            }
        }
    }
    

原生代码

Android 6.0(API 级别 23)及更高级别支持原生跟踪 API trace.h,用于将跟踪事件写入系统缓冲区,以供您使用 Systrace 进行分析。此 API 的常见用例包括观察特定代码块的执行时间以及确定某代码块会产生哪些不良系统行为。

要定义应用或游戏内的原生代码中发生的自定义事件,请完成以下步骤:

  1. 定义在游戏内捕获自定义事件所用 ATrace 函数的函数指针,如以下代码段所示:

        &num;include <android/trace.h>
        &num;include <dlfcn.h>
    
        void *(*ATrace_beginSection) (const char* sectionName);
        void *(*ATrace_endSection) (void);
    
        typedef void *(*fp_ATrace_beginSection) (const char* sectionName);
        typedef void *(*fp_ATrace_endSection) (void);
        
  2. 在运行时加载 ATrace 符号,如以下代码段所示。通常,您在对象构造函数中执行此进程。

        // Retrieve a handle to libandroid.
        void *lib = dlopen("libandroid.so", RTLD_NOW || RTLD_LOCAL);
    
        // Access the native tracing functions.
        if (lib != NULL) {
            // Use dlsym() to prevent crashes on devices running Android 5.1
            // (API level 22) or lower.
            ATrace_beginSection = reinterpret_cast<fp_ATrace_beginSection>(
                dlsym(lib, "ATrace_beginSection"));
            ATrace_endSEction = reinterpret_cast<fp_ATrace_endSection>(
                dlsym(lib, "ATrace_endSection"));
        }
        

    注意:出于安全考虑,请仅在应用或游戏的调试版本中包含对 dlopen() 的调用。

    注意:为了进一步提供对 Android 4.3(API 级别 18)的跟踪支持,您可以使用 JNI 调用托管代码(在上述代码段中显示的代码附近)中的方法。

  3. 在自定义事件的开头和结尾分别调用 ATrace_beginSection()ATrace_endSection()

        &num;include <android/trace.h>
    
        char *customEventName = new char[32];
        sprintf(customEventName, "User tapped %s button", buttonName);
    
        ATrace_beginSection(customEventName);
        // Your app or game's response to the button being pressed.
        ATrace_endSection();
        

    注意:当您调用 ATrace_beginSection() 多次时,调用 ATrace_endSection() 只会结束最近调用的那个 ATrace_beginSection() 方法。因此,对于嵌套调用,请务必将每次对 ATrace_beginSection() 的调用与对 ATrace_endSection() 的调用正确匹配。

    此外,您不能在一个线程上调用 ATrace_beginSection(),而从另一个线程将其结束。您必须在同一线程中调用这两个函数。

温馨提示

以下提示是可选的,但可能会帮助分析人员更轻松地分析原生代码。

跟踪整个函数

在检测调用堆栈或函数计时时,您可能会发现跟踪整个函数非常有用。您可以使用 ATRACE_CALL() 宏来更轻松地设置此类跟踪。此外,如果跟踪函数可能会抛出异常或提前调用 return,则可以使用此类宏跳过 trycatch 块的创建。

要创建用于跟踪整个函数的宏,请完成以下步骤:

  1. 定义宏:

        &num;define ATRACE_NAME(name) ScopedTrace ___tracer(name)
    
        // ATRACE_CALL is an ATRACE_NAME that uses the current function name.
        &num;define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
    
        class ScopedTrace {
          public:
            inline ScopedTrace(const char *name) {
              ATrace_beginSection(name);
            }
    
            inline ~ScopedTrace() {
              ATrace_endSection();
            }
        };
        
  2. 在要跟踪的函数中调用此宏:

        void myExpensiveFunction() {
          ATRACE_CALL();
          // Code that you want to trace.
        }
        

为线程命名

您可以为发生事件的每个线程命名,如以下代码段所示。此步骤可以更轻松地识别属于游戏中特定操作的线程。

    &num;include <pthread.h>

    static void *render_scene(void *parm) {
        // Code for preparing your app or game's visual components.
    }

    static void *load_main_menu(void *parm) {
        // Code that executes your app or game's main logic.
    }

    void init_threads() {
        pthread_t render_thread, main_thread;

        pthread_create(&render_thread, NULL, render_scene, NULL);
        pthread_create(&main_thread, NULL, load_main_menu, NULL);

        pthread_setname_np(render_thread, "MyRenderer");
        pthread_setname_np(main_thread, "MyMainMenu");
    }