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