私は開発中のアプリのメモリリークを把握するために、できる限りのことを試して4日間を費やしましたが、かなり前に物事が意味を成しなくなりました。
私が開発しているアプリは社会的性質のものであるため、アクティビティ(P)のプロファイルを作成し、バッジ(B)などのデータを含むアクティビティをリストします。プロファイルからバッジリスト、他のプロファイル、他のリストなどに移動できます。
このようなフローを想像してください。P1-> B1-> P2-> B2-> P3-> B3など。一貫性を保つために、同じユーザーのプロファイルとバッジをロードしているため、各Pページは同じです。各Bページ。
問題の一般的な要点は次のとおりです。各ページのサイズに応じて少しナビゲートした後、ランダムな場所(ビットマップ、文字列など)でメモリ不足例外が発生し、一貫性がないようです。
なぜメモリ不足になっているのかを理解するために考えられるすべてを実行した後、何も思いつきませんでした。私が理解していないのは、Androidがロード時にメモリを使い果たし、代わりにクラッシュした場合、P1、B1などを殺さないことです。これらの以前のアクティビティは死んで復活するでしょう。 onCreate()およびonRestoreInstanceState()を介してそれらに戻る場合。
これはもちろん、P1-> B1->戻る-> B1->戻る-> B1を実行しても、クラッシュします。これは、ある種のメモリリークを示していますが、hprofをダンプしてMATとJProfilerを使用した後でも、特定することはできません。
Webからの画像の読み込みを無効にし(それを補ってテストを公平にするために読み込まれるテストデータを増やしました)、画像キャッシュがSoftReferencesを使用するようにしました。 Androidは、実際にそれが持っているいくつかのSoftReferencesを解放しようとしますが、メモリがクラッシュする直前です。
バッジページはWebからデータを取得し、BaseAdapterからEntityDataの配列にロードしてListViewにフィードします(実際にはCommonsWareの excellent MergeAdapter を使用していますが、このBadgeアクティビティには実際にありますとにかくアダプタは1つだけですが、どちらにしてもこの事実に言及したかったのです)。
私はコードを調べましたが、リークするものを見つけることができませんでした。見つけることができるものはすべてクリアし、nullにしました。System.gc()でさえ左右しましたが、それでもアプリはクラッシュしました。
私はまだスタック上の非アクティブなアクティビティが刈り取られない理由を理解していません、そして私は本当にそれを理解したいと思います。
この時点で、ヒント、アドバイス、解決策など、役立つものを探しています。
ありがとうございました。
私の場合、reallyがメモリの問題を解決したことの1つは、ビットマップのinPurgeableをtrueに設定することでした。 なぜBitmapFactoryのinPurgeableオプションを使用しないのですか? および詳細については回答の説明を参照してください。
Dianne Hackbornの答えとその後の議論(また、CommonsWareに感謝します)は、私が混乱していた特定の事柄を明確にするのに役立ちました。
スタック上にある非アクティブなアクティビティが刈り取られない理由がまだわかりません。それを理解したいのです。
これは物事の仕組みではありません。アクティビティのライフサイクルに影響する唯一のメモリ管理は、Androidが実行中であると判断するため、すべてのプロセスにわたるglobalメモリです。メモリが不足しているため、バックグラウンドプロセスを終了して元に戻す必要があります。
アプリケーションがより多くのアクティビティを開始するフォアグラウンドにある場合、バックグラウンドに移行することはないため、システムがプロセスを強制終了する前に、常にローカルプロセスのメモリ制限に達します。 (そして、プロセスを強制終了すると、現在フォアグラウンドにあるものを含めて、アクティビティをホストしているプロセスallを強制終了します。
あなたの基本的な問題は私に聞こえます:あなたは同時に多くのアクティビティを実行させている、および/またはそれらのアクティビティのそれぞれはあまりにも多くのリソースを保持しています。
任意の数の潜在的に重いアクティビティを積み重ねることに依存しないように、ナビゲーションを再設計する必要があります。 onStop()で大量の処理(setContentView()を呼び出してアクティビティのビュー階層をクリアし、保持している可能性のある他の変数をクリアするなど)を行わない限り、メモリ不足になります。
新しいFragment APIを使用して、この任意のアクティビティスタックを、メモリをより厳密に管理する単一のアクティビティに置き換えることを検討できます。たとえば、フラグメントのバックスタック機能を使用する場合、フラグメントがバックスタックに移動して表示されなくなると、onDestroyView()メソッドが呼び出されてビュー階層が完全に削除され、フットプリントが大幅に削減されます。
今、あなたが押し戻す、アクティビティに行く、押し戻す、別のアクティビティに行くなどのフローでクラッシュし、深いスタックを持っていない限り、はい、あなたはリークを持っています。このブログ投稿では、リークをデバッグする方法について説明します。 http://Android-developers.blogspot.com/2011/03/memory-analysis-for-Android.html
いくつかのヒント:
アクティビティコンテキストがリークしていないことを確認してください。
ビットマップの参照を保持しないようにしてください。 Activity#onStopのImageViewをすべて削除します。次のようなものです。
Drawable d = imageView.getDrawable();
if (d != null) d.setCallback(null);
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
不要になったビットマップはリサイクルしてください。
Memory-lruなどのメモリキャッシュを使用する場合は、メモリを多く使用していないことを確認してください。
画像が大量のメモリを消費するだけでなく、他のデータをメモリに残しすぎないようにしてください。これは、アプリに無限のリストがある場合に簡単に起こります。データをデータベースにキャッシュしてみてください。
On Android 4.2、ハードウェアアクセラレーションによる bug(stackoverflow#13754876) があるため、hardwareAccelerated=true
マニフェストでメモリリークが発生します。 GLES20DisplayList
-ステップ(2)を実行し、他の誰もこのビットマップを参照していない場合でも、参照を保持し続けます。ここに必要なもの:
a)API 16/17のハードウェアアクセラレーションを無効にします。
または
b)ビットマップを保持しているビューをデタッチします
Android 3+の場合、Android:largeHeap="true"
はAndroidManifest
にあります。しかし、それはあなたの記憶の問題を解決するのではなく、ただ延期するだけです。
無限のナビゲーションなどが必要な場合は、フラグメントを選択する必要があります。したがって、1つのアクティビティがあり、フラグメントを切り替えるだけです。この方法では、4番などのメモリの問題も解決します。
Memory Analyzerを使用して、メモリリークの原因を見つけます。
こちらは非常に優れたビデオです Google I/O 2011:Android Apps
ビットマップを扱う場合、これは必読です: ビットマップを効率的に表示する
多くの場合、ビットマップはAndroidのメモリエラーの原因であるため、再確認するのに適しています。
各アクティビティへの参照を保持していますか?知る限り、これがAndroidがスタックからアクティビティを削除しないようにする理由です。
他のデバイスでもこのエラーを再現できますか? Androidやハードウェアのメーカーに応じて、いくつかのROMデバイスの奇妙な動作を経験しました。
問題は、答えでここで述べられている多くの要因の組み合わせかもしれないと思います。 @Timが言ったように、アクティビティまたはそのアクティビティの要素への(静的な)参照により、GCがアクティビティをスキップする可能性があります。 ここ は、このファセットについて説明している記事です。おそらく問題は、アクティビティを「可視プロセス」状態以上に保つことから生じると思います。これにより、アクティビティとそれに関連するリソースが回収されないことがほぼ保証されます。
私はサービスでしばらく前に反対の問題を経験したので、これが私に考えさせられました:あなたのアクティビティをプロセス優先リストで高く保ち、システムGCの影響を受けないようにするものがあります参照(@Tim)またはループ(@Alvaro)。ループは、無限の項目や長時間実行される項目である必要はありません。再帰的なメソッドやカスケードループ(またはこれらの行に沿ったもの)によく似たものです。
EDIT:これを理解すると、onPauseとonStopは必要に応じてAndroidによって自動的に呼び出されます。メソッドは主にオーバーライドするためにあり、ホスティングプロセスが停止する前に必要なことを処理できます(変数の保存、状態の手動保存など)。ただし、onStop(onDestroyとともに)がごとに呼び出されないことが明確に述べられていることに注意してください場合。さらに、ホストプロセスが「Forground」または「Visible」ステータスのアクティビティ、サービスなどもホストしている場合、OSはプロセス/スレッドの停止を見ることもありません。例:アクティビティとサービスの両方が同じプロセスで処理され、サービスがonStartCommand()
から_START_STICKY
_を返す場合、プロセスは自動的に少なくとも表示状態になります。それがここでのキーかもしれません。Activityの新しいprocを宣言して、それが何かを変更するかどうか確かめてください。この行をマニフェストのアクティビティの宣言に_Android:process=":proc2"
_として追加し、アクティビティが他のプロセスとプロセスを共有している場合はテストを再度実行してください。ここでの考えは、アクティビティをクリーンアップし、pretty問題がアクティビティではないことを確信している場合、他の何かが問題であり、そのための狩りの時間であるということです。
また、どこで見たのかも思い出せません(Android docs)で見た場合でも)、アクティビティを参照するPendingIntent
についての何かを覚えていますこのように振る舞います。
ここ は、プロセスの非殺人の最前線に関するいくつかの洞察を持つonStartCommand()
ページへのリンクです。
私が見つけたメモリリークの最大の原因は、コンテキストに対するグローバルな、高レベルまたは長年の参照が原因でした。変数に格納された「コンテキスト」をどこかに保存している場合、予測できないメモリリークが発生する可能性があります。
だから、私が本当に考えることができるのは、コンテキストを直接または間接的に参照する静的変数がある場合だけです。アプリケーションの一部への参照としてのようなものでさえ。私はあなたがすでにそれを試したことがあると確信していますが、念のため、onDestroy()内のすべての静的変数をnullにしてみてください。
コンテキストが必要なものにgetApplicationContext()を渡してみてください。アクティビティへの参照を保持し、アクティビティがガベージコレクションされないようにするグローバル変数がある場合があります。
私はあなたと同じ問題に遭遇しました。私はインスタントメッセージングアプリで作業していました。同じ連絡先に対して、ChatActivityでProfileActivityを開始することができ、その逆も可能です。別のアクティビティを開始するインテントに余分な文字列を追加するだけで、スターターアクティビティのクラスタイプとユーザーIDの情報を取得します。たとえば、ProfileActivityはChatActivityを開始し、ChatActivity.onCreateで呼び出し側クラスタイプ「ProfileActivity」とユーザーIDをマークします。アクティビティを開始する場合は、ユーザーの「ProfileActivity」であるかどうかを確認します。その場合、「finish()」を呼び出して、新しいプロファイルを作成するのではなく、以前のProfileActivityに戻ります。メモリリークは別の問題です。