作者: ドラモア
1 ANR はどこから来るのか?
ANR (アプリケーションが応答していません): Android アプリの UI スレッドが長時間ブロックされていると、「アプリケーションが応答していません」(ANR) エラーがトリガーされます。アプリがフォアグラウンドにある場合、システムはユーザーにダイアログを表示します。ANR ダイアログは、アプリを強制終了するオプションをユーザーに提供します。
インターフェイスの更新を担当するメイン アプリケーション スレッドがユーザー入力イベントや描画操作を処理できないため、ANR が問題になります。
以上が ANR の公式説明であり、ANR の目的は、ユーザーの直接的な知覚に重大な影響を与える入力およびインターフェイス描画操作の動作に対する、システム レベルからの一種の自己防衛です。
1.1 4つの条件
システム レベルで ANR をトリガーするノードは 4 つだけです。つまり、メインスレッドに非常に時間のかかるタスクがあっても、ANR が発生する条件がなければ ANR は発生しません。
次のいずれかの条件が発生すると、アプリに対して ANR がトリガーされます。
- スケジューリングのタイムアウトを入力します。しきい値は 5 秒です
- サービス実行タイムアウト、しきい値内でサービスの作成と起動を完了できない場合も ANR がトリガーされます。フォアグラウンド タスクのしきい値は 10 秒、バックグラウンド タスクのしきい値は 60 秒です。
- ContentProvider スケジューリング タイムアウト、しきい値は 10 秒です
- BroadcastReceiver スケジューリング タイムアウト、しきい値は 10 秒
1.2 システムが生成する ANR の大まかな流れ
BroadcastReceive タイムアウトを例として、システムが ANR をトリガーする方法を確認します。
1.2.1 BroadcastReceiver が ANR を生成する
BroadcastReceiver 処理ブロードキャストのコア ロジックは次の場所BroadcastQueue
にあります。
public final class BroadcastQueue {
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
setBroadcastTimeoutLocked(timeoutTime);
...
performReceiveLocked(...);//内部最终会调用BroadcastReceiver的onReceiver
...
cancelBroadcastTimeoutLocked();//解除超时
..
}
// 设置超时
final void setBroadcastTimeoutLocked(long timeoutTime) {
if (!mPendingBroadcastTimeoutMessage) {
Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
mHandler.sendMessageAtTime(msg, timeoutTime);
mPendingBroadcastTimeoutMessage = true;
}
}
//解除超时
final void cancelBroadcastTimeoutLocked() {
if (mPendingBroadcastTimeoutMessage) {
mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
mPendingBroadcastTimeoutMessage = false;
}
}
上記は、放送終了までに ANR を設定、キャンセル、トリガーするコア ロジックです。ハンドラー メカニズムを介して [ANR タスク] の送信を遅らせ、指定された時間内にブロードキャスト レシーバー タスクの削除 ANR タスクを完了します。それ以外の場合はトリガーします。
1.2.1 システム処理 ANR
実際、ANR をトリガーする条件に関係なく、処理のために AnrHelper に渡されます. このクラスの ANR を処理するコアのロジックは、「AnrConsumer」という名前のスレッドを開始します。ProcessErrorStateRecord でメソッドを実行しますappNotResponding()
。
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
ArrayList<Integer> firstPids = new ArrayList<>(5);
SparseArray<Boolean> lastPids = new SparseArray<>(20);
...
setNotResponding(true);//标记ANR标识
...
firstPids.add(pid);
...
isSilentAnr = isSilentAnr();//后台的应用发生ANR
if (!isSilentAnr && !onlyDumpSelf) {//前台进程和不仅仅dump自身时
mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
...
firstPids.add(pid);//添加其他进程
}
});
...
StringBuilder report = new StringBuilder();
report.append(MemoryPressureUtil.currentPsiState());//内存信息
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);//cup信息
nativePids.add(..); //添加native进程
...
File tracesFile = ..
report.append(tracesFileException.getBuffer());
info.append(processCpuTracker.printCurrentState(anrTime));
if (tracesFile == null) {
Process.sendSignal(pid, Process.SIGNAL_QUIT); //不dump信息时直接发送SIGNAL_QUIT信号
}
...
File tracesFile = ActivityManagerService.dumpStackTraces(..); //dump栈
...
mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);//ANR弹窗
}
注目ポイント
- Silent ANR」、フォアグラウンド ANR は応答しないダイアログを再生し、バックグラウンド ANR はプロセスを直接強制終了します**。
- 情報をダンプするときは、最初に ANR プロセスの情報をダンプし、条件が許せば他の関連プロセスとネイティブ プロセスをダンプします。システム プロセスに処理する ANR が大量にあり、60 秒以上かかる場合、またはサイレント プロセスである場合、ANR プロセス情報のみがダンプされます。
- すべてのプロセスをダンプする合計時間は 20 秒を超えることはできません.20 秒を超える場合は、すぐに戻って、ANR ポップアップ ウィンドウが時間内にポップアップする (または強制終了される) ことを確認してください。
Process.sendSignal(pid, Process.SIGNAL_QUIT);
システムは Process.SIGNAL_QUIT シグナルを発行します。これはとても重要です
(微信チームからの写真)
アプリケーションで ANR が発生すると、システムは多くのプロセスを収集し、スタックをダンプして ANR トレース ファイルを生成します. 最初に収集されたプロセスは、収集する必要があるプロセスです. SIGQUIT シグナルを送信すると、アプリケーション プロセスはスタックのダンプを開始しますSIGQUITを受け取った後。
2 アプリケーション層の監視方法
Android M(6.0)版以降は、監視data/anr/trace
ファイル。
2.1 WatchDog スキーム
Caton Monitoring の記事でもこのソリューションを紹介しました. 主なアイデアは、メイン スレッド MessageQueue が指定された時間 (5 秒) 内に特定のメッセージを処理したかどうかを検出するタイムアウト検出です。指定されたメッセージが指定された時間内に処理されない場合、ANR が発生したと見なされます。
このスキームは、ANR の欠点を検出するために使用されます。
- 不正確: タイムアウト条件がトリガーされた場合、ソリューションは必ずしも ANR を生成しません。5 秒のタイムアウトは、
ToucEvent
消費されない場合の ANR の条件にすぎません。ANR を生成するその他の条件は 5 秒ではありません。 - 洗練されていない: このソリューションは、メイン スレッド メッセージのスケジューリングを常に「ビジー状態」に保ち、アプリケーションの電力消費と負荷に不必要な影響を与えます。
2.2 モニターシグナル方式 ( SIGQUIT )
システムにANRが発生するとSIGQUIT
信号が発信され、この信号を監視することでANRの発生を判断することができます。このソリューションは、市場で ANR を監視するための主要なソリューションでもあります。
Zygote プロセスを除いて、各プロセスにはSignalCatcher
スレッドがあり、SIGQUIT シグナルをキャッチしてそれに応じて動作します。Android はデフォルトで SIGQUIT を BLOCKED に設定します。これは、SignalCatcher
スレッドのみがシグナルをリッスンできることを意味しSIGQUIT
、sigaction
シグナルをリッスンするために登録することはできません。SIGQUIT
信号を受信できるように、UNBLOCK に設定します。ただし、システムのメカニズムを破壊することなく信号を再送信する必要があることに注意してください。
2.2.1 誤検知と完全性
システムから送信されたシグナルSIGQUIT
は、必ずしもアプリケーションが ANR を持っていることを意味するわけではありません。それ以外の場合は、「SIGQUIT」シグナルも送信されます。たとえば、ANR は他のプロセスで発生します。
ソースコードで答えを見つける
private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
setNotResponding(true);
// mAppErrors can be null if the AMS is constructed with injector only. This will only
// happen in tests.
if (mService.mAppErrors != null) {
mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, //把发生ANR的进程
activity, shortMsg, longMsg, null);
}
startAppProblemLSP();
mApp.getWindowProcessController().stopFreezingActivities();
}
ANR が発生すると、システムは ANR を持つプロセスにマークを付けますNOT_RESPONDING
. アプリケーション層で ActivityManager を介してステータスを確認できます. コード ミルクは次のとおりです:
private static boolean checkErrorState() {
try {
ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
if (procs == null) return false;
for (ActivityManager.ProcessErrorStateInfo proc : procs) {
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
return true;
}
return false;
} catch (Throwable t){
}
return false;
}
シグナル受信後SIGQUIT
、一定時間継続して状態を確認し、フラグが取得できれば、現工程でANRが発生していると考えられます。
2.2.2 偽陰性と改善
一部の ANR 発生は設定されず、プロセスはNOT_RESPONDING
識別されません
- サイレント ANR ( SilentAnr )、SilentANR はプロセスを直接強制終了し、このフラグを設定しません。
- Flashback ANR、OPPO VIVO モデルは ANR の直後にフラッシュバックし、このフラグは設定されません。
解決策:メインスレッドのスタック状態と組み合わせます。
Reflection はメイン スレッドMessageQueue
のmMessages
オブジェクトを取得します. このオブジェクトの when 変数は, メッセージが処理されると予想される時間です. この変数と現在の時間との差は, メッセージの待ち時間を得ることができます. 遅延時間のかかる、時間がかかりすぎる場合は、メインスレッドが「スタック」していることを意味します。
受信しSIGQUIT
、現在のメイン スレッドがスタックしている場合、ANR が発生したと見なされます。
2.2.3 ANR モニタリングのまとめ
システム信号を監視し、SIGQUIT
現在のプロセスのNOT_RESPONDING
識別、プロセスで ANR が発生したことを総合的に判断します。
これは、ANRが発生したことを知り、ANRが発生したことを知り、ANRの原因をさらに知り、ANRが発生したときにコンテキスト情報を収集し、ANRを解決することがより重要であることを示しています.
3 情報収集と監視
3.1 ANR の問題箇所特定の難しさ
ANR情報収集を観察することの難しさは、情報収集がしばしば不正確で不完全であることです.ANRが発生した場合、その時点で収集された情報はANRの本当の原因ではないため、トラブルシューティングのための収集情報の基準値は大幅に減少します.
上図のように、メインスレッドで時間のかかるタスクが実行され、サービス起動タスクが所定の閾値を超えた時点でANRが発生した場合、このときに収集される情報が通常のタスク呼び出し情報です。
一般に、ANR の原因には、メイン スレッドの過剰な実行時間とシステム負荷の高さが含まれます。
メインスレッドタスクの実行時間が長すぎて、大まかに以下のタイプに分けられます
- ANR をトリガーする時間のかかる履歴メッセージが複数あります。
- 歴史的なニュースの中で、非常に時間のかかるニュースがあります。
- 非常に多くの時間のかかる小さなメッセージを実行すると、多くの時間がかかり、ANR がトリガーされます。
システム メモリや CPU 負荷が不足するなど、システムの負荷が高すぎて、タスクが実行されていません。
メイン スレッドの履歴メッセージ タスク、現在および今後のタスク、および一定期間内のシステム負荷条件をより完全に記録できれば、ANR の問題をより正確に診断することが非常に重要になります。
3.2 メッセージスケジューリング監視
メイン スレッドは Looper メッセージの実行計画を記録および監視します。私たちは当然、Looper の Printer 計画に注意を向けます。これについては、サンバンフの最初の 2 つの記事で紹介されており、ここでは拡張しません。
ルーパーがメッセージを配信して実行すると、前後のメッセージ情報が出力され、ターゲット、メッセージのコールバック、メッセージの実行にかかった時間など、メッセージ タスクの関連情報を取得でき、
ます、
。
メッセージには時間がかかり、メイン スレッドの WallTime と ThreadTime を収集する必要があります。
- WallTime: タスクにかかった時間 (ロックの待機を含む)、およびスレッドのスリープ タイム スライスに費やされた時間。
- ThreadTime (CpuTime) は、ロックの待機時間を除いた、スレッドの実際の実行時間です。次に、側のシステム負荷を推測できます。
ほとんどの場合、メッセージの実行は短時間で完了し、Looper にも Idel 状態、つまりメッセージが実行されていない状態があり、これらのメッセージを集約する必要があります。
また、Sanbanx 連載記事のメインスレッドの時間のかかる監視では、メインスレッドがメッセージを処理することが紹介されています.通常、監視する必要がある Looper によって配信されるメッセージに加えて、IdleHandler およびより完全にするために、TouchEvent メッセージも統計レコードに含める必要があります。
3.2.1 メッセージの集約と分類
- 連続時間のかかる小さなメッセージ、50ms未満の連続メッセージ、レコードに集約された統計、レコードに格納されたメッセージの数、合計時間のかかる情報など。
- しきい値を超えるメッセージについては、1 つのレコードがカウントされます。
- システム コール メッセージの統計情報 (ActivityThread.H Activity、Service、ContentProvider) は、ANR の問題を分析するために非常に重要です。
- IDLE ステータス メッセージは個別にカウントされます。
- IdleHandler の統計。
- TouchEvent などのネイティブ レイヤーによってトリガーされるメイン スレッド スケジューリング タスク統計。
一般に、メッセージはタイプ、集約タイプ (Agree)、および時間がかからない連続メッセージに分類されます。時間のかかるタイプ (Huge): 50ms を超えるメッセージ。システムコールメッセージ (SYSTEM)
3.2.2 メッセージスタックの収集
Looper メッセージの何を、どのようなコールバックを行い、どのような処理を行ったかを統計的に記録するだけでなく、各メッセージでどのようなアクションが実行されたかを収集する必要があります.これには、各メッセージの実行スタックの収集が必要です.実行スタックの頻繁な収集にはパフォーマンスへの影響大きいものは戦略的な収集が必要です。
- サブスレッドがメインスレッドのスタックを収集できるようにします。
- 時間がかからないメッセージ タスクは収集されません。
- 一定の閾値を超えて実行されていないメッセージタスクを一度収集し、一定時間メッセージタスクが実行されていない場合は再度収集を行い、間隔時間を直線的に増加させます。
- 【shoppeのノンブロッキング効率的なスタックグラビング】
3.2.3 実行中メッセージと保留中メッセージの統計
ANR が発生する前にメイン スレッドの履歴メッセージのスケジューリングと所要時間を監視することに加えて、スケジュールされているメッセージと、ANR が発生したときの所要時間も把握する必要があります。現在の Trace ロジックを明確に知ることができます。
MessageQueue で実行されるのを待っているメッセージをカウントすることも必要です。
- どの成分がANRを誘発するかを知るために
- 実行待ちメッセージの待機時間をカウントできます。メッセージ キューがどの程度ビジーかを判断します。
3.3 より包括的な情報収集
上記では、メイン スレッドのスケジューリング タスクにかかる時間を包括的に監視し、カウントしました。
3.3.1 ANRInfo を取得する
アプリケーション層は、ActivityManager を通じて ANRInfo を取得できます。
val am = application.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processesInErrorStates = am.processesInErrorState
ProcessesInErrorState で利用可能
shortMsg: ANR Inputdispatching timed out ...
- shortMessage: ANR、TouchEvent タイムアウト、Servcie 起動タイムアウトなどの直接的な原因。
Reason: Input dispatching timed out
xxx is not responding. Waited 5003ms for FocusEvent(hasFocus=false))
Load: 3.19 / 2.46 / 2.42
----- Output from /proc/pressure/memory -----
some avg10=0.00 avg60=0.00 avg300=0.00 total=144741615
full avg10=0.00 avg60=0.00 avg300=0.00 total=29370150
----- End output from /proc/pressure/memory -----
CPU usage from 0ms to xxx ms later with 99% awake:
TOTAL: 6.4% user + 8.2% kernel + 0% iowait + 0.6% irq + 0.1% softirq
CPU usage from 0ms to xxx ms later with 99% awake:
27% TOTAL: 10% user + 14% kernel + 0.3% iowait + 0.9% irq + 0.3% softirq
- longMessage: システムの負荷、カップの使用状況、IO、その他のシステム条件を含みます。
3.3.2 Logcat ログ
アプリケーションの実行中に logcat ログを収集し、対応するポリシーの量を制御する必要性に注意を払い、定期的にクリーンアップします。
String cmd = "logcat -f " + file.getAbsolutePath();
Runtime.getRuntime().exec(cmd);
3.3.3 現在のプロセスの他のスレッドのスタック情報
各スレッド スタックを Java レイヤーから取得するか、リフレクションによって仮想マシン内の Dump スレッド スタックのインターフェイスを取得し、メモリ マップの関数アドレスでインターフェイスを強制的に呼び出し、データをローカル出力にリダイレクトします。
このように、ANR が発生した場合、メインスレッドの過去、現在、未来のスケジューリング情報、システム情報、スレッド情報、Logcat 情報など、参考になる情報が豊富にあります。
4 問題分析、位置づけ、解決
4.1 メインスレッドスケジューリングの分析
メイン スレッド タスクのスケジューリングをチェックして、ANR を誘発する明らかな時間のかかるタスク実行がないかどうかを確認します。
- 録音メッセージのスケジューリングの wallTime と cputime を分析する
- レコードの検索に時間がかかるメッセージ スタックには、検索に問題があります。
- ANR の原因にもなる、時間のかかる小さなメッセージが連続して大量に発生する可能性があることに注意してください。
4.2 ANR情報の解釈
4.2.1 システム負荷
Load: xx/xx/xx
ANR が発生する前の 1 分間、5 分間、15 分間の CPU 負荷。値は、システムがスケジュールするのを待っているタスクの数を表します。値が高すぎる場合は、システムが CPU に対して激しい競合を起こしていることを意味します。と IO、および私たちのアプリケーション プロセスが影響を受ける可能性があります。
4.2.2 CPU 使用率
CPU usage from 0ms to xxx ms later with xx% awake:
14% 1673/system_server: 8% user + 6.7% kernel / faults: 12746 minor
13% 30829/tv.danmaku.bili: 7.3% user + 6.2% kernel / faults: 24286 minor
6.6% 31147/tv.danmaku.bili:ijkservice: 3.7% user + 2.8% kernel / faults: 11880 minor
6% 574/logd: 2.1% user + 3.8% kernel / faults: 64 minor
..
TOTAL: 6.4% user + 8.2% kernel + 0% iowait + 0.6% irq + 0.1% softirq
CPU usage from xxms to xxxms later
73% 1673/system_server: 49% user + 24% kernel / faults: 1695 minor
33% 2330/AnrConsumer: 12% user + 21% kernel
15% 1683/HeapTaskDaemon: 15% user + 0% kernel
9.2% 7013/Binder:1673_12: 6.1% user + 3% kernel
6.1% 1685/ReferenceQueueD: 6.1% user + 0% kernel
6.1% 2715/HwBinder:1673_5: 6.1% user + 0% kernel
3% 2529/PhotonicModulat: 0% user + 3% kernel
25% 30829/tv.danmaku.bili: 4.2% user + 21% kernel / faults: 423 minor
25% 31050/thread_ad: 4.2% user + 21% kernel
...
...
27% TOTAL: 10% user + 14% kernel + 0.3% iowait + 0.9% irq + 0.3% softirq
上記のように、ANR の発生前後の CPU 使用率と、これらのプロセスの具体的な使用率を示します。
- ユーザー: ユーザーモード
- カーネル: カーネルの状態
- iowait: 待って。大きすぎると、ファイルの読み書きやメモリ不足が発生する場合があります。
- irq: ハード割り込み
- softirq: ソフト割り込みの割合
注: 単一プロセスの CPU の負荷は 100% に制限されるのではなく、いくつかのコアがあり、数百% あります (たとえば、8 コアの上限は 800% です)。
さらに、システムkswapd
クリティカル スレッドの CPU スレッドが大きすぎるため、多くの場合、システム リサイクル リソースを伴い、アプリケーション プロセスに影響を与えます。mmcqd
ANR 情報を解釈することで、ANR の問題をより適切かつ包括的に特定することができます。
4.3 ログキャットメッセージ
Logcat 印刷メッセージがオンラインで記録されている場合は、次の側面に注目して問題を確認してください。
onTrimeMemory
: 継続的な onTrimMemory は、多くの場合、APP のメモリが不十分であるか、ANR を引き起こすにはシステム リソースが不十分であることを示します。Slow operation
Slow delivery
これが発生すると、システムのパフォーマンスが制限されます。
5ノット
ANR の原因、ANR を処理するシステム、ANR を監視するアプリケーション層、ANR のアプリケーション側を包括的に監視するためのメインスレッドのタスクスケジューリング、および ANR を解決するためのシステム情報の収集から、ANR を解決するための一般的な考え方は、 ANR問題の分析と解決がついに与えられました。
ANR の問題については、これだけでは不十分です. ここでは一般的なフレームワークを示します. この記事が ANR の問題をより包括的に解決するのに役立つことを願っています.
さまざまなパフォーマンス監視の問題に応じて、さまざまなパフォーマンス最適化方法を採用する必要があります.現在、一部の人々はパフォーマンス最適化の途中でいくつかの最適化方法にあまり習熟していないため、パフォーマンス最適化の途中でさまざまな種類の最適化方法がすべて使用されます.起動最適化、メモリ最適化、ネットワーク最適化、フリーズ最適化、ストレージ最適化などを含むクラスの並べ替えは、「Android パフォーマンス最適化コア ナレッジ ポイント マニュアル」に統合されており、以下を参照できます。
「アプリ パフォーマンス チューニング アドバンスド マニュアル」:https://qr18.cn/FVlo89
スタートアップの最適化
メモリの最適化
UI の
最適化 ネットワークの最適化
ビットマップの最適化と画像圧縮の最適化
マルチスレッド同時実行の最適化とデータ転送効率の最適化
ボリューム パッケージの最適化