Eventi di traccia personalizzata nel codice nativo

Android 6.0 (livello API 23) e versioni successive supportano un'API di tracciamento nativa, trace.h, per scrivere eventi di traccia nel buffer di sistema che potrai analizzare utilizzando Perfetto o systrace. I casi d'uso comuni per questa API includono l'osservazione del tempo impiegato da un particolare blocco di codice per essere eseguito e l'associazione di un blocco di codice a un comportamento indesiderato del sistema.

Nota: sui dispositivi e negli emulatori che eseguono il livello API 27 o precedente, se non è disponibile memoria sufficiente o se la memoria è troppo frammentata, verrà visualizzato il seguente messaggio: Atrace could not allocate enough memory to record a trace. Se si verifica questa situazione e l'acquisizione non include un set completo di dati, devi chiudere i processi in background o riavviare il dispositivo o l'emulatore.

Per definire gli eventi personalizzati che si verificano nel codice nativo all'interno dell'app o del gioco, procedi nel seguente modo:

  1. Definisci i puntatori alle funzioni ATrace che utilizzi per acquisire eventi personalizzati nella tua app o nel tuo gioco, come mostrato nel seguente snippet di codice:

    #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. Carica i simboli ATrace in fase di runtime, come mostrato nel seguente snippet di codice. Di solito, questa procedura viene eseguita in un costruttore di oggetti.

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

    Attenzione : per motivi di sicurezza, includi le chiamate a dlopen() solo nella versione di debug dell'app o del gioco.

    Nota: per fornire supporto del tracciamento fino ad Android 4.3 (livello API 18), puoi utilizzare JNI per chiamare i metodi nel codice gestito intorno al codice mostrato nello snippet precedente.

  3. Chiama ATrace_beginSection() e ATrace_endSection() rispettivamente all'inizio e alla fine del tuo evento personalizzato:

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

    Nota: se chiami più volte ATrace_beginSection(), la chiamata al numero ATrace_endSection() termina solo il metodo ATrace_beginSection() chiamato più di recente. Quindi, per le chiamate nidificate, assicurati di abbinare correttamente ogni chiamata a ATrace_beginSection() con una chiamata a ATrace_endSection().

    Inoltre, non puoi chiamare ATrace_beginSection() su un thread e terminarlo da un altro. Devi chiamare entrambe le funzioni dallo stesso thread.

Suggerimenti utili

I seguenti suggerimenti sono facoltativi, ma potrebbero semplificare l'analisi del codice nativo.

Traccia un'intera funzione

Durante l'implementazione dello stack di chiamate o della temporizzazione della funzione, potrebbe essere utile tracciare intere funzioni. Puoi utilizzare la macro ATRACE_CALL() per semplificare la configurazione di questo tipo di tracciamento. Inoltre, una macro di questo tipo consente di saltare la creazione di blocchi try e catch per i casi in cui la funzione tracciata potrebbe generare un'eccezione o chiamare return in anticipo.

Per creare una macro per tracciare un'intera funzione, procedi nel seguente modo:

  1. Definisci la macro:

    #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. Richiama la macro all'interno della funzione che vuoi tracciare:

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

Assegna un nome ai thread

Puoi assegnare un nome a ogni thread in cui si verificano gli eventi, come mostrato nel seguente snippet di codice. Questo passaggio consente di identificare più facilmente i thread che appartengono ad azioni specifiche all'interno del gioco.

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