Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo definir eventos personalizados

El registro del sistema muestra información sobre los procesos solo a nivel del sistema; por lo tanto, algunas veces resulta difícil saber cuáles de los métodos de tu app o juego estaban en ejecución en un momento determinado respecto de los eventos del sistema.

La plataforma de Android proporciona una API de seguimiento que puedes usar para etiquetar una sección de código específica. Si capturas un registro del sistema nuevo de la versión de "depuración" de tu app e incluyes la opción -a, como se muestra en el siguiente fragmento, esos eventos personalizados aparecerán en un informe de Systrace:

    python systrace.py -a com.example.myapp -b 16384 \
      -o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal \
      dalvik camera input res
    

La opción -a es obligatoria para realizar el seguimiento de tu app. Sin esta opción, los métodos de la app no aparecerán en un informe de Systrace.

En esta guía, se describe cómo definir eventos personalizados tanto en el código administrado como en el código nativo.

Código administrado

En Android 4.3 (API nivel 18) y versiones posteriores, puedes usar la clase Trace en tu código a fin de definir eventos personalizados que, luego, aparezcan en los informes de Systrace, como se muestra en el siguiente fragmento de código.

Nota: Cuando llamas a beginSection() varias veces, la llamada a endSection() solo finaliza el método beginSection() al que se llamó más recientemente. Por lo tanto, para las llamadas anidadas, como las del siguiente fragmento, asegúrate de que cada llamada a beginSection() coincida con una llamada a endSection().

Además, no puedes llamar a beginSection() en un subproceso y finalizarla desde otro subproceso. Debes llamar a ambos métodos en la misma conversación.

Kotlin

    class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup,
                viewType: Int): MyViewHolder {
            return try {
                Trace.beginSection("MyAdapter.onCreateViewHolder")
                MyViewHolder.newInstance(parent)
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection()
            }
        }

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            Trace.beginSection("MyAdapter.onBindViewHolder")
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase")
                    val rowItem = queryDatabase(position)
                    dataset.add(rowItem)
                } finally {
                    Trace.endSection()
                }
                holder.bind(dataset[position])
            } finally {
                Trace.endSection()
            }
        }
    }
    

Java

    public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            Trace.beginSection("MyAdapter.onCreateViewHolder");
            MyViewHolder myViewHolder;
            try {
                myViewHolder = MyViewHolder.newInstance(parent);
            } finally {
                // In try and catch statements, always call "endSection()" in a
                // "finally" block. That way, the method is invoked even when an
                // exception occurs.
                Trace.endSection();
            }
            return myViewHolder;
        }

       @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            Trace.beginSection("MyAdapter.onBindViewHolder");
            try {
                try {
                    Trace.beginSection("MyAdapter.queryDatabase");
                    RowItem rowItem = queryDatabase(position);
                    dataset.add(rowItem);
                } finally {
                    Trace.endSection();
                }
                holder.bind(dataset.get(position));
            } finally {
                Trace.endSection();
            }
        }
    }
    

Código nativo

Android 6.0 (API nivel 23) y versiones posteriores admiten una API de seguimiento nativa, trace.h, que permite escribir eventos de seguimiento en el búfer del sistema que luego puedes analizar con Perfetto o Systrace. Algunos casos prácticos comunes para esta API consisten en observar el tiempo que tarda un bloque de código específico en ejecutarse y asociar un bloque de código con comportamientos del sistema no deseados.

En los dispositivos y emuladores que ejecutan la API nivel 27 o una versión inferior, si la memoria no es suficiente o está demasiado fragmentada, recibirás el siguiente mensaje: Atrace could not allocate enough memory to record a trace. En ese caso, tu captura no tendrá un conjunto completo de datos, cerrará procesos en segundo plano o reiniciará el dispositivo o el emulador.

Si quieres definir los eventos personalizados que se producen en el código nativo dentro de tu app o juego, completa los siguientes pasos:

  1. Define punteros de función para las funciones de ATrace que uses a fin de capturar eventos personalizados en tu juego, como se muestra en el siguiente fragmento de código:

        #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. Carga los símbolos de ATrace en el tiempo de ejecución, como se muestra en el siguiente fragmento de código. Por lo general, este proceso se lleva a cabo en un constructor de objetos.

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

    Precaución: Por motivos de seguridad, incluye las llamadas a dlopen() solo en la versión de depuración de tu app o juego.

    Nota: Si quieres que las versiones anteriores a Android 4.3 (API nivel 18) admitan el seguimiento, puedes usar JNI para llamar a los métodos en el código administrado alrededor del código que se muestra en el fragmento anterior.

  3. Llame a ATrace_beginSection() y a ATrace_endSection() al principio y al final, respectivamente, de tu evento personalizado:

        #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: Cuando llamas a ATrace_beginSection() varias veces, la llamada a ATrace_endSection() solo finaliza el método ATrace_beginSection() al que se llamó más recientemente. Por lo tanto, para las llamadas anidadas, asegúrate de que cada llamada a ATrace_beginSection() coincida con una llamada a ATrace_endSection().

    Además, no puedes llamar a ATrace_beginSection() en un subproceso y terminar la llamada desde un subproceso distinto. Debes llamar a ambas funciones desde el mismo subproceso.

Sugerencias

Las siguientes sugerencias son opcionales, pero podrían facilitar el análisis de tu código nativo.

Cómo hacer el seguimiento de una función completa

Cuando instrumentas tu pila de llamadas o sincronización de funciones, quizás te convenga hacer el seguimiento de funciones completas. Puedes usar la macro ATRACE_CALL() para facilitar la configuración de este tipo de seguimiento. Además, una macro de este tipo te permite omitir la creación de bloques try y catch para casos en los que la función registrada podría generar una excepción o llamar a return con anticipación.

Si deseas crear una macro para hacer el seguimiento de una función completa, sigue estos pasos:

  1. Define 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. Llama a la macro dentro de la función de la que quieres hacer el seguimiento:

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

Cómo asignarles un nombre a los subprocesos

Puedes asignarle un nombre a cada subproceso en el que ocurren tus eventos, como se indica en el siguiente fragmento de código. Este paso facilita la identificación de los subprocesos que pertenecen a acciones específicas dentro de tu juego.

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