2015-08-10 2 views
25

У меня есть 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 

Вещи, которые я внимание при отладке:

  1. При установке приложения на телефон или виртуального устройства, изображения загружаются по сети, как и должно быть. Это видно по красному треугольнику в верхнем левом углу изображения.
  2. При прокрутке, чтобы изображения перезагружались, они извлекаются с диска. Это видно по синему треугольнику в верхнем левом углу изображения.
  3. При прокрутке некоторых изображений некоторые изображения загружаются из памяти, как видно из зеленого треугольника в верхнем левом углу.
  4. После прокрутки еще больше возникает исключение из памяти и прекращается загрузка. На изображениях, которые в настоящее время не хранятся в памяти, отображается только изображение-заполнитель, а в памяти - зеленый треугольник.

Here - образец изображения. Он довольно большой, но я использую fit(), чтобы уменьшить объем памяти в приложении.

Так что мои вопросы:

  • не должны изображения быть перезагружен с диска, когда кэш-память заполнена?
  • Изображения слишком большие? Сколько памяти может быть ожидать, скажем, 0,5 МБ изображения, потреблять при декодировании?
  • Что-нибудь не в порядке/необычного в моем коде ниже?

Настройка статического экземпляра 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); 
} 

Используя статический экземпляр Пикассо в моем 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... 
} 

ImageView в XML файл:

<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" 
/> 

Update

кажется, что прокрутка RecyclerView непрерывно делает увеличение выделения памяти на неопределенный срок. Я сделал тест RecyclerView, снятый в соответствии с official documentation, используя одно изображение для 200 CardView s с ImageView, но проблема не устранена. Большая часть изображений загружается из памяти (зеленая), а прокрутка плавная, но примерно каждая десятая ImageView загружает изображение с диска (синий). Когда изображение загружается с диска, выполняется распределение памяти, тем самым увеличивая распределение в куче и, таким образом, кучу.

Я попытался удалить свою собственную настройку глобального экземпляра Picasso и использовать по умолчанию вместо этого, но проблемы одинаковы.

Я проверил монитор Android-устройств, см. Изображение ниже. Это для Галактики S3. Каждое из распределений, выполняемых при загрузке изображения с диска, можно увидеть справа в разделе «Счет подсчета для каждого размера». Размер немного отличается для каждого размещения изображения, что тоже странно. Нажатие кнопки «Причина GB» делает самое правильное выделение 4,7 МБ.

Android Device Monitor

поведение является одинаковым для виртуальных устройств. На рисунке ниже показано его для AVS Nexus 5. Также здесь наибольшее распределение (10,6 МБ) уходит при нажатии «Причина GB».

Android Device Monitor

Кроме того, здесь есть изображения мест выделения памяти и потоки от монитора Android устройств. Повторные выделения выполняются в потоках Picasso, тогда как один удаленный с Cause GB выполняется в основном потоке.

Allocation Tracker Threads

+0

У меня такая же проблема. Даже при наличии только 25 единиц ресайклеров, если я постоянно прокручиваю вверх и вниз, он выходит из строя с исключением из памяти. – Zapnologica

ответ

31

Я не уверен fit() работает с android:adjustViewBounds="true". Согласно некоторым из past issues, это кажется проблематичным.

Несколько рекомендаций:

  • Установить фиксированный размер для ImageView
  • пользователя GlobalLayoutListener, чтобы получить размер в ImageView однажды он рассчитывается и после этого вызова Пикассо добавив метод resize()
  • Дают Glide попытка - его конфигурация по умолчанию приводит к более низкой площади, чем Picasso (в нем хранятся измененные изображения, а не оригиналы, и используется RGB565)
+1

Да, вы правы. Это был «android: adjustViewBounds =« true », который перепутал вещи. Удаление, из-за чего проблемы сразу исчезли. Я предполагаю, что фиксированная высота может быть в порядке, так что «ImageViews» наверняка будут иметь одинаковый размер. И, возможно, я дам Glide попробовать и тогда. Ура! –

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

Я бы сказал, что это действительно нечисто - вы даете LruCache 100Мб пространства. Несмотря на то, что все устройства отличаются друг от друга, на некоторых устройствах это будет или выше предела, и имейте в виду, что это только LruCache, не учитывающий сколько места кучи требует остальная часть вашего приложения. Я предполагаю, что это прямая причина исключения - вы говорите LruCache, что это позволяет получить намного больше, чем должно быть.

Я бы свести это к чему-то вроде 5MB, чтобы сначала доказать теорию, а затем экспериментировать с более высокими значениями на ваших целевых устройствах. Вы также можете запросить устройство, сколько места у него есть, и установить это значение программно, если хотите. Наконец, есть атрибут android:largeHeap="true", который вы можете добавить в свой манифест, но я собрал это, как правило, плохую практику.

Ваши изображения действительно большие, поэтому я бы предложил уменьшить их. Имейте в виду, что, хотя вы их обрезаете, они по-прежнему должны быть загружены в память в полном объеме.

+0

Да, это имеет смысл. Я просто пытался «максимизировать» его, что, конечно, очень плохая практика. При использовании кеша 10 МБ изображения всегда загружаются с диска, кроме одного, что делает приложение немного «выключенным», так как для загрузки изображения требуется ~ 0,5 секунды. Я думаю, все это сводится к слишком большим изображениям. –

+0

Возможно, вы захотите проверить эти потоки, чтобы уменьшить объем памяти, которую ваши растровые изображения используют, как только они были декодированы: (1) http://stackoverflow.com/questions/15459834/lrucache-not-working, (2) http: //stackoverflow.com/questions/21392972/how-to-load-large-images-in-android-and-avoiding-the-out-of-memory-error –

+0

Приветствия, я буду копаться в этом и посмотреть, могу ли я что-то выдумайте. –

2

с Picasso вы можете решить эту проблему, используя его свойство как:

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

вам нужно изменить размер изображения.

0

Я только что сделал класс Singleton для LoadImages. Проблема заключалась в том, что я использовал слишком много объектов Picasso, созданных со слишком большим количеством Picasso.Builder. Вот моя реализация:

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, Заполнитель и Заполнитель Error.

public class ImageToLoad { 

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

    //Getters and Setters 

}