Не може да се заредят множество изображения в 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