注:この記事はAndroid公式ウェブサイトのANRの翻訳です。一部の無関係な説明は翻訳されていませんが、全体的な意味は元のテキストと一致しています。元のテキストのリンクは次のとおりです。
(記事の「ワーカースレッド」は実際には子スレッドを指します)
まず、ANRの公式Android定義を見てみましょう。
AndroidアプリのUIスレッドが長時間ブロックされると、「Application Not Responding」(ANR)エラーがトリガーされます。アプリがフォアグラウンドにある場合、システムはユーザーにダイアログを表示します。
これは、アプリケーションのUIスレッドが長時間ブロックされると、ANR(アプリケーションが応答しない、アプリケーションが応答しない)エラーがトリガーされることを意味します。アプリケーションがフォアグラウンドにある場合、システムはユーザーにダイアログボックスを表示します(ダイアログボックスの外観については、ANRに遭遇したすべての人が知っている必要があります。ここには投稿しません)
APPのメインスレッド(UIの更新を担当するスレッド)はユーザー入力イベントや描画を処理できず、ユーザーに大きな問題を引き起こすため、ANRが非常に深刻な問題であることを明確にする必要があります。
ANRは、次のいずれかの条件が発生したときに発生します。
- アクティビティがフォアグラウンドにある場合、プログラムは5秒以内に入力イベントまたはBroadcastReceiverに応答しません(キーの押下、画面のタッチイベントなど)
- アクティビティがフォアグラウンドにない場合、BroadcastReceiverが長時間実行されていません
ANRを診断する方法
一般的なシナリオは次のとおりです。
- プログラムは、メインスレッドでのI / Oを含むいくつかの操作を実行します
- プログラムはメインスレッドでいくつかの長期的な操作を行います
- メインスレッドには他のプロセスへの同期バインダー呼び出しがありますが、他のプロセスは費やす必要があります
- 戻るのに長い時間
- メインスレッドは長い同期ロックを待ってブロックされ、この長い操作は別のスレッドにあります
- メインスレッドが同じプロセス内またはバインダー呼び出しを介して他のスレッドと対話する場合、メインスレッドはデッドロックされます。この時点で、メインスレッドは長い操作が完了するのを待っているだけでなく、デッドロック状態でもあります。
次の方法は、上記のどれがANRを引き起こすかを見つけるのに役立ちます。
(1)厳密モードを有効にする
プログラムを開発するとき、StrictModeを使用すると、メインスレッドで予期しないIO操作を見つけるのに役立ちます。StrictModeは、アプリケーションまたはアクティビティレベルで使用できます。
(2)バックグラウンドANRダイアログを有効にする
デバイスの「開発者向けオプション」の「すべてのANRを表示」スイッチが有効になっている場合にのみ、Androidはブロードキャストメッセージの処理に時間がかかるアプリのANRダイアログを表示します。したがって、バックグラウンドのANRダイアログが常に表示されるとは限りませんが、このAPPでは引き続きパフォーマンスの問題が発生しています。
(3)トレースビュー
プログラムがユースケースを実行している場合、Traceviewを使用して、実行中のプログラムのトレース情報を取得し、メインスレッドがビジー状態になっている場所を確認できます。Traceviewの使い方については、次のブログで紹介します。
(4)トレースファイルを引き出します
ANRが発生すると、Androidはトレース情報を保存します。古いリリースバージョンでは、デバイスに1つの/data/anr/traces.txtファイルしかありません。新しいリリースバージョンでは、複数の/ data / anr / anr_があります。 *ファイル。adbを使用して、デバイスまたはシミュレーターからこれらのトレースファイルにアクセスできます。
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
ANRの問題を修正する方法
(1)メインスレッドのスローコード(スローコード)
コード内でメインスレッドが5秒以上ビジー状態になっている場所を特定し、疑わしいユースケースシナリオを見つけて、ANRを再現します。たとえば、次の図に示すTraceviewのタイムラインでは、メインスレッドが5秒を超えてビジーです。
上の図は、onClick関数で時間のかかる操作コードが発生していることを示しています。コード例は次のとおりです。
@Override
public void onClick(View view) {
// 这个任务运行在主线程
BubbleSort.sort(data);
}
この場合、この時間のかかるコードをワーカースレッドに移動する必要があります。Androidフレームワークにはいくつかのクラスがあります。たとえば、次のサンプルコードは、AsyncTaskの使用方法を示しています。
@Override
public void onClick(View view) {
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
BubbleSort.sort(params[0]);// 运行在工作线程
}
}.execute(data);
}
Traceviewは、次の図に示すように、ほとんどのコードがワーカースレッドで実行されていることを示しており、メインスレッドはユーザーイベントに応答できます。
(2)メインスレッドのIO
メインスレッドでIO操作を実行すると、メインスレッドの操作が遅くなる一般的な原因であり、ANRが発生します。前のセクションで示したように、すべてのIO操作をワーカースレッドに移動することをお勧めします。IO操作の例としては、ネットワークとストレージがあります。詳細については、ネットワーク操作の実行 と データの保存を参照してください。
(3)ロックの競合
シナリオによっては、ANRを引き起こすタスクがメインスレッドで直接実行されない場合があります。ワーカースレッドが特定のリソースのロックを取得し、メインスレッドがタスクを完了するためにこのリソースを必要とする場合、ANRが発生する可能性があります。
次の図に示すTraceviewタイムラインでは、ほとんどのタスクがワーカースレッドAsyncTask#2で実行されます。
ただし、ANRが発生している場合は、Androidデバイスモニターでメインスレッドのステータスを確認する必要があります。通常、メインスレッドがUIを更新する準備ができていて正常に応答する場合、メインスレッドの状態は実行可能です。メインスレッドが実行を再開できない場合、メインスレッドはBLOCKED状態になり、イベントに応答できません。次の表に示すように、Androidデバイスモニターに表示されるステータスは[モニター]または[待機]です。
次のトレースは、リソースの待機中にメインスレッドがブロックされていることを示しています。
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
トレースを分析すると、メインスレッドをブロックするコードを見つけるのに役立ちます。次のコードは、メインスレッドをブロックするロックを保持しています。
@Override
public void onClick(View v) {
// 工作线程获得了 lockedResource 的锁
new LockTask().execute(data);
synchronized (lockedResource) {
// 主线程在这里需要 lockedResource,但是它必须等待 LockTask 使用完成
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// 这是一个长时间运行的操作,使得锁持续了一段时间
BubbleSort.sort(params[0]);
}
}
}
別の例は、次のコードに示すように、メインスレッドが別のワーカースレッドの結果を待機していることです。
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// 等待工作线程的通知
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// 结束,通知主线程
notify();
}
}
}
ロック、セマフォ、リソースプール(データベース接続プールなど)、またはその他のミューテックスメカニズムを使用するスレッドなど、メインスレッドをブロックできるシナリオは他にもあります。プログラム内の一般的なリソースによって保持されているロックを評価する必要がありますが、ANRを回避したい場合は、メインスレッドに必要なリソースによって保持されているロックに注意を払う必要があります。あなたがロックを使用している場合。ロックが最小の時間のために開催され、プログラムが最初にロックを必要とするかどうかを評価されていることを確認して、ワーカースレッドの処理に基づいてUIを更新するタイミングを決定、のようなイメージを使用 onProgressUpdate()
し、 onPostExecute() 这种机制来实现主线程和工作线程之间的通信。
デッドロック
スレッドに必要なリソースが別のスレッドによって保持されている場合、そのスレッドは待機状態になり、別のスレッドも最初のスレッドによって保持されているリソースを待機している場合、デッドロックが発生します。メインスレッドがこの状況にある場合、ANRが発生する可能性があります。デッドロックはコンピュータサイエンスで研究されている優れた現象であり、デッドロックを回避するためにいくつかのデッドロック防止アルゴリズムを使用できます。詳細については、Wikipediaのデッドロック および デッドロック防止アルゴリズムを参照してください 。
パフォーマンスの遅い放送受信機
アプリケーションは、フライトモードの有効化または無効化、ネットワーク接続ステータスの変更などのブロードキャストメッセージに応答できます。これらはすべて、ブロードキャストレシーバーによって実装できます。ANRは、プログラムがブロードキャストメッセージを長時間処理するときに発生します。
ANRは、次の状況で発生します。
- BroadcastReceiverは長い間onReceive()メソッドを終了しませんでした
- BroadcastReceiverがgoAsync()メソッドを呼び出し、PendingResultオブジェクトでfinish()を呼び出して失敗しました
BroadcastReceiverのonReceive()メソッドでは、短期間の操作のみを実行する必要があります。プログラムでより複雑なブロードキャストメッセージを処理する必要がある場合は、タスクをIntentServiceに委任して実行する必要があります。Traceviewなどのツールを使用して、レシーバーがメインスレッドで長期間の操作を実行するかどうかを確認できます。たとえば、次の図のタイムラインは、ブロードキャストレシーバーがメインスレッドでメッセージをほぼ100秒間処理することを示しています。
この動作は、次のサンプルコードなど、BroadcastReceiverのonReceive()メソッドで実行される長い操作が原因で発生する可能性があります。
@Override
public void onReceive(Context context, Intent intent) {
// 长时间的操作
BubbleSort.sort(data);
}
この場合、ワーカースレッドを使用してタスクを実行するため、長時間の操作コードを実装のためにIntentServiceに移動することをお勧めします。次のコードは、IntentServiceを使用して長い操作を処理する方法を示しています。
@Override
public void onReceive(Context context, Intent intent) {
// 现在这个任务运行在工作线程
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
IntentServiceを使用した結果、次の図に示すように、この長期操作はメインスレッドではなく、ワーカースレッドで実行されます。
ブロードキャストレシーバーは、goAsync()メソッドを使用して、メッセージの処理にさらに時間が必要であることをシステムに通知できます。ただし、PendingResultオブジェクトでfinish()メソッドも呼び出す必要があります。次の例は、finish()メソッドを呼び出して、システムがブロードキャストメッセージを回復し、ANRを回避できるようにする方法を示しています。
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// 长时间的操作
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
ただし、このブロードキャストがバックグラウンドで行われている場合、コードを別のスレッドに移動してgoAsync()を使用しても、ANRは修正されず、ANRタイムアウトは引き続き有効になります。
ANRの詳細については、アプリのレスポンシブな維持を参照して ください。スレッドの詳細については、スレッドのパフォーマンスを参照してください 。