システム トレースでは、システムレベルでのみプロセスに関する情報が表示されるため、アプリまたはゲームのどのメソッドが、ある時点でシステム イベント対して実行されていたかを特定するのが困難な場合があります。
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
アプリのトレースには、-a
オプションが必要です。このオプションがないと、アプリのメソッドは Systrace レポートに表示されません。
このガイドでは、マネージコードとネイティブ コードの両方でカスタム イベントを定義する方法について説明します。
マネージコード
Android 4.3(API レベル 18)以降では、次のコード スニペットに示すように、コードで Trace
クラスを使用して、Perfetto レポートと 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
がサポートされており、システム バッファにトレース イベントを書き込み、Perfetto または Systrace を使用して分析できます。この API の一般的なユースケースとしては、特定のコードブロックが実行に要した時間をモニタリングする場合や、コードブロックと望ましくないシステム動作とを関連付ける場合などが該当します。
使用できるメモリが不足しているか、メモリが過度に断片化している場合は、API レベル 27 以下のデバイスとエミュレータでは、「Atrace could not allocate enough memory to record a trace
」というメッセージが表示されます。この問題が発生し、キャプチャに完全なデータセットがない場合は、バックグラウンド プロセスを閉じるか、デバイスまたはエミュレータを再起動します。
アプリやゲーム内のネイティブ コード内で発生するカスタム イベントを定義する手順は次のとおりです。
ゲーム内でカスタム イベントをキャプチャするために使用する ATrace 関数の関数ポインタを定義します。次のコード スニペットをご覧ください。
#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);
下記のコード スニペットに示すように、実行時に 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 を使用して、上記のスニペットに示したコードを中心とするマネージコード内のメソッドを呼び出します。
カスタム イベントの最初と最後で、それぞれ
ATrace_beginSection()
とATrace_endSection()
を呼び出します。#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
ブロックの作成をスキップできます。
関数全体をトレースするマクロを作成する手順は次のとおりです。
マクロを定義します。
#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(); } };
トレースする関数内でマクロを呼び出します。
void myExpensiveFunction() { ATRACE_CALL(); // Code that you want to trace. }
スレッドに名前を付ける
下記のコード スニペットに示すように、イベントが発生する各スレッドに対して名前を付けることができます。この手順を行っておくと、ゲーム内の個々のアクションに属するスレッドを簡単に識別できるようになります。
#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"); }