1st Challenge: Caching Solutions


#1

Looks like we’re all having a lot of trouble with these challenges (I’m still stuck in the second one), but at least here’s my [pretty fast & simple] solution for the first challenge.

It all takes place inside the PhotoGalleryFragment and ThumbnailDownloader classes. I slightly modified the Listener interface’s method in order to have something to use as a key for the cache HashMap.

Inside ThumbnailDownloader:

...
	public interface Listener<Token> {
		void onThumbnailDownloaded(Token token, String url, Bitmap thumbnail);
	}
...

and then, inside the ThumbnailDownloader.handleRequest(final Token) method:

...
	mResponseHandler.post(new Runnable() {
		public void run() {
			if (requestMap.get(token) != url) return;
			requestMap.remove(token);
			mListener.onThumbnailDownloaded(token, url, bitmap);
		}
	});
...

As for what concerns the cache, it’s everything inside PhotoGalleryFragment:

...
private LruCache<String, Bitmap> mMemoryCache;
...

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setRetainInstance(true);
		new FetchItemsTask().execute(current_page);
		
		...
		
	   /* 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/5th of the available memory for this memory cache.
	    final int cacheSize = maxMemory / 5;

	    mMemoryCache = new LruCache<String, Bitmap>(cacheSize);
		
		Log.i(TAG, "Background thread started");
	}

...

	//Cache set & get bitmap methods
	public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
	    if (getBitmapFromMemCache(key) == null) {
	        mMemoryCache.put(key, bitmap);
	    }
	}

	public Bitmap getBitmapFromMemCache(String key) {
	    if(key == null) return null;
		
	    return mMemoryCache.get(key);
	}

...

	private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
		public GalleryItemAdapter(ArrayList<GalleryItem> items) {
			super(getActivity(), 0, items);
		}
		
		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			if (convertView == null) {
				convertView = getActivity().getLayoutInflater().inflate(R.layout.gallery_item, parent, false);
			}
		
			ImageView imageView = (ImageView)convertView.findViewById(R.id.gallery_item_imageView);
			imageView.setImageResource(R.drawable.brian_up_close);
			GalleryItem item = getItem(position);
			if (getBitmapFromMemCache(item.getUrl()) == null){
				mThumbnailThread.queueThumbnail(imageView, item.getUrl());
			} else {
				if(isVisible()) {
					imageView.setImageBitmap(getBitmapFromMemCache(item.getUrl()));
				}
			}
		
			return convertView;
		}
	}
}

By testing the solution, if we scroll down some pages and then return back up, we can see immediately all the previous thumbnails since they have not to be downloaded again. If we scroll a lot though, the cache runs out of memory, dismisses some the lru bitmaps to make some space and, if we come back to their views, the app will simply have to download their thumbnails again… as expected. (yeah, I scrolled a lot :smiley: )

EDIT: For the sake of consistency, since I’m using this example to go on with the next chapter, I added a null checking on the key value for the getBitmapFromMemCache(String) method. Otherwise you’ll have a NullPointerExeption with your hardwired query in the first examples. Now the method is:

	public Bitmap getBitmapFromMemCache(String key) {
	    if(key == null) return null;
		
	    return mMemoryCache.get(key);
	}

#2

There’s no need to change the listener interface.

You can retain the getBitmapFromMemoryCache() method in the PhotoGalleryFragment.java and if the pic has been cached, you can skip the duration of queueThumbnail() method. If you use getBitmapFromMemoryCache() in the handleRequest() method, even though the pic has been cached, you will also see the placeholder pic for a very little time because the queueThumbnail() without downloading the pic will also take a very little time.

Bitmap bitmap = SingletonLruCache.getBitmapFromMemoryCache(item.getUrl());
if (bitmap == null) {
                mThumbnailThread.queueThumbnail(imageView, item.getUrl());
            } else {
                if (isVisible()) {
                    imageView.setImageBitmap(bitmap);
                }
            }

However, you can put addBitmapToMemoryCache just after the pic has been downloaded, it’s the best place to put the bitmap into the memory and there’s no need to change the defined interface.

bitmap = BitmapFactory.decodeByteArray(
                            bitmapBytes, 0, bitmapBytes.length);
                    SingletonLruCache.addBitmapToMemoryCache(url, bitmap);

At last, I made the mMemoryCache to be a Singleton Class instance instead of a instance variable.