Événements de trace personnalisés en code natif

Android 6.0 (niveau d'API 23) et versions ultérieures sont compatibles avec l'API de traçage native trace.h, qui permet d'écrire des événements de trace dans la mémoire tampon du système. Vous pouvez ensuite les analyser à l'aide de Perfetto ou de Systrace. Cette API permet généralement d'observer le temps nécessaire à l'exécution d'un bloc de code spécifique et d'associer un bloc de code à un comportement système indésirable.

Remarque : Sur les appareils et les émulateurs exécutant un niveau d'API 27 ou inférieur, si la mémoire disponible est insuffisante ou trop fragmentée, le message suivant s'affiche : Atrace could not allocate enough memory to record a trace (ATrace n'a pas pu allouer suffisamment de mémoire pour enregistrer une trace). Si cela se produit et que votre capture ne dispose pas d'un ensemble complet de données, vous devez fermer les processus en arrière-plan ou redémarrer l'appareil ou l'émulateur.

Pour définir des événements personnalisés qui se produisent dans le code natif de votre application ou de votre jeu, procédez comme suit :

  1. Définissez des pointeurs de fonction pour les fonctions ATrace que vous utilisez pour capturer des événements personnalisés dans votre application ou votre jeu, comme indiqué dans l'extrait de code suivant :

    #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. Chargez les symboles ATrace au moment de l'exécution, comme indiqué dans l'extrait de code suivant. En général, ce processus s'effectue dans un constructeur d'objets.

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

    Attention : Pour des raisons de sécurité, n'incluez des appels à dlopen() que dans la version de débogage de votre application ou de votre jeu.

    Remarque : Pour que le traçage soit encore plus compatible avec Android 4.3 (niveau d'API 18), vous pouvez utiliser JNI pour appeler les méthodes dans le code géré comme le montre le code de l'extrait précédent.

  3. Appelez ATrace_beginSection() et ATrace_endSection() respectivement au début et à la fin de votre événement personnalisé :

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

    Remarque : Lorsque vous appelez ATrace_beginSection() plusieurs fois, l'appel de ATrace_endSection() ne met fin qu'à la méthode ATrace_beginSection() la plus récente. Ainsi, pour les appels imbriqués, assurez-vous de faire correspondre chaque appel à ATrace_beginSection() avec un appel à ATrace_endSection().

    De plus, vous ne pouvez pas appeler ATrace_beginSection() sur un thread et le terminer sur un autre. Vous devez appeler les deux fonctions à partir du même thread.

Conseils pratiques

Les conseils suivants sont facultatifs, mais peuvent faciliter l'analyse de votre code natif.

Tracer une fonction entière

Lors de l'instrumentation de votre pile d'appel ou de la planification de fonctions, il peut être utile de tracer des fonctions entières. Vous pouvez utiliser la macro ATRACE_CALL() pour faciliter la configuration de ce type de traçage. De plus, une telle macro vous permet d'ignorer la création de blocs try et catch dans les cas où la fonction tracée peut générer une exception ou appeler return de manière anticipée.

Pour créer une macro de traçage d'une fonction entière, procédez comme suit :

  1. Définissez 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. Appelez la macro dans la fonction que vous souhaitez tracer :

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

Nommer vos threads

Vous pouvez attribuer un nom à chaque thread dans lequel se produisent vos événements, comme illustré dans l'extrait de code suivant. Cette étape permet d'identifier plus facilement les threads qui appartiennent à des actions spécifiques dans votre jeu.

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