毕加索:记忆力不足

时间:2015-08-10 17:17:08

标签: android out-of-memory picasso

我有public static void setButtonFont(Activity activity, Button btnView, String fontType){ Typeface tf = Typeface.createFromAsset(activity.getAssets(),"fonts/"+fontType); btnView.setTypeface(tf); } 使用RecyclerView呈现多张图片。在向上和向下滚动一段时间之后,应用程序会耗尽内存,并显示以下消息:

Picasso

调试时我注意到的事项:

  1. 在手机或虚拟设备上安装应用程序时,图像会通过网络加载,这就是它的用途。这可以通过图像左上角的红色三角形看到。
  2. 滚动以便重新加载图像时,会从磁盘中提取这些图像。这可以通过图像左上角的蓝色三角形看到。
  3. 当滚动更多时,某些图像会从内存中加载,如左上角的绿色三角形所示。
  4. 滚动更多内容后,会发生内存不足异常并且加载停止。只有占位符图像显示在当前未保存在内存中的图像上,而内存中的图像则以绿色三角形正确显示。
  5. Here是一个示例图片。它非常大,但我使用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 来减少应用程序中的内存占用。

    所以我的问题是:

    • 当内存缓存时,不应该从磁盘重新加载图像 满了?
    • 图片太大了吗?我有多少记忆力 期待一个,让我们说0.5 MB的图像,在解码时消耗?
    • 我的代码中是否有任何错误/异常?

    创建fit()时设置静态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); } 中使用静态Picasso实例:

    RecyclerView.Adapter

    XML文件中的@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... }

    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的{​​{1}}进行了一次测试RecyclerView,使用CardView的200 ImageView单个图像,但问题仍然存在。大多数图像是从内存中加载的(绿色),滚动是平滑的,但大约每十分之一ImageView从磁盘加载图像(蓝色)。从磁盘加载映像时,会执行内存分配,从而增加堆上的分配,从而增加堆本身的分配。

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

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

    official documentation

    虚拟设备的行为相同。下图显示了Nexus 5 AVD。同样在这里,当按下&#34时,最大的分配(10.6 MB)会消失;原因GB&#34;。

    Android Device Monitor

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

    Android Device Monitor Allocation Tracker

4 个答案:

答案 0 :(得分:37)

我不确定fit()是否适用于android:adjustViewBounds="true"。根据一些past issues,它似乎有问题。

一些建议:

  • 为ImageView
  • 设置固定大小
  • 用户GlobalLayoutListener获取ImageView的大小一旦计算完毕,并在此调用之后Picasso添加resize()方法
  • 尝试Glide - 它的默认配置比Picasso(它存储已调整大小的图像而不是原始图像并使用RGB565)

答案 1 :(得分:6)

.memoryCache(new LruCache(100000000)) // Maybe something fishy here?

我会说这确实很可疑 - 你给了LruCache 100MB的空间。虽然所有设备都不同,但对于某些设备而言,这将达到或超过限制,请记住,这只是LruCache,并不考虑应用程序其余部分所需的堆空间。我的猜测是,这是例外的直接原因 - 你告诉LruCache它被允许变得比它应该大得多。

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

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

答案 2 :(得分:2)

使用毕加索你可以使用它的属性解决问题       像:

  Picasso.with(context)
                .load(url)
                .resize(300,300)
                .into(listHolder.imageview);

您需要调整图片大小。

答案 3 :(得分:0)

我刚刚为LoadImages制作了一个Singleton类。问题是我使用太多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);
        }
    }
}

然后创建一个ImageToLoad类,其中包含ImageView,Url,占位符和错误占位符。

public class ImageToLoad {

    private String url;
    private ImageView destination;
    private int placeholderResourceId;
    private int errorPlaceholderResourceId;

    //Getters and Setters

}