Androidアプリのメモリ不足の問題-すべて試しましたが、それでも途方に暮れています


87

開発中のアプリのメモリリークを把握するために4日間かけてすべてのことを試してみましたが、物事はずっと前に意味をなさなくなりました。

私が開発しているアプリはソーシャルな性質のものなので、アクティビティー(P)をプロファイルし、アクティビティーをデータ付きでリストします(例:バッジ(B))。プロファイルからバッジリスト、他のプロファイル、他のリストなどに移動できます。

したがって、このようなフローを想像してみてください。P1-> B1-> P2-> B2-> P3-> B3など。各Bページ。

問題の概要は次のとおりです。各ページのサイズに応じて少しナビゲートした後、ランダムな場所(ビットマップ、文字列など)でメモリ不足の例外が発生し、一貫性がないようです。

なぜ私がメモリ不足に陥っているのかを理解するために考えられるすべてのことをした後、私は何も思いつきませんでした。理解できないのは、AndroidがP1、B1などをロード時にメモリ不足でクラッシュしてクラッシュした場合に、P1、B1などを強制終了しない理由です。onCreate()およびonRestoreInstanceState()を介してこれらに戻ると、これらの以前のアクティビティが終了し、復活することを期待します。

これは言うまでもなく、P1-> B1->戻る-> B1->戻る-> B1を実行しても、クラッシュが発生します。これはある種のメモリリークを示していますが、hprofをダンプしてMATおよびJProfilerを使用した後でも、正確に特定することはできません。

Webからの画像の読み込みを無効にし(そして、それを補ってテストを公正にするために読み込まれるテストデータを増やしました)、画像キャッシュがSoftReferencesを使用することを確認しました。Androidは実際にはいくつかのSoftReferencesを解放しようとしますが、メモリ不足になる直前です。

バッジページはWebからデータを取得し、BaseAdapterからEntityDataの配列にロードして、それをListViewにフィードします(私は実際にはCommonsWareの優れたMergeAdapterを使用していますが、このバッジアクティビティでは、とにかく1 つしかアダプターがありませんが、いずれにせよこの事実に言及したかった)。

私はコードを調べましたが、リークするものを見つけることができませんでした。見つけることができるすべてのものをクリアしてnullにし、System.gc()でさえ左右に移動しましたが、それでもアプリがクラッシュします。

スタック上にある非アクティブなアクティビティが取得されない理由はまだわかりません。それを理解したいのですが。

この時点で、ヒント、アドバイス、解決策など、役立つものを探しています。

ありがとうございました。


それで、過去20秒間に15のアクティビティを開いた場合(ユーザーが非常に速く通過している場合)、それは問題でしょうか?アクティビティが表示された後にアクティビティをクリアするには、どのコード行を追加する必要がありますか?私が取得outOfMemoryエラー。ありがとう!
Ruchir Baronia

回答:


109

スタック上にある非アクティブなアクティビティが取得されない理由はまだわかりません。それを理解したいのですが。

これは物事が機能する方法ではありません。アクティビティのライフサイクルに影響を与える唯一のメモリ管理は、Androidがメモリが不足していると判断し、バックグラウンドプロセスを終了して一部を取り戻す必要があるため、すべてのプロセスのグローバルメモリです。

アプリケーションがフォアグラウンドに置かれ、ますます多くのアクティビティを開始する場合、アプリケーションはバックグラウンドに移行しないため、システムがプロセスを強制終了する前に、常にローカルプロセスのメモリ制限に達します。(そして、プロセスを強制終了すると、現在フォアグラウンドにあるものを含め、すべてのアクティビティーをホストているプロセスを強制終了します。)

つまり、基本的な問題のように聞こえます。同時に実行しているアクティビティが多すぎる、またはこれらの各アクティビティが保持しているリソースが多すぎます。

ナビゲーションを再設計するだけで、ヘビーウェイトになる可能性のある任意の数のアクティビティを積み重ねることに依存しなくなります。onStop()で大量の処理(setContentView()を呼び出してアクティビティのビュー階層をクリアしたり、アクティビティが保持している他の変数をクリアしたりするなど)を行わない限り、メモリが不足するだけです。

新しいフラグメントAPIを使用して、この任意のアクティビティスタックを、より厳密にメモリを管理する単一のアクティビティに置き換えることを検討してください。たとえば、フラグメントのバックスタック機能を使用している場合、フラグメントがバックスタックに移動して表示されなくなると、onDestroyView()メソッドが呼び出されてビュー階層が完全に削除され、フットプリントが大幅に削減されます。

ここで、押し戻す、アクティビティに移動する、押し戻す、別のアクティビティに移動するなどのフローでクラッシュし、深いスタックがない場合は、リークがあります。このブログ投稿では、リークをデバッグする方法について説明しています。http//android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
OPの弁護では、これはドキュメントが示唆するものではありません。引用developer.android.com/guide/topics/fundamentals/…「(アクティビティが停止すると)ユーザーには見えなくなり、メモリが他の場所で必要になったときにシステムによって強制終了される可能性があります。」「アクティビティが一時停止または停止した場合、システムはそれをメモリから削除できます...終了を要求する(finish()メソッドを呼び出す)ことにより」「(onDestroy()が呼び出されます)システムはこのインスタンスを一時的に破棄しているためですスペースを節約するための活動です。」
CommonsWare 2011

42
同じページで、「ただし、システムがメモリを回復するためにアクティビティを破棄した場合」。あなたが示しているのは、Android がメモリを解放するためにアクティビティを破壊することは決してなく、そうするためにプロセスを終了するだけであることです。その場合、Androidはメモリを解放するためにアクティビティを破棄することを繰り返し示唆しているため、このページは深刻な書き換えが必要です。また、引用部分の多くはActivityJavaDocsにも存在することに注意してください。
CommonsWare、2011

6
私はこれをここに置いたと思いましたが、このドキュメントの更新リクエストを投稿しました:code.google.com/p/android/issues/detail?id
Justin Breitfeller

15
それは2013年であり、ドキュメントは誤った点をより明確に提示するようになっただけです:developer.android.com/training/basics/activity-lifecycle/…、「アクティビティが停止すると、回復する必要がある場合、システムがインスタンスを破壊する可能性がありますシステムメモリ。
極端な

2
まあ、がらくた。システムはプロセスで停止したアクティビティを管理し、必要に応じてそれらをシリアル化および逆シリアル化(破棄および作成)するだろうと(ドキュメントのせいで)常に考えていました。
dcow 2013年

21

いくつかのヒント:

  1. アクティビティコンテキストをリークしていないことを確認してください。

  2. ビットマップの参照を保持しないようにしてください。次のようなActivity#onStopのすべてのImageViewをクリーンアップします。

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
  3. 不要になったビットマップはリサイクルしてください。

  4. memory-lruなどのメモリキャッシュを使用する場合は、メモリをあまり使用していないことを確認してください。

  5. 画像はメモリを大量に消費するだけでなく、メモリに他のデータを多く保存しすぎないようにしてください。アプリに無限のリストがある場合、これは簡単に起こります。データベースにデータをキャッシュしてみてください。

  6. Android 4.2では、ハードウェアアクセラレーションにバグ(stackoverflow#13754876)があるためhardwareAccelerated=true、マニフェストで使用するとメモリリークが発生します。GLES20DisplayList-ステップ(2)を実行し、他の誰もこのビットマップを参照していない場合でも、参照を保持し続けます。ここにあなたが必要です:

    a)API 16/17のハードウェアアクセラレーションを無効にします。
    または
    b)ビットマップを保持するビューを切り離す

  7. Android 3以降の場合は、で使用android:largeHeap="true"してみてくださいAndroidManifest。しかし、それはあなたの記憶の問題を解決せず、ただ延期するだけです。

  8. 無限のナビゲーションなどが必要な場合は、フラグメント-を選択する必要があります。したがって、フラグメントを切り替えるだけの1つのアクティビティがあります。このようにして、番号4などのいくつかのメモリの問題も解決します。

  9. メモリアナライザーを使用して、メモリリークの原因を見つけます。
    これは、Google I / O 2011からの非常に優れたビデオです。Androidアプリのメモリ管理
    ビットマップを扱う場合、これは必読です:ビットマップを効率的に表示する


それで、過去20秒間に15のアクティビティを開いた場合(ユーザーが非常に速く通過している場合)、それは問題でしょうか?アクティビティが表示された後にアクティビティをクリアするには、どのコード行を追加する必要がありますか?私が取得outOfMemoryエラー。ありがとう!
Ruchir Baronia

4

多くの場合、ビットマップはAndroidのメモリエラーの原因であるため、再確認するのに適しています。


それで、過去20秒間に15のアクティビティを開いた場合(ユーザーが非常に速く通過している場合)、それは問題でしょうか?アクティビティが表示された後にアクティビティをクリアするには、どのコード行を追加する必要がありますか?私が取得outOfMemoryエラー。ありがとう!
Ruchir Baronia

2

各アクティビティへの参照を保持していますか?これは、Androidがスタックからアクティビティを削除できない理由です。

他のデバイスでもこのエラーを再現できますか?ROMやハードウェアの製造元に応じて、一部のAndroidデバイスで奇妙な動作が発生しました。


これは、最大ヒープが16MBに設定されたCM7を実行しているDroidで再現できました。これは、エミュレーターでテストしているのと同じ値です。
Artem Russakovskii

あなたは何かに夢中になっているかもしれません。2番目のアクティビティが開始すると、最初のアクティビティはonPause-> onStopを実行しますか、それとも単にonPauseを実行しますか?すべてのon *******ライフサイクル呼び出しを出力しているので、onStopなしでonPause-> onCreateが表示されています。そして、クラッシュダンプの1つは、実際には、3つのアクティビティが強制終了していたため、onPause = trueまたはonStop = falseのようなものを言っていました。
Artem Russakovskii

OnStopは、アクティビティが画面を離れたときに呼び出す必要がありますが、システムによって早期に回収された場合は呼び出されない場合があります。
2011

クリックしてもonCreateとonRestoreInstanceStateが呼び出されないので、再利用されません。
Artem Russakovskii

ライフサイクルに応じたものかもしれないが、公式の開発者のブログへのリンクを持っている私の答えをチェックし、けれども呼ばれることはありません、それは以上の可能性があなたの周りのあなたのビットマップを渡している方法です
DTEN

2

問題はおそらく、回答でここに述べられている多くの要因の組み合わせが問題を与えているものだと思います。@Timが言ったように、アクティビティまたはそのアクティビティ内の要素への(静的)参照は、GCがアクティビティをスキップする原因となる可能性があります。ここではこのファセットを議論する記事です。考えられる問題は、アクティビティを「Visible Process」状態以上に維持することから発生すると考えられます。これにより、アクティビティとその関連リソースが再利用されないことがほぼ保証されます。

私はしばらく前にサービスで反対の問題を経験したので、これが私がこの考えに取り掛かった理由です。たとえば、次のように、システムのGCの影響を受けないように、プロセスの優先順位リストでアクティビティを高く保つ何かがあります。参照(@Tim)またはループ(@Alvaro)。ループは、無限または長時間実行されるアイテムである必要はなく、再帰メソッドまたはカスケードループ(またはそれらの行に沿った何か)によく似たものである必要があります。

編集:私がこれを理解しているように、onPauseとonStopはAndroidによって必要に応じて自動的に呼び出されます。ホスティングプロセスが停止する前に必要な処理(変数の保存、手動での状態の保存など)を行うことができるように、メソッドは主にオーバーライドするためにあります。ただし、onStopが(onDestroyとともに)呼び出されない場合があることは明確に述べられていることに注意してください。さらに、ホスティングプロセスが「フォアグラウンド」または「可視」ステータスのアクティビティやサービスなどもホストしている場合、OSはプロセス/スレッドの停止を監視しないこともあります。例えば:活動及びサービスが両方とも同じプロセスでluanchedとサービスリターンされるSTART_STICKYからonStartCommand()プロセスは自動的に少なくとも目に見えるステータスを取ります。それがここでの鍵となる可能性があります。アクティビティの新しいプロシージャを宣言して、それが何か変更されるかどうかを確認してください。マニフェストのアクティビティの宣言に次の行を追加してみてください。アクティビティがandroid:process=":proc2"他のプロセスとプロセスを共有している場合は、テストを再度実行してください。ここでの考えは、アクティビティをクリーンアップし、問題が自分のアクティビティではないことをかなり確信している場合、他の何かが問題であり、そのためにハンターする時間だということです。

また、どこで見たのか思い出せません(Androidドキュメントで見た場合でも)がPendingIntent、アクティビティを参照するとアクティビティがこのように動作する可能性があることを覚えています。

こちらのリンクであるonStartCommand()プロセス非殺傷前にいくつかの洞察力を持つページが。


あなたが提供したリンクに基づいて(ありがとう)、アクティビティのonStopが呼び出されている場合はアクティビティを強制終了できますが、私の場合、何かがonStopの実行を妨げていると思います。その理由を詳しく調べます。ただし、onPauseのみのアクティビティも強制終了できることも記載されています。これは、私の場合は発生していません(onPauseは呼び出されますが、onStopは呼び出されません)。
Artem Russakovskii

1

だから私が本当に考えることができる唯一のことは、コンテキストを直接または間接的に参照する静的変数があるかどうかです。アプリケーションの一部への参照と同じくらいでも。私はあなたがすでにそれを試したと確信していますが、念のために提案します。ガベージコレクターがそれを確実に取得するために、onDestroy()ですべての静的変数をnullにしてみてください


1

私が見つけたメモリリークの最大の原因は、コンテキストへのグローバル、高レベル、または長期にわたる参照が原因でした。「コンテキスト」を変数のどこかに格納しておくと、予期しないメモリリークが発生する可能性があります。


ええ、私は何度もこれを聞いて読みましたが、それを突き止めるのに苦労しました。それらを追跡する方法を確実に理解できたとしたら。
Artem Russakovskii

1
私の場合、これは、コンテキストに等しく設定されたクラスレベルの変数に変換されました(例:Class1.variable = getContext();)。一般に、アプリでの「コンテキスト」のすべての使用法を「getContext」などの新しい呼び出しで置き換えると、最大のメモリ問題が解決しました。しかし私のものは不安定で不安定で、あなたの場合のように予測できなかったので、おそらく何か違うものでした。
Dirk Conrad Coetsee、2011

1

コンテキストが必要なものにgetApplicationContext()を渡してみてください。アクティビティへの参照を保持し、それらがガベージコレクションされないようにするグローバル変数がある可能性があります。


1

私のケースでメモリの問題を本当に助けたものの1つは、ビットマップでinPurgeableをtrueに設定することでした。BitmapFactoryのinPurgeableオプションを使用しない理由を参照してください詳細については、回答のディスカッションをご覧ください。

ダイアンハックボーンの回答とその後のディスカッション(これもCommonsWareに感謝します)は、私が混乱していた特定のことを明確にするのに役立ちました。ありがとうございました。


これは古いトピックであることはわかっていますが、多くの検索でこのスレッドが見つかるようです。私は、私はそんなにあなたがここで見つけることができ、そこから学んだ偉大なチュートリアルをリンクさせたい:developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

私はあなたと同じ問題に遭遇しました。私はインスタントメッセージングアプリに取り組んでいましたが、同じ連絡先に対して、ChatActivityでProfileActivityを開始でき、その逆も可能です。別のアクティビティを開始するインテントに文字列を追加するだけで、スターターアクティビティのクラスタイプとユーザーIDの情報を取得します。たとえば、ProfileActivityはChatActivityを開始し、ChatActivity.onCreateで、呼び出し元クラスタイプ「ProfileActivity」とユーザーIDをマークします。アクティビティを開始する場合は、ユーザーの「ProfileActivity」かどうかを確認します。その場合は、「finish()」を呼び出して、新しいプロファイルを作成する代わりに、以前のProfileActivityに戻ります。メモリリークは別のものです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.