原生程式碼中的自訂追蹤事件

Stay organized with collections Save and categorize content based on your preferences.

Android 6.0 (API 級別 23) 以上版本支援原生追蹤記錄 API (trace.h),可將在系統緩衝區中寫入追蹤記錄事件,然後就可使用 Perfetto 或 systrace 進行分析。此 API 的常見用途包括觀察特定程式碼區塊的執行時間,以及將程式碼區塊與不當系統行為建立關聯的時間。

注意:在執行 API 級別 27 以下版本的裝置和模擬器中,如果可用記憶體不足或記憶體過舊,系統就會顯示以下訊息:Atrace could not allocate enough memory to record a trace。 在這種情況下,如果擷取的資料沒有完整的資料組合,建議您關閉背景處理程序,或重新啟動裝置或模擬器。

如要定義應用程式或遊戲中的原生程式碼發生的自訂事件,請完成下列步驟:

  1. 定義 ATrace 函式的函式指標,可用於擷取應用程式或遊戲中的自訂事件,如以下程式碼片段所示:

    #include <android/trace.h>
    #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()

    #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. 定義巨集:

    #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();
        }
    };
    
  2. 在您要追蹤的函式內呼叫巨集:

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

命名執行緒

您可以為每個事件發生的執行緒命名,如以下程式碼片段所示。這個步驟可讓您以更輕鬆的方式辨識屬於遊戲中特定動作的執行緒。

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