Android异步加载网络图片 + 双缓存

网上看了好多这样的例子,感觉直接把人家的例子拿来用太过草率,也不便于以后的更改。就干脆自己写一个小框架。

需求分析:

Android的App中加载网络图片是个非常常用的操作,再加上很多情况下,同一个图片极有可能被程序加载很多次。而多次重复的对同一个URL下的图片进行加载,这无疑会增大很多的流量开销,并且用户体验也非常不好。

解决方式:

总体来说分为三步:

1. 当需要加载一张图片时,首先判断程序缓存(以下用AppCache代替)中是否有该张图片,若有,则继续直接取得,否则下一步

2. AppCache中没有该张图片时就开始向文件缓存(以下用FileCache代替)中查找,若有,取之并向AppCache中添加该图片,否则下一步

3. FileCache中也没有时,就开始根据图片的URL来从网上获取(通过类ImageHttp),若有,取之并先向FileCache中添加再向AppCache中添加,否则返回null(此时一般在程序内置一张图片代替,可手动添加,此处略)

以上说的比较粗略,详情看一下代码,注释很详细:


1. 首先看一下关于URL的加密,其中加密的方式有很多,所以在这里为了日后的拓展,就写一个简单的接口,并用自己的MD5加密方式实现了这个接口

接口OnCode

package com.example.image.code;

/**
 * @author wzj
 * @version 2014-12-10 上午10:44:48
 */
public interface OnCode {

	/**
	 * @param message
	 * @return 加密的信息
	 */
	String encode(String message);
	
	/**
	 * @param message
	 * @return 解密的信息
	 */
	String decode(String message);
	
}

MD5类,实现了该接口

package com.example.image.code;

import com.ndktools.javamd5.Mademd5;

/**
 * @author wzj
 * @version 2014-12-10 上午10:46:20
 */
public class MD5 implements OnCode {

	@Override
	public String encode(String message) {
		Mademd5 md = new Mademd5();
		return md.toMd5(message);
	}

	@Override
	public String decode(String message) {
		return null;
	}

}


2. 加载图片时用到的回调接口OnBitmap,使用时需要实现该接口即可在接口的方法中得到加载的图片

package com.example.image.util.image.dao;

import android.graphics.Bitmap;

/**
 * @author wzj
 * @version 2014-12-10 上午11:13:21
 */
public interface OnBitmap {
	/**
	 * 加载结束时回调该方法,该方法是在主线程中执行的,异常或出错时 image为 null
	 * @param image
	 */
	void onBitmap(Bitmap image);
}

3. 程序缓存AppCache类,使用了Java中的软引用SoftReference,既能存储数据也不影响Java垃圾回收器的回收

package com.example.image.util.image;

import java.lang.ref.SoftReference;
import java.util.HashMap;

import android.graphics.Bitmap;

/**
 * SoftReference: 软引用,可以保存数据,也不妨碍垃圾回收器的回收
 * @author wzj
 * @version 2014-12-9 下午12:39:50
 */
public final class AppCache {

	private static HashMap<String, SoftReference<Bitmap>> data = new HashMap<String, SoftReference<Bitmap>>();
	
	public static void setImage(String url, Bitmap bitmap) {
		if(url == null || bitmap == null){
			return;
		}
		if(!data.containsKey(url) || (data.containsKey(url) && data.get(url).get() == null)){
			data.put(url, new SoftReference<Bitmap>(bitmap));
		}
	}
	
	public static Bitmap getImage(String url) {
		return data.containsKey(url) ? data.get(url).get() : null;
	}
	
}


4. 文件缓存FileCache类,包含读写图片和清除缓存三个方法

<pre name="code" class="java">package com.jandar.wzj.http.image;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.jandar.wzj.http.image.code.OnCode;
import com.jandar.wzj.util.L;


/**
 * 关于URL加密的必要性:
 * 1. 文件名暴露了图片的网络资源的URL,一般来讲是不安全的
 * 2. URL中包含有"/"、":"等文件命名不支持的字符
 * @author wzj
 * @version 2014-12-9 下午12:21:53
 */
public class FileCache {

	private String path;
	private OnCode onCode;

	public FileCache(String path, OnCode onCode) {
		this.path = path;
		this.onCode = onCode;
	}

	// 从本地存储图片
	public void setImage(String url, InputStream imageIs) {
		File dir = new File(path);
		if (!dir.exists()) {
			dir.mkdirs();
		}
		File file = new File(dir, onCode.encode(url));
		if (file.exists()) {
			/**
			 * 此处,可能同一个url但是网络资源发生改变
			 */
			new L("该图片已在SD卡中,不需要重复写入..");
			return;
		}
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(file);
			byte[] buffer = new byte[1024];
			int index = -1;
			while ((index = imageIs.read(buffer)) != -1) {
				fos.write(buffer, 0, index);
			}
			new L("SD卡写入成功");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(fos != null){
					fos.close();
				}
				if(imageIs != null){
					imageIs.close();
				}
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
	}

	// 从本地读取图片
	public Bitmap getImage(String url) {
		File dir = new File(path);
		//若文件目录不存在,则返回null
		if(!dir.exists()){
			return null;
		}
		File file = new File(path, onCode.encode(url));
		if (file.exists()) {
			try {
				Bitmap image = BitmapFactory.decodeFile(file.getPath());
				// 此时再次向程序缓存存入数据
				AppCache.setImage(url, image);
				return image;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * 清除文件缓存
	 * @return
	 */
	public boolean clearFileCache() {
		try {
			File dir = new File(path);
			dir.delete();
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

}
 

5. 网络加载ImageHttp类,在网络中读取文件,有一点要明白,关于网络加载的InputStream不可重复使用(原因暂不明)

<pre name="code" class="java">package com.jandar.wzj.http.image;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;

import com.jandar.wzj.http.image.dao.OnBitmap;
import com.jandar.wzj.util.L;


/**
 * @author wzj
 * @version 2014-12-9 下午2:57:44
 */
public class ImageHttp {

	/**
	 * 通过网络加载的方式中,当加载成功时,会向文件缓存存入数据,所以该类中要有与之对应的FileCache类对象的引用
	 */
	private FileCache fileCache;

	public ImageHttp() {
		
	}
	
	public ImageHttp(FileCache fileCache) {
		this.fileCache = fileCache;
	}


	public void loadImage(String url, final int timeOut, final OnBitmap listener) {
		new LoadByHttp(timeOut, listener).execute(url);
	}

	private class LoadByHttp extends AsyncTask<String, Void, Bitmap> {
		int timeOut;
		OnBitmap listener;
		boolean isFinished = false;
		Timer timer;

		LoadByHttp(final int timeOut, final OnBitmap listener) {
			this.timeOut = timeOut;
			this.listener = listener;
			this.timer = new Timer();
		}

		@Override
		protected Bitmap doInBackground(String... params) {

			timer.schedule(new TimerTask() {

				@Override
				public void run() {
					// 超时处理
					if (!isFinished) {
						isFinished = true;
						listener.onBitmap(null);
						LoadByHttp.this.cancel(true);
					}
				}

			}, timeOut);

			String imageUrl = params[0];
			InputStream is = null;
			ByteArrayOutputStream baos = null;
			try {
				// is只能用一次,切记!!!
				is = new URL(imageUrl).openStream();
				Bitmap image;
				
				if(fileCache != null){
					// 存入本地缓存
					fileCache.setImage(imageUrl, is);
					image = fileCache.getImage(imageUrl);
				} else {
					image = BitmapFactory.decodeStream(is);
				}
				new L(image == null ? "image为null" : "image不为null");
				// 存入程序缓存
				AppCache.setImage(imageUrl, image);
				return image;
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					if (baos != null) {
						baos.close();
					}
					if (is != null) {
						is.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			return null;
		}

		@Override
		protected void onPostExecute(Bitmap result) {
			if (!isFinished) {
				isFinished = true;
				listener.onBitmap(result);
				timer.cancel();
			}
		}

	}

}
 


6. 面向开发者的最终类ImageLoader,包含了三中处理方式类的引用

package com.example.image.util.image;

import android.graphics.Bitmap;

import com.example.image.code.MD5;
import com.example.image.code.OnCode;
import com.example.image.util.L;
import com.example.image.util.image.dao.OnBitmap;

/**
 * 加载超时时间的三种获取策略:
 * 1. 若调用本类的loadImage方法时含有timeOut参数,则取该参数
 * 2. 若调用loadImage不含该参数,而构造方法中含该参数,则取该参数
 * 3. 若loadImage方法和构造方法都不含有该参数时,则取该类中的静态常量 TIME_OUT
 * @author wzj
 * @version 2014-12-9 上午9:36:37
 */
public class ImageLoader {

	private FileCache fileCache;
	private ImageHttp imageHttp;
	private int timeOut;
	// 默认超时时间是20秒
	private static final int TIME_OUT = 20000;
	
	/**
	 * 不开启文件缓存
	 */
	public ImageLoader(){
		this(TIME_OUT);
	}
	
	/**
	 * 不开启文件缓存
	 */
	public ImageLoader(int timeOut){
		this.timeOut = timeOut;
		imageHttp = new ImageHttp();
	}
	
	/**
	 * 开启文件缓存
	 * @param path: 缓存存放的路径
	 */
	public ImageLoader(String path) {
		this(path, TIME_OUT);
	}

	/**
	 * 开启文件缓存
	 * @param path: 缓存存放的路径
	 * @param timeOut: 设置默认超时时间
	 */
	public ImageLoader(String path, int timeOut) {
		this.timeOut = timeOut;
		fileCache = new FileCache(path, getCode());
		imageHttp = new ImageHttp(fileCache);
	}
	
	
	/**
	 * 可重写该方法来改变加密方式
	 * @return 
	 */
	protected OnCode getCode() {
		return new MD5();
	}
	
	
	/**
	 * 加载图片,加载结果会在接口中回调
	 * @param url
	 * @param listener
	 */
	public void loadImage(String url, OnBitmap listener){
		loadImage(url, timeOut, listener);
	}
	
	/**
	 * 加载图片,加载结果会在接口中回调
	 * @param url
	 * @param listener
	 * @param timeOut 设置此次加载的超时时间
	 */
	public void loadImage(String url, int timeOut, OnBitmap listener) {
		Bitmap image;

		// 1. 读取程序缓存
		if ((image = AppCache.getImage(url)) != null) {
			listener.onBitmap(image);
			new L("程序缓存取得的图片");
			return;
		}

		// 2. 读取文件缓存,并向程序缓存存入数据
		if (fileCache != null && (image = fileCache.getImage(url)) != null) {
			listener.onBitmap(image);
			new L("SD卡缓存取得的图片");
			return;
		}

		// 3. 读取网络资源,并向文件缓存和程序缓存存入数据
		imageHttp.loadImage(url, timeOut, listener);
		new L("网络取得的图片");

		
	}
	
	/**
	 * 清除缓存
	 */
	public boolean clearFileCache() {
		return fileCache == null ? false : fileCache.clearFileCache();
	}

}<strong style="color: rgb(255, 0, 0);">
</strong>


7. 老规矩,测试类MainActivity

package com.example.image;

import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

import com.example.image.util.image.ImageLoader;
import com.example.image.util.image.dao.OnBitmap;

public class MainActivity extends Activity {
//	private EditText _edit;
	private ImageView _image;
	private Button _button;
	private static final String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cache";
	
	private ImageLoader imageLoader;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		imageLoader = new ImageLoader(path);
		
//		_edit = (EditText) findViewById(R.id.edit);
		_image = (ImageView) findViewById(R.id.image);
		_button = (Button) findViewById(R.id.button);
		
		_button.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View v) {
				final long pre = System.currentTimeMillis();
				imageLoader.loadImage("http://www.baidu.com/img/bd_logo1.png", new OnBitmap() {
					
					@Override
					public void onBitmap(Bitmap image) {
						if(image != null){
							long time = System.currentTimeMillis() - pre;
							_button.setText("用时: " + time + " 毫秒");
							_image.setImageBitmap(image);
						} else {
							_button.setText("加载失败");
						}
					}
				});
			}
			
		});
		
	}

}<strong style="color: rgb(255, 0, 0);">
</strong>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值