1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.example.android.supportv4.media; 18 19import android.graphics.Bitmap; 20import android.os.AsyncTask; 21import android.support.v4.graphics.BitmapCompat; 22import android.support.v4.util.LruCache; 23import android.util.Log; 24 25import com.example.android.supportv4.media.utils.BitmapHelper; 26 27import java.io.IOException; 28 29/** 30 * Implements a basic cache of album arts, with async loading support. 31 */ 32public final class AlbumArtCache { 33 private static final String TAG = "AlbumArtCache"; 34 35 private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024; // 12 MB 36 private static final int MAX_ART_WIDTH = 800; // pixels 37 private static final int MAX_ART_HEIGHT = 480; // pixels 38 39 // Resolution reasonable for carrying around as an icon (generally in 40 // MediaDescription.getIconBitmap). This should not be bigger than necessary, because 41 // the MediaDescription object should be lightweight. If you set it too high and try to 42 // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors. 43 private static final int MAX_ART_WIDTH_ICON = 128; // pixels 44 private static final int MAX_ART_HEIGHT_ICON = 128; // pixels 45 46 private static final int BIG_BITMAP_INDEX = 0; 47 private static final int ICON_BITMAP_INDEX = 1; 48 49 private final LruCache<String, Bitmap[]> mCache; 50 51 private static final AlbumArtCache sInstance = new AlbumArtCache(); 52 53 public static AlbumArtCache getInstance() { 54 return sInstance; 55 } 56 57 private AlbumArtCache() { 58 // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and 59 // Integer.MAX_VALUE: 60 int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE, 61 (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()/4))); 62 mCache = new LruCache<String, Bitmap[]>(maxSize) { 63 @Override 64 protected int sizeOf(String key, Bitmap[] value) { 65 return BitmapCompat.getAllocationByteCount(value[BIG_BITMAP_INDEX]) 66 + BitmapCompat.getAllocationByteCount(value[ICON_BITMAP_INDEX]); 67 } 68 }; 69 } 70 71 public Bitmap getBigImage(String artUrl) { 72 Bitmap[] result = mCache.get(artUrl); 73 return result == null ? null : result[BIG_BITMAP_INDEX]; 74 } 75 76 public Bitmap getIconImage(String artUrl) { 77 Bitmap[] result = mCache.get(artUrl); 78 return result == null ? null : result[ICON_BITMAP_INDEX]; 79 } 80 81 public void fetch(final String artUrl, final FetchListener listener) { 82 // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests 83 // are not handled properly: they may cause redundant costly operations, like HTTP 84 // requests and bitmap rescales. For production-level apps, we recommend you use 85 // a proper image loading library, like Glide. 86 Bitmap[] bitmap = mCache.get(artUrl); 87 if (bitmap != null) { 88 Log.d(TAG, "getOrFetch: album art is in cache, using it " + artUrl); 89 listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]); 90 return; 91 } 92 Log.d(TAG, "getOrFetch: starting asynctask to fetch " + artUrl); 93 94 new AsyncTask<Void, Void, Bitmap[]>() { 95 @Override 96 protected Bitmap[] doInBackground(Void[] objects) { 97 Bitmap[] bitmaps; 98 try { 99 Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap(artUrl, 100 MAX_ART_WIDTH, MAX_ART_HEIGHT); 101 Bitmap icon = BitmapHelper.scaleBitmap(bitmap, 102 MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON); 103 bitmaps = new Bitmap[] {bitmap, icon}; 104 mCache.put(artUrl, bitmaps); 105 } catch (IOException e) { 106 return null; 107 } 108 Log.d(TAG, "doInBackground: putting bitmap in cache. cache size=" + mCache.size()); 109 return bitmaps; 110 } 111 112 @Override 113 protected void onPostExecute(Bitmap[] bitmaps) { 114 if (bitmaps == null) { 115 listener.onError(artUrl, new IllegalArgumentException("got null bitmaps")); 116 } else { 117 listener.onFetched(artUrl, 118 bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]); 119 } 120 } 121 }.execute(); 122 } 123 124 public static abstract class FetchListener { 125 public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage); 126 public void onError(String artUrl, Exception e) { 127 Log.e(TAG, "AlbumArtFetchListener: error while downloading " + artUrl, e); 128 } 129 } 130} 131