Niestandardowe zdarzenia śledzenia w kodzie natywnym

Android 6.0 (poziom interfejsu API 23) i nowsze obsługują natywny interfejs API śledzenia (trace.h), aby zapisywać zdarzenia śledzenia w buforze systemowym, które możesz następnie analizować za pomocą Perfetto lub systrace. Typowe przypadki użycia tego interfejsu API obejmują obserwację czasu potrzebnego na wykonanie określonego bloku kodu oraz powiązanie bloku kodu z niepożądanym zachowaniem systemu.

Uwaga: jeśli nie ma wystarczającej ilości pamięci lub jest ona zbyt fragmentowana, na urządzeniach i emulatorach z interfejsem API poziomu 27 lub niższym pojawi się ten komunikat: Atrace could not allocate enough memory to record a trace. Jeśli tak się stanie, a przechwycony zbiór danych nie zgromadził pełnego zbioru danych, musisz zamknąć procesy w tle albo ponownie uruchomić urządzenie lub emulator.

Aby zdefiniować zdarzenia niestandardowe występujące w kodzie natywnym w aplikacji lub grze:

  1. Zdefiniuj wskaźniki funkcji dla funkcji ATrace, których używasz do rejestrowania zdarzeń niestandardowych w aplikacji lub grze, jak pokazano w tym fragmencie kodu:

    #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. Wczytywanie symboli ATrace w czasie działania, jak pokazano w tym fragmencie kodu. Zwykle tę procedurę wykonuje się w konstruktorze obiektów.

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

    Uwaga: ze względów bezpieczeństwa wywołania dlopen() umieszczaj tylko w wersji do debugowania aplikacji lub gry.

    Uwaga: aby zapewnić obsługę śledzenia na urządzeniach z Androidem 4.3 (poziom interfejsu API 18), możesz użyć JNI do wywoływania metod w kodzie zarządzanym wokół kodu widocznego we wcześniejszym fragmencie.

  3. Wywołaj zdarzenia ATrace_beginSection() i ATrace_endSection() na początku i na końcu zdarzenia niestandardowego:

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

    Uwaga: jeśli wywołasz metodę ATrace_beginSection() wiele razy, wywołanie ATrace_endSection() kończy się tylko ostatnio wywołaną metodą ATrace_beginSection(). W przypadku wywołań zagnieżdżonych sprawdź, czy każde wywołanie ATrace_beginSection() jest prawidłowo dopasowane do wywołania ATrace_endSection().

    Poza tym nie możesz wywołać funkcji ATrace_beginSection() w jednym wątku i zakończyć ją w innym wątku. Obie funkcje musisz wywołać z tego samego wątku.

Wskazówki

Te wskazówki są opcjonalne, ale mogą ułatwić analizę kodu natywnego.

Śledzić całą funkcję

Podczas instrumentowania stosu wywołań lub czasu funkcji może Ci się przydać śledzenie całych funkcji. Aby ułatwić sobie konfigurowanie tego typu śledzenia, możesz użyć makra ATRACE_CALL(). Poza tym takie makro pozwala pomijać tworzenie bloków try i catch w przypadkach, gdy śledzona funkcja może zgłosić wyjątek lub wcześnie wywołać metodę return.

Aby utworzyć makro do śledzenia całej funkcji, wykonaj te czynności:

  1. Zdefiniuj 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. Wywołaj makro w funkcji, którą chcesz śledzić:

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

Nazywanie wątków

Możesz nadać nazwę każdemu wątkowi, w którym występują zdarzenia, tak jak w tym fragmencie kodu. Ten krok ułatwia rozpoznawanie wątków należących do określonych działań w grze.

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