最近想把Android异步加载实现原理的理解整理下,学习了郭林大神的关于内存缓存和硬盘缓存的系列博客,自己也想写写自己看完博客后的收获。先推荐郭大神的这篇博客Android照片墙完整版,完美结合LruCache和DiskLruCache,针对郭大神的这篇博客,我写了一个Demo做了些封装实现。首先将封装出一个异步加载的图片加载类ImageAsynLoadView,代码实习如下:
/* * 异步加载的ImageView * */ public class ImageAsynLoadView extends ImageView { //进度 private final Paint paint; private final Context context; private Resources res; private int max = 100; private int progress = 0; private int ringWidth; // 圆环的颜色 private int ringColor; // 进度条颜色 private int progressColor; // 字体颜色 private int textColor; // 字的大小 private int textSize; private String textProgress; //异步信息 private BitmapCache imageCache; private ImageLoadTask imageLoadTask; public ImageAsynLoadView(Context context, AttributeSet attrs) { super(context, attrs); this.imageCache = ImageApp.getSelf().getBitmapCache(); this.context = context; this.paint = new Paint(); this.res = context.getResources(); this.paint.setAntiAlias(true); // 消除锯齿 this.ringWidth = dip2px(context, 3); // 设置圆环宽度 this.ringColor = Color.BLACK;// 黑色进度条背景 this.progressColor = Color.WHITE;// 白色进度条 this.textColor = Color.BLACK;// 黑色数字显示进度; this.textSize = 15;// 默认字体大小 } /** * 设置进度条最大值 * * @param max */ public void setMax(int max) { if (max < 0) max = 0; if (progress > max) progress = max; this.max = max; } /** * 获取进度条最大值 * * @return */ public int getMax() { return max; } /** * 设置加载进度,取值范围在0~之间 * * @param progress */ public void setProgress(int progress) { if (progress >= 0 && progress <= max) { this.progress = progress; invalidate(); } } /** * 获取当前进度值 * * @return */ public int getProgress() { return progress; } /** * 设置圆环背景色 * * @param ringColor */ public void setRingColor(int ringColor) { this.ringColor = res.getColor(ringColor); } /** * 设置进度条颜色 * * @param progressColor */ public void setProgressColor(int progressColor) { this.progressColor = res.getColor(progressColor); } /** * 设置字体颜色 * * @param textColor */ public void setTextColor(int textColor) { this.textColor = res.getColor(textColor); } /** * 设置字体大小 * * @param textSize */ public void setTextSize(int textSize) { this.textSize = textSize; } /** * 设置圆环半径 * * @param ringWidth */ public void setRingWidthDip(int ringWidth) { this.ringWidth = dip2px(context, ringWidth); } /** * 通过不断画弧的方式更新界面,实现进度增加 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(progress > 0 &&progress < max){ int center = getWidth() / 2; int radios = center/ 2 - ringWidth / 2; // 绘制圆环 this.paint.setStyle(Paint.Style.STROKE); // 绘制空心圆 this.paint.setColor(ringColor); this.paint.setStrokeWidth(ringWidth); canvas.drawCircle(center, center, radios, this.paint); RectF oval = new RectF(center - radios, center - radios, center + radios, center + radios); this.paint.setColor(progressColor); canvas.drawArc(oval, 90, 360 * progress / max, false, paint); this.paint.setStyle(Paint.Style.FILL); this.paint.setColor(textColor); this.paint.setStrokeWidth(0); this.paint.setTextSize(textSize); this.paint.setTypeface(Typeface.DEFAULT_BOLD); textProgress = (int) (1000 * (progress / (10.0 * max))) + "%"; float textWidth = paint.measureText(textProgress); canvas.drawText(textProgress, center - textWidth / 2, center + textSize / 2, paint); } } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } public boolean loadCacheImage(String url){ Bitmap bitmap = imageCache.get(url); if(bitmap != null){ setImageBitmap(bitmap); return true; }else{ setBackgroundResource(R.drawable.empty_photo); return false; } } public void loadNetworkImage(String url){ if(imageLoadTask != null){ imageLoadTask.cancel(true); } imageLoadTask = new ImageLoadTask(this,imageCache); imageLoadTask.execute(url); } private class ImageLoadTask extends AsyncTask<String, Integer, Bitmap> { private ImageView imageView; private BitmapCache cache; public ImageLoadTask(ImageView imageView,BitmapCache cache){ this.imageView = imageView; this.cache = cache; } @Override protected Bitmap doInBackground(String... params) { if(imageView == null){ return null; } publishProgress(0); final String url = params[0]; Bitmap result = cache.get(url); Bitmap bitmap = null; if(result == null){ HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); InputStream is = null; ByteArrayOutputStream baos = null; //以下为异步的网络图片获取 try { HttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); long length = httpEntity.getContentLength(); is = httpEntity.getContent(); if (is != null) { baos = new ByteArrayOutputStream(); byte[] buf = new byte[128]; int read = -1; int count = 0; while ((read = is.read(buf)) != -1) { baos.write(buf, 0, read); count += read; publishProgress(count /(int)length * 100); //设置进度判断图片加载进度 } byte[] data = baos.toByteArray(); bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (baos != null) { baos.close(); } if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } if(bitmap != null){ cache.put(url, bitmap); result = bitmap; } } publishProgress(max); return result; } @Override protected void onProgressUpdate(Integer... progress) { setProgress(progress[0]);//此处为对图片的同步更新 } @Override protected void onPostExecute(Bitmap result) { super.onPostExecute(result); if(result != null){ imageView.setImageBitmap(result); } } } }下面说下本次的重点,实现对读取内存缓存和硬盘缓存的封装实现。图片获取流程如下:
以上流程的代码实现如下:
/** * 实习内存+硬盘同时缓存实现(并发读写,并发写还没想好实现怎么加锁控制) * @author zhanglei * */ public class BitmapCache { private DiskLruCache mDiskLruCache; private LruCache<String, Bitmap> mMemoryCache; private BitmapCache(Context context){ if(context != null){ context = context.getApplicationContext(); } } /** * 判断当前是否在主线程 **/ private static void checkNotOnMainThread() { if (Looper.myLooper() == Looper.getMainLooper()) { throw new IllegalStateException( "线程读取错误"); } } /** * 将缓存记录同步到journal文件中(系统关闭前实现操作,最好在Acticity的onStop()调用) */ public void flush(){ if(mDiskLruCache != null){ new Thread(new Runnable(){ @Override public void run() { try { mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } } private void setMemoryCache(LruCache<String, Bitmap> mMemoryCache){ this.mMemoryCache = mMemoryCache; } private void setDiskCache(DiskLruCache diskCache) { this.mDiskLruCache = diskCache; } public boolean containsInDiskCache(String url) { if (null != mDiskLruCache) { checkNotOnMainThread(); try { return null != mDiskLruCache.get(encodeUrlforDiskCache(url)); } catch (IOException e) { e.printStackTrace(); } } return false; } public boolean containsInMemoryCache(String url) { return null != mMemoryCache && null != mMemoryCache.get(url); } public Bitmap get(String url){ return get(url,null); } public Bitmap get(String url, BitmapFactory.Options decodeOpts){ //内存缓存获取 Bitmap result = getFromMemoryCache(url); if(result == null){ //然后硬盘缓存获取 result = getFromDiskCache(url,decodeOpts); } return result; } private Bitmap getFromDiskCache(String url,final BitmapFactory.Options decodeOpts) { if(mDiskLruCache != null){ String key = encodeUrlforDiskCache(url); InputStream stream = null; Snapshot snapShot; try { snapShot = mDiskLruCache.get(key); if(snapShot != null){ stream = snapShot.getInputStream(0); } } catch (IOException e) { e.printStackTrace(); } if(stream != null){ return decodeBitmap(stream, decodeOpts); } } return null; } public Bitmap getFromMemoryCache(String url) { Bitmap result = null; if(mMemoryCache != null){ //此处互斥控制通对mMemoryCache加锁,防止获取图片的时刻内存缓存机制删除多余图片 synchronized (mMemoryCache) { result = mMemoryCache.get(url); } } return result; } private String encodeUrlforDiskCache(String url) { //加密字符串 return Md5.encode(url); } private Bitmap decodeBitmap(InputStream is,BitmapFactory.Options opts){ if (opts == null) { opts = new BitmapFactory.Options(); } if (opts.inSampleSize <= 1) { opts.inSampleSize = 1; } return BitmapFactory.decodeStream(is, null, opts); } public void put(String url,InputStream ins) throws IOException{ String key = encodeUrlforDiskCache(url); if(mMemoryCache != null){ Bitmap bitmap = BitmapFactory.decodeStream(ins); if(bitmap != null) mMemoryCache.put(key, bitmap); } Editor editor = null; try { editor = mDiskLruCache.edit(key); } catch (IOException e) { e.printStackTrace(); } if(editor != null){ OutputStream ops = editor.newOutputStream(0); if(writeOutputStream(ins,ops)) editor.commit(); else editor.abort(); } } private boolean writeOutputStream(InputStream input, OutputStream output) throws IOException { BufferedOutputStream out = null; BufferedInputStream in = null; try{ in = new BufferedInputStream(input, 8 * 1024); out = new BufferedOutputStream(output, 8 * 1024); int b; while ((b = in.read()) != -1) { out.write(b); } return true; } catch (final IOException e) { e.printStackTrace(); } finally { if(in != null){ in.close(); } if(out != null){ out.close(); } } return false; } public void put(String url,Bitmap bmp){ put(url,bmp,Bitmap.CompressFormat.PNG, 100); } public void put(String url,Bitmap bmp,Bitmap.CompressFormat compressFormat,int compressQuality){ if(mMemoryCache != null){ mMemoryCache.put(url, bmp); } //此处应该有并发的控制,应该对url控制,不然相同的图片重复写入硬盘(没有想到很好的控制) if(mDiskLruCache != null){ String key = encodeUrlforDiskCache(url); OutputStream ops = null; try { Editor editor = mDiskLruCache.edit(key); ops = editor.newOutputStream(0); bmp.compress(compressFormat, compressQuality, ops); ops.flush(); editor.commit(); } catch (IOException e) { e.printStackTrace(); } finally{ try { ops.close(); } catch (IOException e) { e.printStackTrace(); } } } } //构造类 public final static class Builder { static final int IMAGEBYTE = 1024 * 1024; static final int DEFAULT_MEM_CACHE_MAX_SIZE_MB = 3; static final int DEFAULT_DIS_CACHE_MAX_SIZE_MB = 10; private int diskCacheMaxSize; private int mMemoryCacheMaxSize; private File diskCacheLoc; private Context context; public Builder(Context context){ this.context = context; diskCacheMaxSize = DEFAULT_DIS_CACHE_MAX_SIZE_MB*IMAGEBYTE; mMemoryCacheMaxSize = DEFAULT_MEM_CACHE_MAX_SIZE_MB * IMAGEBYTE; } public Builder setDiskFileLocation(File location){ diskCacheLoc = location; return this; } public BitmapCache bulid() throws FileNotFoundException{ final BitmapCache bmpCache = new BitmapCache(context); bmpCache.setMemoryCache(new LruCache<String,Bitmap>(mMemoryCacheMaxSize)); if(diskCacheLoc == null){ throw new FileNotFoundException("diskCache is not intial Location"); } new AsyncTask<Void, Void, DiskLruCache>() { @Override protected DiskLruCache doInBackground(Void... params) { try { return DiskLruCache.open(diskCacheLoc,1, 1, diskCacheMaxSize); } catch (IOException e) { e.printStackTrace(); return null; } } @Override protected void onPostExecute(DiskLruCache result) { bmpCache.setDiskCache(result); } }.execute(); return bmpCache; } } }以上代码实现对内存和硬盘缓存的封装,实际就是对DiskLruCache和LruCache,下次有时间把对内存缓存和硬盘缓存的实现机制谈谈自己的理解。对BItmapCache的初始化我放到Application里实现,代码如下:
public class ImageApp extends Application { private static ImageApp mApp; private BitmapCache bitmapCache; private File cacheFileDir; @Override public void onCreate() { super.onCreate(); mApp = (ImageApp)getApplicationContext(); if(bitmapCache == null){ if (Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) { cacheFileDir = new File(Environment.getExternalStorageDirectory() + File.separator + "ImageCaches"); if (!cacheFileDir.exists()) { cacheFileDir.mkdirs(); } } else { cacheFileDir = new File(mApp.getCacheDir().getAbsolutePath() + "/listcache"); } try { bitmapCache = new BitmapCache.Builder(mApp). setDiskFileLocation(cacheFileDir).bulid(); } catch (FileNotFoundException e) { e.printStackTrace(); } } } public static ImageApp getSelf(){ return mApp; } public BitmapCache getBitmapCache() { return bitmapCache; } }
然后就是用GridView对图片加载的一些优化,代码如下:
public class PictrueListAdapter extends BaseListAdapter<String> implements AbsListView.OnScrollListener{ private LayoutInflater inflater; private boolean isScrolling; private int imageHeight = 0; private int mFirstVisibleItem; private int mLastVisibleItem; public PictrueListAdapter(Context context, List<String> values) { super(context, values); inflater =LayoutInflater.from(context); } private boolean isScrolling(){ return isScrolling; } @Override protected View getItemView(View convertView, int position) { ViewHolder holder; if(convertView == null){ convertView = inflater.inflate(R.layout.pictrue_item, null); holder = new ViewHolder(); holder.imageView = (ImageAsynLoadView)convertView.findViewById(R.id.photo); holder.tv_status = (TextView) convertView.findViewById(R.id.tv_status); convertView.setTag(holder); }else{ holder = (ViewHolder)convertView.getTag(); } if (holder.imageView.getLayoutParams().height != imageHeight) { holder.imageView.getLayoutParams().height = imageHeight; } final String url = getItem(position); if(url != null){ //只加载GridView可见部分的代码实现 if(!isScrolling() && position >= mFirstVisibleItem && position <= mLastVisibleItem){ boolean isFromCache = holder.imageView.loadCacheImage(url); if(!isFromCache){ holder.imageView.loadNetworkImage(url); } if(isFromCache){ holder.tv_status.setText("From Cache"); }else{ holder.tv_status.setText("From NetWork"); } holder.imageView.setTag(url); }else{ //为了节约资源加载,滚动状态只加载缓存图片 if(!holder.imageView.loadCacheImage(url)){ holder.imageView.setImageResource(R.drawable.empty_photo); } } }else{ holder.imageView.setImageResource(R.drawable.empty_photo); } return convertView; } public void setItemHeight(int columnWidth) { imageHeight= columnWidth; notifyDataSetChanged(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 设置是否滚动的状态 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { // 不滚动状态 isScrolling = false; this.notifyDataSetChanged(); } else { isScrolling = true; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; int lastIndex= firstVisibleItem + visibleItemCount; if(lastIndex > totalItemCount -1){ mLastVisibleItem = totalItemCount -1; }else{ mLastVisibleItem = lastIndex; } } class ViewHolder{ public ImageAsynLoadView imageView; public TextView tv_status; } }完整的代码实现如示例 ImageCache