ViewPagerで表示させているFragmentのメモリ解放がうまくできていない?
ViewPagerでギャラリーアプリのようなものを作っています。
写真はVolleyを用いてネットから取得しているのですが、
例えば横縦横縦・・・と並んでいる写真を7枚ほど閲覧するとメモリ不足になり写真が表示されなくなります。
現状以下のコードをFragment#onDestroyView
のタイミングで呼び出し画像のメモリ解放を行っているのですが、他に対策などありましたら教えて欲しいです。
/**
* Viewを再帰的に探索して保持しているデータを削除
*
* @param view FragmentのrootViewなど
*/
protected static void cleanupView(View view) {
if (view instanceof PhotoView) {
PhotoView photoView = (PhotoView) view;
photoView.setImageBitmap(null);
//もしタグが残っていたらキャンセル処理をさせておく
ImageLoader.ImageContainer imageContainer = (ImageLoader.ImageContainer) photoView.getTag();
if (imageContainer != null) {
imageContainer.cancelRequest();
photoView.setTag(null);
}
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
cleanupView(viewGroup.getChildAt(i));
}
}
}
PhotoViewer(extends ViewPager)を配置したPhotoViewerFragmentのレイアウト
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.sakaguchi.fragment.PhotoViewerFragment">
<com.example.sakaguchi.view.PhotoViewer
android:id="@+id/photo_viewer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
PhotoViewerで表示させているPhotoFragmentのレイアウト
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.sakaguchi.fragment.PhotoFragment">
<com.example.sakaguchi.view.ScalingPhotoView
android:id="@+id/photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<com.example.sakaguchi.view.PhotoViewerToolBar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="bottom"
android:background="@color/half_transparent_black">
<com.example.sakaguchi.view.AddCartButton
android:id="@+id/add_cart"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/add_cart_button_for_photo"
android:gravity="center"
android:paddingLeft="30dp"
android:paddingRight="30dp"
android:text="@string/add_cart"
android:textColor="@color/orange"
android:textSize="15sp"/>
<TextView
android:id="@+id/photo_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:gravity="center"
android:textColor="@android:color/white"/>
</com.example.sakaguchi.view.PhotoViewerToolBar>
</FrameLayout>
追記
クラッシュした時のヒープダンプをMATで調べてみるとbyte[]が一番メモリを消費しているようです。
画像のメモリ解放処理が甘いのでしょうか?
追記2
@yamacraftさんのおっしゃる通りViewPagerのキャッシュ指定は特にしていないのでデフォルトの1だと思います。
Fragmentの参照が生きているのでは?という話ですが、自パッケージのオブジェクトについて見てみると、そのFragment
に配置しているAddCartButton
が5つ・ScalingPhotoView
のオブジェクトが3つ残っているなどFragmentの参照がいくらか残っているようです。
ですが、以下のページによるとViewPagerは今と左右の3ページ分保持する仕様のようなので、1ページ1つのAddCartButton
が5つ残っているのは気になりますが、そのFragment
の参照は問題ではないような気がします。
FragmentPagerAdapterとListFragmentを使ったらはまった – unsweets.log
ちなみにその時の自パッケージのオブジェクトShallow HeapTOP3は以下の通りでした。
1. AddCartButton
- 3,160Byte (オブジェクト数:5)
2. PhotoViewerToolBar
- 1,536Byte (オブジェクト数:3)
3. ScalingPhotoView
- 1,392Byte (オブジェクト数:3)
追記3
横縦横縦・・・の写真を閲覧していて6枚目で急にローディングに少し時間がかかり、7枚目は他と変わらないものの、8枚目はメモリ不足になります。
再起動した後などは使用できるメモリがいくらか多かったのか8枚目も見れて9枚目でメモリ不足になったこともありました。
ですが、例えば以下のパターンの場合は多少増減はするものの大した変化は見られず、メモリ不足にならずに50枚全てを閲覧することができます。
写真1 縦向き 8枚
写真2 縦向き 8枚
写真3 縦向き 8枚
写真4 横向き 8枚
写真5 横向き 8枚
写真6 横向き 2枚
写真番号ごとに同じ見た目ですが、50枚それぞれ読み込みURLは異なっています。
50枚目を閲覧した後のメモリダンプを調べてみたところ、byte[]のShallow Heapがメモリ不足になった時の2/3ほどでした。
自作の画像を表示させているViewのオブジェクト数などは、50枚閲覧した方はViewPagerの前のListViewで使っている画像を表示させているViewがないこと以外は変わっていません。
画像によるのか画像の向きが関係しているのか確認してみます。
追記4
私がiPhone6sで撮影した横向きの写真(ファイルサイズ1.6MBほど)とそれをMacのプレビューで縦向きにしたものを用意して試してみました。
横向き - http://imgs.link/BThDaB.jpg
縦向き - http://imgs.link/RMOdBs.jpg
全て横向きや縦向きの場合は50枚閲覧しても問題なく、縦横交互にすると9枚目でメモリ不足になりました。
また縦横2枚ずつ交互の場合も9枚目、縦横3枚ずつ交互の場合は11枚目でメモリ不足になりました。
もしかしたらこれはViewPagerがらみではなくVolleyがらみの問題でしょうか?
追記5
@yamacraft さん追記ありがとうございます。
キャンセル処理は最初に書いたようにFragment#onDestroyView
のタイミングでさせているはずなので問題ないはずです。
//もしタグが残っていたらキャンセル処理をさせておく
の部分です。
読み込みが終わったらタグを消す処理をしているので、タグが残っている=読み込み処理をまだやっている
と判断してキャンセル処理をさせています。
そもそも次へ移動する操作は写真が表示されてから行っているので、読み込みタスクは残っていないと思っています。
以前写真一覧でメモリ不足に悩まされたことがありましたが、その時は自前で同様のメモリ解放処理を書いたら解決しました。
当時の質問
- 写真を一定量読み込むとメモリ不足になってしまうのですが
- byte配列がメモリの大部分を占拠するのを解消したい。
VolleyはAPI23で非推奨のものを使っているのは知りませんでした。
まだ本アプリはまだcompileSdkVersionが22ですが、今回や今後のことも考えて他のライブラリに乗り換えることも前向きに検討してみます。