RecyclerView
を使用して複数の画像を表示するPicasso
があります。上下にスクロールすると、アプリケーションは次のようなメッセージでメモリ不足になります。
E/dalvikvm-heap﹕ Out of memory on a 3053072-byte allocation.
I/dalvikvm﹕ "Picasso-/wp-content/uploads/2013/12/DSC_0972Small.jpg" prio=5 tid=19 RUNNABLE
I/dalvikvm﹕ | group="main" sCount=0 dsCount=0 obj=0x42822a50 self=0x59898998
I/dalvikvm﹕ | sysTid=25347 Nice=10 sched=0/0 cgrp=apps/bg_non_interactive handle=1500612752
I/dalvikvm﹕ | state=R schedstat=( 10373925093 843291977 45448 ) utm=880 stm=157 core=3
I/dalvikvm﹕ at Android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
I/dalvikvm﹕ at Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:623)
I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.decodeStream(BitmapHunter.Java:142)
I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.hunt(BitmapHunter.Java:217)
I/dalvikvm﹕ at com.squareup.picasso.BitmapHunter.run(BitmapHunter.Java:159)
I/dalvikvm﹕ at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:390)
I/dalvikvm﹕ at Java.util.concurrent.FutureTask.run(FutureTask.Java:234)
I/dalvikvm﹕ at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1080)
I/dalvikvm﹕ at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:573)
I/dalvikvm﹕ at Java.lang.Thread.run(Thread.Java:841)
I/dalvikvm﹕ at com.squareup.picasso.Utils$PicassoThread.run(Utils.Java:411)
I/dalvikvm﹕ [ 08-10 18:48:35.519 25218:25347 D/skia ]
--- decoder->decode returned false
デバッグ中に注意すること:
こちら はサンプル画像です。かなり大きいですが、アプリのメモリフットプリントを減らすためにfit()
を使用します。
だから私の質問は:
Activity
:を作成するときに静的Picassoインスタンスをセットアップする
private void setupPicasso()
{
Cache diskCache = new Cache(getDir("foo", Context.MODE_PRIVATE), 100000000);
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setCache(diskCache);
Picasso picasso = new Picasso.Builder(this)
.memoryCache(new LruCache(100000000)) // Maybe something fishy here?
.downloader(new OkHttpDownloader(okHttpClient))
.build();
picasso.setIndicatorsEnabled(true); // For debugging
Picasso.setSingletonInstance(picasso);
}
RecyclerView.Adapter
:で静的Picassoインスタンスを使用する
@Override
public void onBindViewHolder(RecipeViewHolder recipeViewHolder, int position)
{
Picasso.with(mMiasMatActivity)
.load(mRecipes.getImage(position))
.placeholder(R.drawable.picasso_placeholder)
.fit()
.centerCrop()
.into(recipeViewHolder.recipeImage); // recipeImage is an ImageView
// More...
}
XMLファイルのImageView
:
<ImageView
Android:id="@+id/mm_recipe_item_recipe_image"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:adjustViewBounds="true"
Android:paddingBottom="2dp"
Android:layout_alignParentTop="true"
Android:layout_centerHorizontal="true"
Android:clickable="true"
/>
RecyclerView
を連続的にスクロールすると、メモリ割り当てが無限に増加するようです。 RecyclerView
を持つ200個のCardView
sに対して単一のイメージを使用して、 公式ドキュメント に一致するようにテストImageView
を削除しましたが、問題は解決しません。ほとんどの画像はメモリから読み込まれ(緑)、スクロールはスムーズですが、ImageView
の約10分の1がディスクから画像を読み込みます(青)。イメージがディスクからロードされると、メモリの割り当てが実行され、ヒープ上の割り当て、ひいてはヒープ自体の割り当てが増加します。
グローバルPicasso
インスタンスの独自のセットアップを削除して、代わりにデフォルトを使用しようとしましたが、問題は同じです。
Android Device Monitor、下の画像を参照してください。これはGalaxy S3用です。ディスクから画像がロードされるときに行われる各割り当ては、右下に表示されます「サイズごとの割り当て数」:画像の割り当てごとにサイズがわずかに異なりますが、これも奇妙です。「Cause GB」を押すと、4.7 MBの右端の割り当てがなくなります。
動作は仮想デバイスと同じです。下の画像は、Nexus 5 AVDの場合を示しています。また、「Cause GB」を押すと、最大の割り当て(10.6 MBの割り当て)がなくなります。
さらに、メモリ割り当ての場所とAndroid Device Monitorからのスレッド)の画像があります。繰り返し割り当てはPicasso
スレッドで行われ、Cause GB
で削除された割り当てはメインスレッドで行われます。
fit()
が_Android:adjustViewBounds="true"
_で動作するかどうかわかりません。 過去の問題 のいくつかによると、問題があるようです。
いくつかの推奨事項:
resize()
メソッドを追加して取得する.memoryCache(new LruCache(100000000)) // Maybe something fishy here?
これは確かに怪しいと言えます-あなたはLruCache
100MBのスペースを与えています。すべてのデバイスは異なりますが、これは一部のデバイスの制限以上であり、これはLruCache
のみであり、アプリの他の部分が必要とするヒープスペースを考慮していないことに注意してください。私の推測では、これが例外の直接的な原因であるということです-あなたはLruCache
に、本来よりもずっと大きくなることを許可していると言っているのです。
最初に理論を証明するためにこれを5MBのようなものに減らしてから、ターゲットデバイスで徐々に高い値を試してみます。必要に応じて、デバイスの空き容量をデバイスに照会し、この値をプログラムで設定することもできます。最後に、Android:largeHeap="true"
属性をマニフェストに追加できますが、これは一般的に悪い習慣です。
あなたの画像は確かに大きいので、それらも減らすことをお勧めします。トリミングしている場合でも、一時的にフルサイズでメモリにロードする必要があることに注意してください。
ピカソでは、次のようなプロパティを使用して問題を解決できます。
Picasso.with(context)
.load(url)
.resize(300,300)
.into(listHolder.imageview);
画像のサイズを変更する必要があります。
LoadImagesのシングルトンクラスを作成しました。問題は、Picasso.Builderで作成したPicassoオブジェクトが多すぎることです。これが私の実装です。
public class ImagesLoader {
private static ImagesLoader currentInstance = null;
private static Picasso currentPicassoInstance = null;
protected ImagesLoader(Context context) {
initPicassoInstance(context);
}
private void initPicassoInstance(Context context) {
Picasso.Builder builder = new Picasso.Builder(context);
builder.listener(new Picasso.Listener() {
@Override
public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
exception.printStackTrace();
}
});
currentPicassoInstance = builder.build();
}
public static ImagesLoader getInstance(Context context) {
if (currentInstance == null) {
currentInstance = new ImagesLoader(context);
}
return currentInstance;
}
public void loadImage(ImageToLoad loadingInfo) {
String imageUrl = loadingInfo.getUrl().trim();
ImageView destination = loadingInfo.getDestination();
if (imageUrl.isEmpty()) {
destination.setImageResource(loadingInfo.getErrorPlaceholderResourceId());
} else {
currentPicassoInstance
.load(imageUrl)
.placeholder(loadingInfo.getPlaceholderResourceId())
.error(loadingInfo.getErrorPlaceholderResourceId())
.into(destination);
}
}
}
次に、ImageView、Url、Placeholder、Error Placeholderを保持するImageToLoad
クラスを作成します。
public class ImageToLoad {
private String url;
private ImageView destination;
private int placeholderResourceId;
private int errorPlaceholderResourceId;
//Getters and Setters
}