The Android Developer Challenge is back! Submit your idea before December 2.

Systrace 用のカスタム イベントを定義する

Systrace で表示できるのは、システムレベルのプロセスに関する情報だけに限られるため、システム イベントが発生した時刻にアプリやゲームのいずれのメソッドが実行されていたのかを把握することは難しい場合があります。

Android プラットフォームには、特定のコード セクションにラベル付けできるトレース API が用意されています。下記のスニペットに示すように、-a オプションを追加して、アプリの「デバッグ」バージョンの新しいシステム トレースをキャプチャすると、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
    

このガイドでは、マネージコードとネイティブ コードの両方でカスタム イベントを定義する方法について説明します。

マネージコード

Android 4.3(API レベル 18)以降の場合、コード内で Trace クラスを使用してカスタム イベントを定義することで、Systrace レポート内に表示できます。下記のコード スニペットをご覧ください。

注: beginSection() を複数回呼び出した後で endSection() を呼び出すと、最後に呼び出された beginSection() メソッドだけが終了します。そのため、下記のスニペットのようなネスト呼び出しの場合、各 beginSection() 呼び出しと各 endSection() 呼び出しが適切にマッチングしているか確認する必要があります。

また、あるスレッドで beginSection() を呼び出して、別のスレッドから終了することはできません。1 つのスレッド内で両方のメソッドを呼び出す必要があります。

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

ネイティブ コード

Android 6.0(API レベル 23)以降の場合、ネイティブ トレース API(trace.h)がサポートされています。この API を使用すると、トレース イベントをシステム バッファに書き込み、Systrace を使用して分析することができます。この API の一般的なユースケースとしては、特定のコードブロックが実行に要した時間をモニタリングする場合や、コードブロックと望ましくないシステム動作とを関連付ける場合などが該当します。

アプリやゲーム内のネイティブ コード内で発生するカスタム イベントを定義する手順は次のとおりです。

  1. ゲーム内でカスタム イベントをキャプチャするために使用する ATrace 関数の関数ポインタを定義します。次のコード スニペットをご覧ください。

        &num;include <android/trace.h>
        &num;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. 下記のコード スニペットに示すように、実行時に ATrace シンボルをロードします。このプロセスは通常、オブジェクト コンストラクタ内で実行します。

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

    注: セキュリティ上の理由から、アプリやゲームのデバッグ バージョンに限り、dlopen() 呼び出しを組み込みます。

    注: Android 4.3(API レベル 18)にトレース サポートを戻すには、JNI を使用して、上記のスニペットに示したコードを中心とするマネージコード内のメソッドを呼び出します。

  3. カスタム イベントの最初と最後で、それぞれ ATrace_beginSection()ATrace_endSection() を呼び出します。

        &num;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();
        

    注: ATrace_beginSection() を複数回呼び出した後で ATrace_endSection() を呼び出すと、最後に呼び出された ATrace_beginSection() メソッドだけが終了します。そのため、ネスト呼び出しの場合、各 ATrace_beginSection() 呼び出しと各 ATrace_endSection() 呼び出しが適切にマッチングしているか確認する必要があります。

    また、あるスレッドで ATrace_beginSection() を呼び出して、別のスレッドから終了することはできません。1 つのスレッド内で両方の関数を呼び出す必要があります。

ヒント

以下のヒントは必須ではありませんが、ネイティブ コードの分析が容易になる場合があります。

関数全体をトレースする

コールスタックや関数のタイミングをインストルメント化する場合は、関数全体をトレースすると便利です。ATRACE_CALL() マクロを使用すると、この種のトレースを簡単にセットアップできます。また、このようなマクロを使用すると、トレース対象関数が例外をスローしたり、早期に return を呼び出したりする可能性のあるケースにおいて、try ブロックや catch ブロックの作成をスキップできます。

関数全体をトレースするマクロを作成する手順は次のとおりです。

  1. マクロを定義します。

        &num;define ATRACE_NAME(name) ScopedTrace ___tracer(name)
    
        // ATRACE_CALL is an ATRACE_NAME that uses the current function name.
        &num;define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
    
        class ScopedTrace {
          public:
            inline ScopedTrace(const char *name) {
              ATrace_beginSection(name);
            }
    
            inline ~ScopedTrace() {
              ATrace_endSection();
            }
        };
        
  2. トレースする関数内でマクロを呼び出します。

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

スレッドに名前を付ける

下記のコード スニペットに示すように、イベントが発生する各スレッドに対して名前を付けることができます。この手順を行っておくと、ゲーム内の個々のアクションに属するスレッドを簡単に識別できるようになります。

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