Benutzerdefinierte Trace-Ereignisse im nativen Code

Android 6.0 (API-Level 23) und höher unterstützen die native Tracing API trace.h, um Trace-Ereignisse in den Systempuffer zu schreiben, die Sie dann mit Perfetto oder systrace analysieren können. Zu den häufigsten Anwendungsfällen für diese API gehört das Beobachten der Zeit, die ein bestimmter Codeblock zur Ausführung benötigt, und die Verknüpfung eines Codeblocks mit unerwünschtem Systemverhalten.

Hinweis: Wenn auf Geräten und Emulatoren mit API-Level 27 oder niedriger nicht genügend Arbeitsspeicher verfügbar ist oder der Arbeitsspeicher zu fragmentiert ist, wird die folgende Meldung angezeigt: Atrace could not allocate enough memory to record a trace. Wenn dies der Fall ist und Ihre Erfassung nicht über einen vollständigen Datensatz verfügt, sollten Sie Hintergrundprozesse schließen oder das Gerät oder den Emulator neu starten.

So definieren Sie benutzerdefinierte Ereignisse, die im nativen Code Ihrer App oder Ihres Spiels auftreten:

  1. Definieren Sie Funktionszeiger für die ATrace-Funktionen, mit denen Sie benutzerdefinierte Ereignisse in Ihrer App oder Ihrem Spiel erfassen, wie im folgenden Code-Snippet gezeigt:

    #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. Laden Sie die ATrace-Symbole während der Laufzeit, wie im folgenden Code-Snippet gezeigt. Normalerweise führen Sie diesen Vorgang in einem Objektkonstruktor durch.

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

    Achtung : Aus Sicherheitsgründen solltest du Aufrufe von dlopen() nur in der Debug-Version deiner App oder deines Spiels einfügen.

    Hinweis : Für Tracing-Unterstützung ab Android 4.3 (API-Ebene 18) können Sie mit JNI die Methoden im verwalteten Code um den im vorherigen Snippet herum gezeigten Code aufrufen.

  3. Rufen Sie ATrace_beginSection() bzw. ATrace_endSection() am Anfang bzw. am Ende des benutzerdefinierten Ereignisses auf:

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

    Hinweis: Wenn Sie ATrace_beginSection() mehrmals aufrufen, wird durch das Aufrufen von ATrace_endSection() nur die zuletzt aufgerufene ATrace_beginSection()-Methode beendet. Achte also bei verschachtelten Aufrufen darauf, dass jeder Aufruf ATrace_beginSection() einem Aufruf von ATrace_endSection() entspricht.

    Außerdem können Sie ATrace_beginSection() nicht in einem Thread aufrufen und in einem anderen beenden. Sie müssen beide Funktionen aus demselben Thread aufrufen.

Praktische Tipps

Die folgenden Tipps sind optional, erleichtern Ihnen aber möglicherweise die Analyse Ihres nativen Codes.

Eine ganze Funktion verfolgen

Bei der Instrumentierung Ihres Aufrufstacks oder der Funktionszeit kann es hilfreich sein, ganze Funktionen nachzuverfolgen. Mit dem Makro ATRACE_CALL() lässt sich diese Art von Tracing leichter einrichten. Außerdem können Sie mit einem solchen Makro das Erstellen der Blöcke try und catch in Fällen überspringen, in denen die verfolgte Funktion eine Ausnahme auslöst oder return vorzeitig aufruft.

Führen Sie die folgenden Schritte aus, um ein Makro zum Tracing einer gesamten Funktion zu erstellen:

  1. Definieren Sie das Makro:

    #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. Rufen Sie das Makro in der Funktion auf, die Sie verfolgen möchten:

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

Threads benennen

Sie können jedem Thread, in dem Ihre Ereignisse auftreten, einen Namen geben, wie im folgenden Code-Snippet gezeigt. Dieser Schritt erleichtert die Identifizierung der Threads, die zu bestimmten Aktionen in Ihrem Spiel gehören.

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