Android NDK开发详解之调试和性能分析的原生跟踪自定义事件

Android NDK开发详解之调试和性能分析的原生跟踪自定义事件

    • 定义自定义事件
    • 原生代码中的自定义跟踪事件

定义自定义事件

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

Jetpack 提供了一个跟踪 API,可用于为特定的代码段添加标签。然后,设备上捕获的跟踪记录中会报告此信息。Macrobenchmark 会自动使用自定义跟踪记录点来捕获跟踪记录。

使用 systrace 命令行工具捕获跟踪记录时,必须使用 -a 选项;如果不使用此选项,您应用的方法将不会显示在系统跟踪报告中。

Kotlin
如需在代码中创建自定义跟踪事件,请使用 tracking-ktx 库。 尤其是,顶层 trace 函数会自动处理开始和结束逻辑。详见以下代码段。

class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup,
            viewType: Int): MyViewHolder {
        trace("MyAdapter.onCreateViewHolder") {
            MyViewHolder.newInstance(parent)
        }
    }

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

Java
如需在代码中创建自定义跟踪事件,请使用 Jetpack 跟踪库中的 Trace 类,如以下代码段所示。
注意:如果您多次调用 beginSection(),调用 endSection() 只会结束最近调用的 beginSection() 方法。因此,对于嵌套调用(如以下代码段中所示),请务必将每次对 beginSection() 的调用与对 endSection() 的调用正确匹配。

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

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();
        }
    }
}

还有一个适用于自定义跟踪事件的 NDK API。如需详细了解如何使用该 API 来处理原生代码,请参阅原生代码中的自定义跟踪事件文档。

原生代码中的自定义跟踪事件

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

注意:在搭载 API 级别 27 及更低级别的设备和模拟器上,如果没有足够的可用内存或内存过于碎片化,您将收到以下消息:Atrace could not allocate enough memory to record a trace。 如果发生这种情况,并且您没有捕获到完整的数据集,则应当关闭后台进程或重新启动设备或模拟器。

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

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

#include 
#include 

void *(*ATrace_beginSection) (const char* sectionName);
void *(*ATrace_endSection) (void);

typedef void *(*fp_ATrace_beginSection) (const char* sectionName);
typedef void *(*fp_ATrace_endSection) (void);
在运行时加载 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 调用托管代码(在上述代码段中显示的代码附近)中的方法。

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

#include 

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,使用这样的宏还可以跳过创建 try 和 catch 代码块。

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

定义宏:

#define ATRACE_NAME(name) ScopedTrace ___tracer(name)

// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)

class ScopedTrace {
  public:
    inline ScopedTrace(const char *name) {
      ATrace_beginSection(name);
    }

    inline ~ScopedTrace() {
      ATrace_endSection();
    }
};

在要跟踪的函数中调用此宏:

void myExpensiveFunction() {
  ATRACE_CALL();
  // Code that you want to trace.
}
为线程命名
您可以为发生事件的每个线程命名,如以下代码段所示。此步骤可让您更轻松地识别属于游戏中特定操作的线程。


#include 

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");
}

你可能感兴趣的:(学习交流,android,java,kotlin,c++,数据结构)