Невозможно загрузить несколько изображений в RemoteViewsFactory

Я создаю простой виджет для Android, который извлекает и отображает обложки некоторых фильмов с веб-сайта. Это просто GridView, которое отображает изображения. Виджет работает нормально, но в тот момент, когда я начинаю прокручивать, я получаю и OutOfMemoryError, и Android убивает мой процесс.

Вот код моего RemoteViewsFactory. Я не могу понять, как это решить. Изображения, которые я использую, имеют правильный размер, поэтому я не трачу память в Android. Они идеального размера. Как видите, я использую очень маленький кэш LRU, а в своем манифесте я даже включил android:largeHeap="true". Я ломал голову над этой проблемой целых два дня, и мне интересно, возможно ли вообще то, что я пытаюсь сделать?

public class SlideFactory implements RemoteViewsFactory {

    private JSONArray jsoMovies = new JSONArray();
    private Context ctxContext;
    private Integer intInstance;
    private RemoteViews remView;
    private LruCache<String, Bitmap> lruCache;
    private static String strCouch = "http://192.168.1.110:5984/movies/";

    public SlideFactory(Context ctxContext, Intent ittIntent) {
        this.ctxContext = ctxContext;
        this.intInstance = ittIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        this.lruCache = new LruCache<String, Bitmap>(4 * 1024 * 1024);
        final Integer intMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final Integer intBuffer = intMemory / 8;

        lruCache = new LruCache<String, Bitmap>(intBuffer) {
            @Override
            protected int sizeOf(String strDigest, Bitmap bmpCover) {
                return bmpCover.getByteCount() / 1024;
            }
        };
    }

    public RemoteViews getViewAt(int intPosition) {
        if (intPosition <= getCount()) {
            try {
                String strDocid = this.jsoMovies.getJSONObject(intPosition).getString("id");
                String strDigest = this.jsoMovies.getJSONObject(intPosition).getJSONObject("value").getJSONObject("_attachments").getJSONObject("thumb.jpg").getString("digest");
                String strTitle = this.jsoMovies.getJSONObject(intPosition).getJSONObject("value").getString("title");
                Bitmap bmpThumb = this.lruCache.get(strDigest);

                if (bmpThumb == null) {
                    String strUrl = strCouch + strDocid + "/thumb.jpg";
                    System.out.println("Fetching" + intPosition);
                    bmpThumb = new ImageFetcher().execute(strUrl).get();
                    this.lruCache.put(strDigest, bmpThumb);
                }
                remView.setImageViewBitmap(R.id.movie_cover, bmpThumb);
                remView.setTextViewText(R.id.movie_title, strTitle);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return remView;
        }
        return null;
    }

    public void onCreate() {
        return;
    }

    public void onDestroy() {
        jsoMovies = null;
    }

    public int getCount() {
        return 20;
    }

    public RemoteViews getLoadingView() {
        return null;//new RemoteViews(this.ctxContext.getPackageName(), R.layout.loading);
    }

    public int getViewTypeCount() {
        return 1;
    }

    public long getItemId(int intPosition) {
        return intPosition;
    }

    public boolean hasStableIds() {
        return true;
    }

    public void onDataSetChanged() {
        this.remView = new RemoteViews(this.ctxContext.getPackageName(), R.layout.slide);
        try {
            DefaultHttpClient dhcNetwork = new DefaultHttpClient();
            String strUrl = strCouch + "_design/application/_view/language?" + URLEncoder.encode("descending=true&startkey=[\"hi\", {}]&attachments=true");
            HttpGet getMovies = new HttpGet(strUrl);
            HttpResponse resMovies = dhcNetwork.execute(getMovies);
            Integer intMovies = resMovies.getStatusLine().getStatusCode();
            if (intMovies != HttpStatus.SC_OK) {
                throw new HttpResponseException(intMovies, "Server responded with an error");
            }
            String strMovies = EntityUtils.toString(resMovies.getEntity(), "UTF-8");
            this.jsoMovies = new JSONObject(strMovies).getJSONArray("rows");
        } catch (Exception e) {
            Log.e("SlideFactory", "Unknown error encountered", e);
        } 
    }
}

Вот источник AsyncTask, извлекающий изображения:

public class ImageFetcher extends AsyncTask<String, Void, Bitmap> {
    @Override
    protected Bitmap doInBackground(String... strUrl) {
        Bitmap bmpThumb = null;
        try {
            URL urlThumb = new URL(strUrl[0]);
            HttpURLConnection hucConnection = (HttpURLConnection) urlThumb.openConnection();
            InputStream istThumb = hucConnection.getInputStream();
            bmpThumb = BitmapFactory.decodeStream(istThumb);
            istThumb.close();
            hucConnection.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bmpThumb;
    }
}

person Mridang Agarwalla    schedule 18.11.2013    source источник
comment
@commonsware, не могли бы вы пролить свет на это? У тебя всегда был ответ. :) Спасибо.   -  person Mridang Agarwalla    schedule 21.11.2013
comment
Можете ли вы опубликовать вывод logcat?   -  person bgse    schedule 22.11.2013
comment
@MridangAgarwalla Можете ли вы опубликовать код для ImageFetcher? Можете ли вы также указать размер изображений, которые вы загружаете (или, может быть, размер самого большого из них)..??   -  person Amulya Khare    schedule 22.11.2013
comment
@AmulyaKhare, я разместил источник ImageFetcher. Я мог бы поиграть с размером выборки, но это резко снижает качество. Мои изображения все 250x375 пикселей. Я помещаю println в метод getViewAt и замечаю, что Android пытается загрузить все изображения сразу, хотя в GridView видны только несколько изображений. Я думал, что эти адаптеры для ListView, GridView и StackVIew работают так, что они предварительно загружают только несколько элементов, и в тот момент, когда пользователь прокручивает их, и они не в фокусе, Android отбрасывает старые макеты. Я не понимаю, почему он пытается загрузить все элементы.   -  person Mridang Agarwalla    schedule 22.11.2013
comment
Вы читали это? И эта this.lruCache = new LruCache<String, Bitmap>(4 * 1024 * 1024); строка бесполезна.   -  person Leonidos    schedule 22.11.2013
comment
@Leonidos, я пробовал это, удалив все функции LRUCache из своего кода, но это не помогло.   -  person Mridang Agarwalla    schedule 22.11.2013
comment
Если вы нашли решение OOM при использовании setImageViewBitmap, опубликуйте его! Информации об этом нет вообще!   -  person anil    schedule 23.06.2015


Ответы (2)


У меня был подобный горький опыт, и после долгих копаний я обнаружил, что setImageViewBitmap копирует все растровое изображение в новый экземпляр, поэтому занимает двойную память. Рассмотрите возможность изменения следующей строки либо на статический ресурс, либо на что-то еще!

remView.setImageViewBitmap(R.id.movie_cover, bmpThumb);

Это занимает много памяти и пингует сборщик мусора для очистки памяти, но настолько медленно, что ваше приложение не может вовремя использовать освобожденную память.

person AVEbrahimi    schedule 26.11.2013
comment
Поскольку я загружаю изображения с удаленного сервера, как мне создать статическую ссылку. Под статической ссылкой вы имеете в виду активы в папке с возможностью рисования? Также было бы здорово, если бы вы могли дать ссылку на исходники Android, где вы обнаружили проблему с методом setImageViewBitmap . Спасибо @AVEbrahimi - person Mridang Agarwalla; 26.11.2013
comment
Спасибо! Это действительно спасло положение! - person Mridang Agarwalla; 27.11.2013
comment
@MridangAgarwalla Не могли бы вы опубликовать свое решение? - person Eloar; 06.01.2015
comment
Я не думаю, что проблема была решена. Если вы нашли решение, пожалуйста, напишите его! - person anil; 23.06.2015

Мой обходной путь использует LRUCache для хранения растровых изображений виджета:

Сначала запустите как обычно:

protected void initializeCache()
    {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // Use 1/10th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 10;

        bitmapLruCache = new LruCache<String, Bitmap>(cacheSize)
        {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in kilobytes rather than
                // number of items.
                return bitmap.getByteCount() / 1024;
            }
        };
    }

Затем повторно используйте растровые изображения:

void updateImageView(RemoteViews views, int resourceId, final String imageUrl)
    {
        Bitmap bitmap = bitmapLruCache.get(imageUrl);

        if (bitmap == null)
        {
            bitmap = // get bitmap from web with your loader
            bitmapLruCache.put(imageUrl, bitmap);
        }

        views.setImageViewBitmap(resourceId, bitmap);
    }

С этим виджетом кода теперь не происходит сбой моего приложения.

Дополнительная информация здесь

person anil    schedule 27.10.2015