Android 实现图片缓存异步加载框架学习笔记

来源:互联网 发布:怎么清空淘宝搜索记录 编辑:程序博客网 时间:2024/04/29 16:37

最近想把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;}@Overrideprotected 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;}@Overrideprotected 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(){@Overridepublic 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();elseeditor.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;@Overridepublic 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;}@Overrideprotected 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();}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) { // 设置是否滚动的状态 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { // 不滚动状态       isScrolling = false;       this.notifyDataSetChanged();    } else {       isScrolling = true;    }}@Overridepublic 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

0 0