毕加索:记忆力不足

Krø*_*lle 28 android out-of-memory picasso

我有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
Run Code Online (Sandbox Code Playgroud)

调试时我注意到的事情:

  1. 在手机或虚拟设备上安装应用程序时,图像将通过网络加载,这就是它的用途.这可以通过图像左上角的红色三角形看到.
  2. 滚动以便重新加载图像时,它们将从磁盘中获取.这可以通过图像左上角的蓝色三角形看到.
  3. 当滚动更多时,一些图像从内存加载,如左上角的绿色三角形所示.
  4. 滚动一些之后,会发生内存不足异常并且加载停止.只有占位符图像显示在当前未保存在内存中的图像上,而内存中的图像则以绿色三角形正确显示.

是一个示例图像.它非常大,但我fit()用来减少应用程序中的内存占用.

所以我的问题是:

  • 当内存缓存已满时,是否应该从磁盘重新加载映像?
  • 图像太大了吗?在解码时,我可以期待多少内存,比如0.5 MB的图像?
  • 我的代码下面有什么不对/异常的吗?

在创建时设置静态Picasso实例Activity:

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);
}
Run Code Online (Sandbox Code Playgroud)

在我的使用静态Picasso实例RecyclerView.Adapter:

@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...
}
Run Code Online (Sandbox Code Playgroud)

ImageViewXML文件中:

<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"
/>
Run Code Online (Sandbox Code Playgroud)

更新

似乎RecyclerView连续滚动会使内存分配无限增加.我做了一个测试RecyclerView剥离以匹配官方文档,使用单个图像200 CardViewImageView,但问题仍然存在.大多数图像都是从内存中加载的(绿色),滚动是平滑的,但大约每十分钟ImageView加载一次来自磁盘的图像(蓝色).从磁盘加载映像时,将执行内存分配,从而增加堆上的分配,从而增加堆本身的分配.

我尝试删除自己的全局Picasso实例设置并使用默认设置,但问题是相同的.

我使用Android设备监视器进行了检查,请参见下图.这是一个Galaxy S3.从磁盘加载图像时完成的每个分配都可以在"每个大小的分配计数"下的右侧看到.每个图像分配的大小略有不同,这也很奇怪.按"原因GB"使最右边的4.7 MB分配消失.

Android设备监视器

虚拟设备的行为是相同的.下图显示了Nexus 5 AVD.此外,当按下"原因GB"时,最大的分配(10.6 MB的分配)消失了.

Android设备监视器

此外,这里是内存分配位置和Android设备监视器中的线程的图像.重新发生的分配在Picasso线程中完成,而删除的分配Cause GB在主线程上完成.

分配跟踪器 主题

Sam*_*ski 38

我不确定是否fit()合作android:adjustViewBounds="true".根据过去的一些问题,它似乎有问题.

一些建议:

  • 为ImageView设置固定大小
  • 用户使用GlobalLayoutListener获取ImageView的大小,并在此调用之后Picasso添加resize()方法
  • 滑翔一个尝试-其默认配置导致了较低的足迹比毕加索(它存储的调整大小成像,而不是原来的,并使用RGB565)

  • 是的你是对的。正是`android:adjustViewBounds="true"`把事情搞砸了。删除它使问题立即消失。我想固定的高度也可能是合适的,这样“ImageViews”就一定具有相同的大小。也许有时我也会尝试一下 Glide。干杯! (2认同)

Sam*_*zor 6

.memoryCache(new LruCache(100000000)) // Maybe something fishy here?
Run Code Online (Sandbox Code Playgroud)

我会说这确实很可疑 - 你给的是LruCache100MB的空间.虽然所有设备都是不同的,但是对于某些设备来说,这将达到或超过限制,请记住,这只是LruCache应用程序其余部分需要的堆空间.我的猜测是,这是异常的直接原因 - 你告诉LruCache它允许它变得比它应该大得多.

我会将其减少到5MB左右,以首先证明理论,然后在目标设备上逐步实验更高的值.您还可以查询设备的空间大小,并根据需要以编程方式设置此值.最后,android:largeHeap="true"你可以添加到你的清单中的属性,但我收集这通常是不好的做法.

你的图像确实很大,所以我建议减少它们.请记住,即使你正在修剪它们,它们仍然需要以其全尺寸暂时加载到内存中.