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

原创 2015年01月11日 17:50:01

最近想把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图片异步加载框架Android-Universal-Image-Loader

Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架。这个框架已经被很多开发者所使用,是最常用的几个Android开源项目之一,主流的应用,随便反编译几个,...
  • HanTangSongMing
  • HanTangSongMing
  • 2014年12月17日 09:03
  • 17473

Android:android的框架区别(网络框架、图片异步加载与缓存框架、数据框架)特点整理

在我们安卓开发中,很多繁琐的代码会很头疼,编写程序的周期会增加,因此很多框架都冒出来了,我们在搞懂底层原理之时,我们更应该学会用一些优秀框架来解决复杂代码,和繁琐的重写代码。因为针对最简单的case,...
  • James_lang
  • James_lang
  • 2017年02月27日 15:47
  • 337

Android开发图片缓存框架Glide的总结

前段时间写过一篇图片缓存框架Picasso的用法,对于Picasso有些同学也比较熟悉,采用Lru最近最少缓存策略,并且自带内存和硬盘缓存机制,在图片加载尤其是多图加载着实为大伙省了不少力,在此之前同...
  • li0978
  • li0978
  • 2016年11月30日 23:40
  • 1221

Android四大图片缓存框架之-Fresco(一)

本文来自于Fresco中文文档,这仅仅是自己的学习笔记!!!大牛绕路,放我我。 关于Fresco的介绍,请查看链接 关于android图片缓存,这是一个android程序员必须了解的。关于四大图片...
  • qq_21430549
  • qq_21430549
  • 2015年10月23日 10:55
  • 7995

android图片缓存实现(自定义ImageLoader)

先说一下图片的三级缓存:首先肯定是内存缓存,接着是本地缓存(SD卡),网络缓存。 再来看一看图片缓存的流程图: 下面上代码:代码中用到了线程池、还有内存中的软引用 import java....
  • q908555281
  • q908555281
  • 2015年09月22日 16:46
  • 2533

android 图片缓存 异步加载 简要介绍

Caching Bitmaps [缓存位图]          加载单个Bitmap到UI是简单直接的,但是如果你需要一次加载大量的图片,事情则会变得复杂起来。在大多数情况下(例如在ListVi...
  • u012519664
  • u012519664
  • 2014年12月10日 09:19
  • 414

使用Volley网络框架实现ImageCache三级缓存

(1)首先要到Volley包 http://download.csdn.net/download/u012255016/8583561 (2)写一个类实现ImageCache public ...
  • u012255016
  • u012255016
  • 2015年04月11日 19:00
  • 2144

Android图片缓存分析与优化

Android系统对堆内存大小作了限制,不同的设备上这个阈值也会不同,当已分配内存加新分配内存大于堆内存就会导致OOM。虽然Android机型的配置在不断升级,但还是存在着几年前的旧机型,它们的特点是...
  • pw963852741pw
  • pw963852741pw
  • 2017年01月21日 15:44
  • 362

iOS开发swift版异步加载网络图片(带缓存和缺省图片)

iOS开发之swift版异步加载网络图片     与SDWebImage异步加载网络图片的功能相似,只是代码比较简单,功能没有SD的完善与强大,支持缺省添加图片,支持本地缓存。      异步加载...
  • walkerwqp
  • walkerwqp
  • 2017年01月06日 09:55
  • 1471

网络请求HttpClient 异步加载 图片缓存

HttpCient: HttpClient是Apache开源组织提供的HTTP网络访问接口(一个开源的项目),从名字上就可以看出,它是一个简单的HTTP客户端(并不是浏览器),可以发送HTTP请求,...
  • xy8199
  • xy8199
  • 2017年09月09日 09:02
  • 335
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android 实现图片缓存异步加载框架学习笔记
举报原因:
原因补充:

(最多只允许输入30个字)