1 /* 2 * Copyright (C) 2014 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 17 package com.android.music.utils; 18 19 import android.graphics.Bitmap; 20 import android.os.AsyncTask; 21 import android.util.LruCache; 22 23 import java.io.IOException; 24 25 /** 26 * Implements a basic cache of album arts, with async loading support. 27 */ 28 public final class AlbumArtCache { 29 private static final String TAG = LogHelper.makeLogTag(AlbumArtCache.class); 30 31 private static final int MAX_ALBUM_ART_CACHE_SIZE = 12 * 1024 * 1024; // 12 MB 32 private static final int MAX_ART_WIDTH = 800; // pixels 33 private static final int MAX_ART_HEIGHT = 480; // pixels 34 35 // Resolution reasonable for carrying around as an icon (generally in 36 // MediaDescription.getIconBitmap). This should not be bigger than necessary, because 37 // the MediaDescription object should be lightweight. If you set it too high and try to 38 // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors. 39 private static final int MAX_ART_WIDTH_ICON = 128; // pixels 40 private static final int MAX_ART_HEIGHT_ICON = 128; // pixels 41 42 private static final int BIG_BITMAP_INDEX = 0; 43 private static final int ICON_BITMAP_INDEX = 1; 44 45 private final LruCache<String, Bitmap[]> mCache; 46 47 private static final AlbumArtCache sInstance = new AlbumArtCache(); 48 getInstance()49 public static AlbumArtCache getInstance() { 50 return sInstance; 51 } 52 AlbumArtCache()53 private AlbumArtCache() { 54 // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and 55 // Integer.MAX_VALUE: 56 int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE, 57 (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory() / 4))); 58 mCache = new LruCache<String, Bitmap[]>(maxSize) { 59 @Override 60 protected int sizeOf(String key, Bitmap[] value) { 61 return value[BIG_BITMAP_INDEX].getByteCount() 62 + value[ICON_BITMAP_INDEX].getByteCount(); 63 } 64 }; 65 } 66 getBigImage(String artUrl)67 public Bitmap getBigImage(String artUrl) { 68 Bitmap[] result = mCache.get(artUrl); 69 return result == null ? null : result[BIG_BITMAP_INDEX]; 70 } 71 getIconImage(String artUrl)72 public Bitmap getIconImage(String artUrl) { 73 Bitmap[] result = mCache.get(artUrl); 74 return result == null ? null : result[ICON_BITMAP_INDEX]; 75 } 76 fetch(final String artUrl, final FetchListener listener)77 public void fetch(final String artUrl, final FetchListener listener) { 78 // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests 79 // are not handled properly: they may cause redundant costly operations, like HTTP 80 // requests and bitmap rescales. For production-level apps, we recommend you use 81 // a proper image loading library, like Glide. 82 Bitmap[] bitmap = mCache.get(artUrl); 83 if (bitmap != null) { 84 LogHelper.d(TAG, "getOrFetch: album art is in cache, using it", artUrl); 85 listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]); 86 return; 87 } 88 LogHelper.d(TAG, "getOrFetch: starting asynctask to fetch ", artUrl); 89 90 new AsyncTask<Void, Void, Bitmap[]>() { 91 @Override 92 protected Bitmap[] doInBackground(Void[] objects) { 93 Bitmap[] bitmaps; 94 try { 95 Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap( 96 artUrl, MAX_ART_WIDTH, MAX_ART_HEIGHT); 97 Bitmap icon = BitmapHelper.scaleBitmap( 98 bitmap, MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON); 99 bitmaps = new Bitmap[] {bitmap, icon}; 100 mCache.put(artUrl, bitmaps); 101 } catch (IOException e) { 102 return null; 103 } 104 LogHelper.d(TAG, 105 "doInBackground: putting bitmap in cache. cache size=" + mCache.size()); 106 return bitmaps; 107 } 108 109 @Override 110 protected void onPostExecute(Bitmap[] bitmaps) { 111 if (bitmaps == null) { 112 listener.onError(artUrl, new IllegalArgumentException("got null bitmaps")); 113 } else { 114 listener.onFetched( 115 artUrl, bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]); 116 } 117 } 118 } 119 .execute(); 120 } 121 122 public static abstract class FetchListener { onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage)123 public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage); onError(String artUrl, Exception e)124 public void onError(String artUrl, Exception e) { 125 LogHelper.e(TAG, e, "AlbumArtFetchListener: error while downloading " + artUrl); 126 } 127 } 128 } 129