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

最近想把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

阅读更多
个人分类: Android学习记录
想对作者说点什么? 我来说一句

xutils jar包

2015年06月06日 277KB 下载

没有更多推荐了,返回首页

不良信息举报

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

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭