1

I have a news feed, where news items contain images. Each news item (in the table view) obtains a image url from a server and downloads the images asynchronously / from cache.

I will use a bit of pseudocode / Java to explain my process in the simplest terms possible.

NewsItemAdapter

Map<String, Bitmap> imageCache = new HashMap<String, Bitmap>();  // init cache
String imurl = get image url from appropriate NewsObject;
Bitmap value = imageCache.get(imurl);

if (value != null) {   // if bitmap is in cache

    load bitmap into image view from cache;
    add bitmap to NewsObject for accessing later;

}else {

    execute Asynchronous bitmap download task;

}

Asynchronous bitmap download task (The reason for scaleDownBitmap() is because of OutOfMemory errors I get)

doinBackground(Void...params){

    myBitmap = download bitmap from imurl;
    imageCache.put(imurl, scaleDownBitmap(myBitmap)); // put bitmap into cache
    return scaleDownBitmap(myBitmap);
}

onPostExecute(Bitmap result){

    load result into image view;

}

MainActivity

setOnNewsItemClickListener{

    intent = get intent to mainNewsScreen; //after you click on news item
    intent.putExta("newsBitmap", bitmap from NewsObject); // set in NewsItemAdapter
    startActivity(intent);

}        

MainNewsScreen

onCreate(){

    load bitmap from intent extras into image view;

}

My main problem is if I remove the scaleDownBitmap() method found here I get OutOfMemory errors.

But I am losing a lot of image quality. As you can see I haven't used bitmap.recycle() at all, I'm not entirely sure where I'd use it as I need to keep the images in memory (I would have thought).

Any idea how to make this more efficient. I'm sure this would be helpful to a lot of people attempting to create a similar app.

Community
  • 1
  • 1
Greg Peckory
  • 7,700
  • 21
  • 67
  • 114

1 Answers1

1

Consider using an lrucache instead and storing the image on disk when it falls out of the cache.

Best practice is explained here: http://developer.android.com/intl/es/training/displaying-bitmaps/cache-bitmap.html

Consider this code as an idea, some of it copied from the above link:

...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

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

mMemoryCache = 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;
    }
    @Override
    protected void entryRemoved (boolean evicted, K key, V oldValue, V newValue) {
       // Save your entry to disc instead
    }
};

Google has made a DiscLruCache that you can simply download and use in your project (its usage is described in the above link):

https://developer.android.com/intl/es/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html

Also don't keep an infinite amount of news / images in the view. You're going to have to remove news items as the user scrolls through them and reads them.

Consider using a Recycler view for this (along with Cards if you want a Material design feel to your app): https://developer.android.com/intl/es/reference/android/support/v7/widget/RecyclerView.html

<!-- A RecyclerView with some commonly used attributes -->
<android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

    // use a linear layout manager
    mLayoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(mLayoutManager)

Cards: https://developer.android.com/intl/es/reference/android/support/v7/widget/CardView.html

<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_view"
    android:layout_gravity="center"
    android:layout_width="200dp"
    android:layout_height="200dp"
    card_view:cardCornerRadius="4dp">

    <TextView
        android:id="@+id/info_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.v7.widget.CardView>

More information about combining a Recycler view with Card views: https://developer.android.com/training/material/lists-cards.html

JohanShogun
  • 2,956
  • 21
  • 30
  • Great, thanks for the answer! What about passing the bitmap through an intent? Would I be better off passing a reference to a global bitmap and manually recycling it? Or is what I am doing reaosnably efficient approach? – Greg Peckory Jul 25 '15 at 16:13
  • I try to avoid manually recycling bitmaps, it's asking for state issues and lingering references to them. Android will automatically recycle your bitmap and free all related memory to it when the last reference to it is lost (the days of when you had to manually handle native memory with bitmaps is over) – JohanShogun Jul 25 '15 at 16:16
  • I would also avoid passing the bitmap through intents and instead share the cache between activities. One way of doing that would be to make the cache a singleton, shared within the application. Remember that if you pick this approach you may manually have to call evictAll(); on it when appropriate to not hog too much RAM. – JohanShogun Jul 25 '15 at 16:18
  • Finally, you may want to consider a fragment approach instead of an activity based one. That way you store the LruCache in your activity and access it in your Fragments. – JohanShogun Jul 25 '15 at 16:21
  • A few questions before I accept your answer: Firstly, the news will be limited to approx. 50 items, so is recycling still necessary? Secondly, could you expand on why I should use a singleton, and not a global variable? I'm new enough to this stuff. Thirdly. if I am using fragments, would I need to use singletons at all? Thanks a lot! – Greg Peckory Jul 25 '15 at 18:01
  • 1) 50 items seems like a lot to me, consider bringing it down if you want to have larger pictures. Remember you can have more items available without drawing them on the screen, that's where the recycler view comes in handy. 2) a singleton is basically a global instance you can access, but you have more control over it than a global variable. 3) nope, if you go for fragments you only need the cache in the activity. – JohanShogun Jul 25 '15 at 18:16
  • Almost finished... Would the bitmap cache itself not use up too much memory? I thought this may have been causing the problem. By default, doesn't the list view recycle the cells, isn't this the point of the image cache in the first place? Maybe I'm a bit confused and apologies if so. I think I'll go for the fragments, it seems singletons can be a bad idea. Thanks for both options though, very helpful ! – Greg Peckory Jul 25 '15 at 18:35
  • The list view recycles the cells, if you don't hang onto the bitmap a when they are no longer needed by the list then you will have to re-download them. To avoid the problems and lag of re-downloading you cache them. The LruCache will never take more size than you have allowed it to upon creation. To make sure you don't have to redownload the images when they are discarded from your cache you store them on disc, which is significantly faster than redownloading and better for the users data-usage. Lru= least recently used, so images not used recently are discarded first. – JohanShogun Jul 25 '15 at 18:43
  • Great! So the least recently used images are stored in a local cache (like shared preferences) rather than in memory, is that right? Would this make it noticeably slower for loading the images? And what would you recommend as the max size for the Lru cache in MB? – Greg Peckory Jul 25 '15 at 18:51
  • The LruCache is stored in RAM, but the DiscLruCache mentioned in my answer stores both in RAM and on disc. You keep a smaller memory cache which is changes it content often for speed. The Disc cache is slower, but faster than redownloading. As for cache size, I find that 1/8 of total app RAM is usually quite sufficient, like in the example. :) – JohanShogun Jul 25 '15 at 18:54