web-dev-qa-db-ja.com

Androidバックスタックのフラグメントがメモリを大量に消費している

問題:

私はAndroidアプリケーションを使用して、ユーザーがユーザーのプロファイルを参照できるようにしますViewProfileFragment。内部ViewProfileFragmentで、ユーザーは彼に移動する画像をクリックできますStoryViewFragmentさまざまなユーザーの写真が表示される場所です。ユーザーのプロフィール写真をクリックすると、新しいユーザーのプロフィールでViewProfileFragmentの別のインスタンスに移動できます。ユーザーが繰り返しクリックした場合ユーザーのプロファイル、ギャラリーに移動する画像をクリックしてから、別のプロファイルをクリックすると、フラグメントがメモリにすばやく蓄積され、恐ろしいOutOfMemoryErrorが発生します。ここに、私が説明している図のフローを示します。

UserAがBobのプロファイルをクリックします。 Bobのプロフィールの内側UserAがImageAをクリックして、さまざまなユーザー(Bobを含む)の写真のギャラリーに移動します。 UserAはスーのプロファイルをクリックし、次に彼女の画像の1つをクリックします-プロセスの繰り返しなど.

UserA -> ViewProfileFragment
         StoryViewFragment -> ViewProfileFragment
                               StoryViewFragment -> ViewProfileFragment

典型的なフローからわかるように、バックスタックにはViewProfileFragmentStoryViewFragmentのインスタンスが多数積み上げられています。

関連コード

私はこれらを次のロジックでフラグメントとしてロードしています:

//from MainActivity
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.activity_main_content_fragment, fragment, title);
ft.addToBackStack(title);

試してみたこと

1)FragmentTransactionが発生したときにreplaceメソッドがトリガーされるように、特にonPausereplaceを使用しています。内部onPauseは、フラグメントがでない場合に、できる限り多くのリソースを解放しようとしています(ListViewアダプタのデータをクリアする、変数を「ヌルにする」など)。アクティブなフラグメントであり、バックスタックにプッシュされると、より多くのメモリが解放されます。しかし、リソースを解放するための私の努力は、部分的な成功にすぎません。 MATによると、私はGalleryFragmentViewProfileFragmentによって消費される多くのメモリをまだ持っています。

2)また、addToBackStack()の呼び出しも削除しましたが、戻ることはできないため、ユーザーエクスペリエンスが低下します(ユーザーが[戻る]ボタンを押すとアプリが閉じるだけです)。

3)MATを使用して多くのスペースを占めるすべてのオブジェクトを検索し、onPause(およびonResume)メソッド内でさまざまな方法でそれらを解放して解放しましたリソースですが、それらはまだかなりのサイズです。

enter image description here

4)また、両方のフラグメントのonPauseにforループを記述し、次のロジックを使用して、すべてのImageViewsをnullに設定しました。

 for (int i=Shell.getHeaderViewCount(); i<Shell.getCount(); i++) {

     View h = Shell.getChildAt(i);
     ImageView v = (ImageView) h.findViewById(R.id.galleryImage);
       if (v != null) {
           v.setImageBitmap(null);
       }
  }

myListViewAdapter.clear()

[〜#〜]質問[〜#〜]

1)Fragmentをバックスタックに残しておく方法を見落としているのですが、.replace(fragment)のサイクルがすべてのメモリを使い果たしないように、そのリソースを解放する方法も見逃していますか?

2)多くのフラグメントがバックスタックにロードされることが予想される場合の「ベストプラクティス」は何ですか?開発者はこのシナリオをどのように正しく処理しますか? (または、私のアプリケーションのロジックは本質的に欠陥があり、私はそれを間違っているだけですか?)

これに対する解決策をブレインストーミングする際の助けがあれば、大歓迎です。

22
Max Worg

フラグメントは、親アクティビティと同じライフサイクルを共有していることがわかります。 フラグメントのドキュメント によると:

フラグメントは常にアクティビティに埋め込まれている必要があり、フラグメントのライフサイクルはホストアクティビティのライフサイクルに直接影響されます。たとえば、アクティビティが一時停止すると、その中のすべてのフラグメントも停止し、アクティビティが破棄されると、すべてのフラグメントも停止します。ただし、アクティビティの実行中(ライフサイクルが再開された状態)は、各フラグメントを個別に操作できます。

そのため、フラグメントのonPause()の一部のリソースをクリーンアップするために実行したステップは、親アクティビティが一時停止しない限りトリガーされません。親アクティビティによって読み込まれているフラグメントが複数ある場合は、アクティブなフラグメントを切り替えるために、ある種のメカニズムを使用している可能性があります。

onPauseに依存せずに、フラグメントの setUserVisibleHint をオーバーライドすることで、問題を解決できる場合があります。これは、フラグメントが表示されたり表示されなくなったりしたときに(たとえば、FragmentAからFragmentBに切り替わるPagerAdapterがある場合など)、リソースのセットアップまたはリソースのクリーンアップをどこで行うかを決定するのに適した場所です。

public class MyFragment extends Fragment {
  @Override
  public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
      //you are visible to user now - so set whatever you need 
      initResources();
    }
    else { 
     //you are no longer visible to the user so cleanup whatever you need
     cleanupResources();
    }
  }
}

すでに述べたように、バックスタックにアイテムを積み上げているので、メモリフットプリントは少なくとも少しはあると予想されますが、上記の手法でフラグメントが見えなくなったときにリソースをクリーンアップすることで、フットプリントを最小限に抑えることができます。

もう1つの提案は、メモリアナライザツール( [〜#〜] mat [〜#〜] )の出力と一般的なメモリ分析を理解することです。 これは良い出発点です 。 Androidでメモリをリークするのは本当に簡単です。そのため、私の考えでは、コンセプトとメモリがどのようにしてあなたから離れることができるかを理解することが必要です。あなたの問題はあなたが原因ではない可能性がありますフラグメントが見えなくなったときにリソースを解放するおよびある種のメモリリークがあるため、setUserVisibleHintリソースのクリーンアップをトリガーしても大量のメモリが使用されている場合は、メモリリークが原因である可能性があるため、両方を除外してください。

7
radiovisual

ソースコードに具体的にアクセスすることなしに、全体像(多くの情報を提供してきたとしても)を確認することは困難です。不可能ではないとしても、実用的ではないと私は確信しています。

そうは言っても、Fragmentsを操作する際に注意すべき点がいくつかあります。最初の免責事項。

Fragmentsが導入されたとき、それらは常に最高のアイデアのように聞こえました。 複数のアクティビティを同時に表示できるので、ちょっと。それがセールスポイントでした。

したがって、全世界はゆっくりとフラグメントを使い始めました。それはブロックの新しい子供でした。誰もがフラグメントを使用していました。 Fragmentsを使用していなかった場合は、「間違っていた」可能性があります。

数年後にはアプリが登場し、(ありがたいことに)より多くのアクティビティに断片化する傾向が(ありがたいことに)戻っています。これは、新しいAPI(Transition APIなどで見られるように、ユーザーが実際に気付くことなくアクティビティ間を遷移する機能)によって実施されます。

したがって、要約すると:私はフラグメントが嫌いです。私はそれが最悪の1つであると信じていますAndroid実装の中で最も人気があるのは、アクティビティ間に(現在のように)移行フレームワークがないために人気を得ただけです。フラグメントのライフサイクルは、何でも、予期したときに呼び出されることが保証されていないランダムなコールバックのボール。

(わかりました、少し誇張していますが、Android経験豊富な開発者に、フラグメントで問題が発生したかどうかを聞いてみてください。そうすれば、答えは非常によくなります)。

言われているすべてのことで、フラグメントは機能します。そしてあなたの解決策はうまくいくはずです。

それでは、これらのハード参照を保持できる人物/場所を調べてみましょう。

:これをデバッグする方法について、ここでアイデアを投げますが、直接的な解決策を提供することはおそらくありません。参考にしてください。

WHAT IS GOING ON?:あなたはバックスタックにフラグメントを追加しています。バックスタックはhardFragmentへの参照であり、weakまたはsoftではない( source

今、誰がバックスタックを保管していますか? FragmentManagerおよび…ご想像のとおり、ハードライブ参照も使用します( source )。

そして最後に、各アクティビティにはFragmentManagerへのハード参照が含まれています。

つまり、アクティビティが終了するまで、そのフラグメントへのすべての参照がメモリに存在します。 Fragment Managerレベル/バックスタックで発生した追加/削除操作に関係なく。

どうすればよいですか?いくつかのことが頭に浮かびます。

  1. Picasso のような単純な画像ローダー/キャッシュライブラリを使用して、画像がリークされていないことを確認してください。独自の実装を使用する場合は、後で削除できます。そのすべての欠点について、ピカソは本当に簡単に使用でき、メモリを「正しい方法」で処理する状態になりました。

  2. 画像から「ビットマップをリークしている可能性があります」の問題を取り除いた後(しゃれは意図されていません!)、フラグメントのライフサイクルを再検討するときが来ました。フラグメントをバックスタックに配置しても、フラグメントは破棄されませんが、リソースをクリアする機会があります。Fragment#onDestroyView()が呼び出されます。そして、ここで、フラグメントがすべてのリソースを無効にすることを確認します。

フラグメントがsetRetainInstance(true)を使用しているかどうかは言及しません。アクティビティが破棄/再作成されたときにこれらが破棄/再作成されないため(例:ローテーション)、すべてのビューがリークされる可能性があるため、注意してください適切に処理されていません。

  1. 最後に、これは診断が困難です。おそらく、アーキテクチャを再検討したいでしょう。同じフラグメント(viewprofile)を複数回起動している場合は、代わりに、同じインスタンスを再利用して「新しいユーザー」をロードすることを検討してください。ユーザーのリストをロード順に追跡することでバックスタックを処理できるため、onBackPressedをインターセプトしてスタックを「下」に移動できますが、ユーザーがナビゲートするときに常に新しい/古いデータをロードします。 StoryViewFragmentも同様です。

全体として、これらはすべて私の経験からもたらされた提案ですが、詳細を確認できない限り、あなたを助けることは本当に難しいです。

うまくいけば、それは出発点であることがわかります。

幸運を祈ります。

19