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