图片加载有很多,部份都是使用LruCache、弱引用、软引用等编写的,其编目的就是优化内存、缓存、加载效果,不过,它们都是各有千秋,下面看看【Apollo播放器】它是对图片加载如何做的
它主要结构如下:
ImageInfo - 图片信息相关
ImageCache - 图片缓存相关
ImageProvider -图片提供者相关(管理器)
GetBitmapTask -图片下载任务
下面一一分析每个类的结构和使用
public class ImageInfo { //图像类型 //专辑(ablum), 艺术家(artist), 播放列表(playlist), 流派genre public String type; //图像来源 //lastfm - 来自网站 //file - 来自音频 //gallery - 来自相册 //first_avail - 来自 'file' 或 'lastfm' public String source; //请求的图像的大小 //缩略图 or 正常图 public String size; //执行图像获取所需的额外数据 //lastFM - 艺术家需要艺术家的形象 //ablum - 专辑、歌手专辑图像 //file - 需要相册id //gallery - 需要图像的文件路径 //first_available - 需要两个文件lastfm数据 public String[] data; }
public final class ImageCache { private static final String TAG = ImageCache.class.getSimpleName(); //二级缓存实例对象,其底层是继承自LinkedHashMap<K, V> //具体实现请看源码:http://androidxref.com/4.4.2_r1/xref/packages/apps/UnifiedEmail/src/com/android/mail/utils/LruCache.java private LruCache<String, Bitmap> mLruCache; private static ImageCache sInstance; public ImageCache(final Context context) { init(context); } /** * 图片缓存单例模式 * @param context * @return */ public final static ImageCache getInstance(final Context context) { if (sInstance == null) { sInstance = new ImageCache(context.getApplicationContext()); } return sInstance; } public void init(final Context context) { final ActivityManager activityManager = (ActivityManager)context .getSystemService(Context.ACTIVITY_SERVICE); final int lruCacheSize = Math.round(0.25f * activityManager.getMemoryClass() * 1024 * 1024);//四舍五入计算二级缓存大小 mLruCache = new LruCache<String, Bitmap>(lruCacheSize) { @Override protected int sizeOf(final String paramString, final Bitmap paramBitmap) { //返回每个key所对应的values在二级缓存中报占大小 return paramBitmap.getByteCount(); } }; } /** * 查找或创建的高速缓存 * @param activity * @return */ public static final ImageCache findOrCreateCache(final Activity activity) { FragmentManager nFragmentManger = activity.getFragmentManager(); RetainFragment retainFragment = (RetainFragment)nFragmentManger.findFragmentByTag(TAG); if (retainFragment == null) { retainFragment = new RetainFragment(); nFragmentManger.beginTransaction().add(retainFragment, TAG).commit(); } ImageCache cache = (ImageCache)retainFragment.getObject(); if (cache == null) { cache = getInstance(activity); retainFragment.setObject(cache); } return cache; } /** * 存入缓存 * @param data * @param bitmap */ public void add(final String data, final Bitmap bitmap) { if (data == null || bitmap == null) { return; } if (get(data) == null) { mLruCache.put(data, bitmap); } } /** * 获取缓存图片 * @param data * @return */ public final Bitmap get(final String data) { if (data == null) { return null; } if (mLruCache != null) { final Bitmap mBitmap = mLruCache.get(data); if (mBitmap != null) { return mBitmap; } } return null; } /** * 移除指定缓存 */ public void remove(final String key) { if (mLruCache != null) { mLruCache.remove(key); } } /** * 清除缴存并回收 */ public void clearMemCache() { if (mLruCache != null) { mLruCache.evictAll(); } System.gc(); } /** * 个人理解为是 保持应用(FragmentManager)只存在这一个实例 * @author xiajun * */ public static final class RetainFragment extends Fragment { private Object mObject; public RetainFragment() { } @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Make sure this Fragment is retained over a configuration change setRetainInstance(true); } public void setObject(final Object object) { mObject = object; } public Object getObject() { return mObject; } } }
public class ImageProvider implements GetBitmapTask.OnBitmapReadyListener{ private ImageCache memCache; private Map<String, Set<ImageView>> pendingImagesMap = new HashMap<String, Set<ImageView>>(); private Set<String> unavailable = new HashSet<String>(); private Context mContext; private int thumbSize; private static ImageProvider mInstance; protected ImageProvider( Activity activity ) { mContext = activity; memCache = ImageCache.getInstance( activity ); Resources resources = mContext.getResources(); DisplayMetrics metrics = resources.getDisplayMetrics(); thumbSize = (int) ( ( 153 * (metrics.densityDpi/160f) ) + 0.5f ); } public final static ImageProvider getInstance( final Activity activity ) { if (mInstance == null) { mInstance = new ImageProvider( activity ); mInstance.setImageCache(ImageCache.findOrCreateCache(activity)); } return mInstance; } public void setImageCache(final ImageCache cacheCallback) { memCache = cacheCallback; } /** * 加载图片 * @param imageView 显示的View * @param imageInfo 图片信息相关 */ public void loadImage( ImageView imageView, ImageInfo imageInfo ){ //创建一个图片标识,例如:21albartimg 图片命名和创建规则,请详细查看ImageUtils.java String tag = ImageUtils.createShortTag(imageInfo) + imageInfo.size; if( imageInfo.source.equals(SRC_FILE) || imageInfo.source.equals(SRC_LASTFM) || imageInfo.source.equals(SRC_GALLERY)){ clearFromMemoryCache( ImageUtils.createShortTag(imageInfo) ); asyncLoad( tag, imageView, new GetBitmapTask( thumbSize, imageInfo, this, imageView.getContext() ) ); } if(!setCachedBitmap(imageView, tag)){ asyncLoad( tag, imageView, new GetBitmapTask( thumbSize, imageInfo, this, imageView.getContext() ) ); } } /** * 缓存位图 * @param imageView * @param tag * @return */ private boolean setCachedBitmap(ImageView imageView, String tag) { if (unavailable.contains(tag)) { handleBitmapUnavailable(imageView, tag); return true; } Bitmap bitmap = memCache.get(tag); if (bitmap == null) return false; imageView.setTag(tag); imageView.setImageBitmap(bitmap); return true; } /** * 处理位图不可用 * @param imageView * @param tag */ private void handleBitmapUnavailable(ImageView imageView, String tag) { imageView.setTag(tag); imageView.setImageDrawable(null); } /** * 加载位图 * @param imageView * @param bitmap * @param tag */ private void setLoadedBitmap(ImageView imageView, Bitmap bitmap, String tag) { if (!tag.equals(imageView.getTag())) return; final TransitionDrawable transition = new TransitionDrawable(new Drawable[]{ new ColorDrawable(android.R.color.transparent), new BitmapDrawable(imageView.getResources(), bitmap) }); imageView.setImageDrawable(transition); final int duration = imageView.getResources().getInteger(R.integer.image_fade_in_duration); transition.startTransition(duration); } /** * 异步加载 * @param tag * @param imageView * @param task */ private void asyncLoad(String tag, ImageView imageView, AsyncTask<String, Integer, Bitmap> task) { Set<ImageView> pendingImages = pendingImagesMap.get(tag); if (pendingImages == null) { pendingImages = Collections.newSetFromMap(new WeakHashMap<ImageView, Boolean>()); // create weak set pendingImagesMap.put(tag, pendingImages); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);//可用于并行执行任务的执行 } pendingImages.add(imageView); imageView.setTag(tag); imageView.setImageDrawable(null); } @Override public void bitmapReady(Bitmap bitmap, String tag) { if (bitmap == null) { unavailable.add(tag); } else { memCache.add(tag, bitmap); } Set<ImageView> pendingImages = pendingImagesMap.get(tag); if (pendingImages != null) { pendingImagesMap.remove(tag); for (ImageView imageView : pendingImages) { setLoadedBitmap(imageView, bitmap, tag); } } } /** * 清除内存缓存 * @param tag */ public void clearFromMemoryCache(String tag){ if (unavailable.contains(tag + SIZE_THUMB)) { unavailable.remove(tag + SIZE_THUMB); } if (pendingImagesMap.get(tag + SIZE_THUMB)!=null){ pendingImagesMap.remove(tag + SIZE_THUMB); } if (memCache.get(tag + SIZE_THUMB)!=null){ memCache.remove(tag + SIZE_THUMB); } if (unavailable.contains(tag + SIZE_NORMAL)) { unavailable.remove(tag + SIZE_NORMAL); } if (pendingImagesMap.get(tag + SIZE_NORMAL)!=null){ pendingImagesMap.remove(tag + SIZE_NORMAL); } if (memCache.get(tag + SIZE_NORMAL)!=null){ memCache.remove(tag + SIZE_NORMAL); } } /** * 清除所有缓存(内存和磁盘) */ public void clearAllCaches(){ try{ ImageUtils.deleteDiskCache(mContext); memCache.clearMemCache(); } catch(Exception e){} } }
public class GetBitmapTask extends AsyncTask<String, Integer, Bitmap> { private WeakReference<OnBitmapReadyListener> mListenerReference; private WeakReference<Context> mContextReference; private ImageInfo mImageInfo; private int mThumbSize; public GetBitmapTask( int thumbSize, ImageInfo imageInfo, OnBitmapReadyListener listener, Context context ) { mListenerReference = new WeakReference<OnBitmapReadyListener>(listener); mContextReference = new WeakReference<Context>(context); mImageInfo = imageInfo; mThumbSize = thumbSize; } @Override protected Bitmap doInBackground(String... ignored) { Context context = mContextReference.get(); if (context == null) { return null; } //返回图像信息来源于... File nFile = null; if( mImageInfo.source.equals(SRC_FILE) && !isCancelled()){ nFile = ImageUtils.getImageFromMediaStore( context, mImageInfo ); } else if ( mImageInfo.source.equals(SRC_LASTFM) && !isCancelled()){ nFile = ImageUtils.getImageFromWeb( context, mImageInfo ); } else if ( mImageInfo.source.equals(SRC_GALLERY) && !isCancelled()){ nFile = ImageUtils.getImageFromGallery( context, mImageInfo ); } else if ( mImageInfo.source.equals(SRC_FIRST_AVAILABLE) && !isCancelled()){ Bitmap bitmap = null; if( mImageInfo.size.equals( SIZE_NORMAL ) ){ bitmap = ImageUtils.getNormalImageFromDisk( context, mImageInfo ); } else if( mImageInfo.size.equals( SIZE_THUMB ) ){ bitmap = ImageUtils.getThumbImageFromDisk( context, mImageInfo, mThumbSize ); } //if we have a bitmap here then its already properly sized if( bitmap != null ){ return bitmap; } if( mImageInfo.type.equals( TYPE_ALBUM ) ){ nFile = ImageUtils.getImageFromMediaStore( context, mImageInfo ); } if( nFile == null && ( mImageInfo.type.equals( TYPE_ALBUM ) || mImageInfo.type.equals( TYPE_ARTIST ) ) ) nFile = ImageUtils.getImageFromWeb( context, mImageInfo ); } if( nFile != null ){ //返回正常大小的图 if( mImageInfo.size.equals( SIZE_NORMAL ) ) return BitmapFactory.decodeFile(nFile.getAbsolutePath()); //返回缩略图 return ImageUtils.getThumbImageFromDisk( context, nFile, mThumbSize ); } return null; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); OnBitmapReadyListener listener = mListenerReference.get(); if(bitmap == null && !isCancelled()){ if(mImageInfo.size.equals(SIZE_THUMB)) bitmap = BitmapFactory.decodeResource(mContextReference.get().getResources(), R.drawable.no_art_small); else if(mImageInfo.size.equals(SIZE_NORMAL)) bitmap = BitmapFactory.decodeResource(mContextReference.get().getResources(), R.drawable.no_art_normal); } if (bitmap != null && !isCancelled()) { if (listener != null) { listener.bitmapReady(bitmap, ImageUtils.createShortTag(mImageInfo) + mImageInfo.size ); } } } public static interface OnBitmapReadyListener { public void bitmapReady(Bitmap bitmap, String tag); } }
下面看看ImageUtils这个类的格式命名规则
public class ImageUtils { //图片格式 private static final String IMAGE_EXTENSION = ".img"; private static File getFile(Context context, ImageInfo imageInfo){ return new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION); } /** * 从磁盘中获取正常图片 * @param context * @param imageInfo * @return */ public static Bitmap getNormalImageFromDisk( Context context, ImageInfo imageInfo ){ Bitmap bitmap = null; File nFile = getFile( context, imageInfo ); if(nFile.exists()){ bitmap = BitmapFactory.decodeFile( nFile.getAbsolutePath() ); } return bitmap; } /** * 从磁盘中获取指定大小的缩略图片 * @param context * @param imageInfo * @param thumbSize * @return */ public static Bitmap getThumbImageFromDisk( Context context, ImageInfo imageInfo, int thumbSize ){ File nFile = getFile( context, imageInfo ); return getThumbImageFromDisk( context, nFile, thumbSize ); } /** * 从磁盘中获取指定大小的缩略图片 * @param context * @param nFile * @param thumbSize * @return */ public static Bitmap getThumbImageFromDisk( Context context, File nFile, int thumbSize ){ if(!nFile.exists()) return null; final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; try{ BitmapFactory.decodeFile( nFile.getAbsolutePath() , options); } catch(Exception e){ } final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > thumbSize || width > thumbSize) { if (width > height) { inSampleSize = Math.round((float)height / (float)thumbSize); } else { inSampleSize = Math.round((float)width / (float)thumbSize); } final float totalPixels = width * height; final float totalReqPixelsCap = thumbSize * thumbSize * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } } options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; Bitmap bitmap = null; try{ bitmap = BitmapFactory.decodeFile( nFile.getAbsolutePath() , options ); } catch (Exception e){ } return bitmap; } /** * 获取相册中的图片 * @param context * @param imageInfo * @return */ public static File getImageFromGallery( Context context, ImageInfo imageInfo ){ String albumArt = ( imageInfo.type == TYPE_ALBUM ) ? imageInfo.data[3] : imageInfo.data[1]; if(albumArt != null){ try{ File orgFile = new File(albumArt); File newFile = new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION); InputStream in = new FileInputStream(orgFile); OutputStream out = new FileOutputStream(newFile); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0){ out.write(buf, 0, len); } in.close(); out.close(); return newFile; } catch( Exception e){ } } return null; } /** * 获取Web的图片 * @param context * @param imageInfo * @return */ public static File getImageFromWeb( Context context, ImageInfo imageInfo ) { String imageUrl = null; try { if( imageInfo.type.equals(TYPE_ALBUM) ){ Album image = Album.getInfo(imageInfo.data[1], imageInfo.data[2], LASTFM_API_KEY); if (image != null) { imageUrl = image.getLargestImage(); } } else if ( imageInfo.type.equals(TYPE_ARTIST) ) { PaginatedResult<Image> images = Artist.getImages(imageInfo.data[0], 2, 1, LASTFM_API_KEY); Iterator<Image> iterator = images.getPageResults().iterator(); if (iterator.hasNext()) { Image image = iterator.next(); imageUrl = image.getLargestImage(); } } } catch ( Exception e ) { return null; } if ( imageUrl == null || imageUrl.isEmpty() ) { return null; } File newFile = getFile( context, imageInfo ); ApolloUtils.downloadFile( imageUrl, newFile ); if (newFile.exists()) { return newFile; } return null; } /** * 获取多媒体图片 * @param context * @param imageInfo * @return */ public static File getImageFromMediaStore( Context context, ImageInfo imageInfo ){ String mAlbum = imageInfo.data[0]; String[] projection = { BaseColumns._ID, Audio.Albums._ID, Audio.Albums.ALBUM_ART, Audio.Albums.ALBUM }; Uri uri = Audio.Albums.EXTERNAL_CONTENT_URI; Cursor cursor = null; try{ cursor = context.getContentResolver().query(uri, projection, BaseColumns._ID+ "=" + DatabaseUtils.sqlEscapeString(mAlbum), null, null); } catch(Exception e){ e.printStackTrace(); } int column_index = cursor.getColumnIndex(Audio.Albums.ALBUM_ART); if(cursor.getCount()>0){ cursor.moveToFirst(); String albumArt = cursor.getString(column_index); if(albumArt != null){ try{ File orgFile = new File(albumArt); File newFile = new File(context.getExternalCacheDir(), createShortTag(imageInfo)+IMAGE_EXTENSION); InputStream in = new FileInputStream(orgFile); OutputStream out = new FileOutputStream(newFile); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0){ out.write(buf, 0, len); } in.close(); out.close(); cursor.close(); return newFile; } catch( Exception e){ } } } return null; } /** * 删除磁盘缓存 * @param context * @throws IOException */ public static void deleteDiskCache(Context context) throws IOException { final File dir = context.getExternalCacheDir(); final File[] files = dir.listFiles(); for (final File file : files) { if (!file.delete()) { throw new IOException("failed to delete file: " + file); } } } /** * 创建图片标识 * @param imageInf * @return */ public static String createShortTag(ImageInfo imageInf){ String tag = null; if( imageInf.type.equals( TYPE_ALBUM ) ){ //专辑 id + 专辑后缀 tag = imageInf.data[0] + ALBUM_SUFFIX; } else if (imageInf.type.equals( TYPE_ARTIST )){ //艺术家名称 + 艺术家后缀 tag = imageInf.data[0] + ARTIST_SUFFIX; } else if (imageInf.type.equals( TYPE_GENRE )){ //流派名称 + 流派后缀 tag = imageInf.data[0] + GENRE_SUFFIX; } else if (imageInf.type.equals( TYPE_PLAYLIST )){ //流派名称 + 播放列表后缀 tag = imageInf.data[0] + PLAYLIST_SUFFIX; } ApolloUtils.escapeForFileSystem(tag); return tag; } }
【Apollo播放器】图片加载模式,就说到这里,期待后面更多代码分析....