在最初的Android学习中,对于从网络上去下载图片是一个比较繁琐的操作,需要自己去开启子线程执行下载。假如是一两张图片还好,如果是成百上千张图片同时去加载,这将会给系统带来很大的资源开销,好在总有“大神”存在,现在已经有很多开源框架可以去解决网络加载图片的问题。
Volley框架是一个使用率较高的第三方开源框架,在Volley中有ImageRequest类可以实现图片加载,但是ImageRequest并未进行图片的缓存,如果是重复的去加载图片这将会给用户带来额外的流量开销。Volley中还有一个ImageLoader内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。ImageLoader的用法主要分为如下四步:
1. 创建一个RequestQueue对象
2. 创建ImageLoader对象
3. 创建ImageListener对象
4. 调用ImageLoader中的get()方法加载图片
一、创建RequestQueue
创建一个自定义的MyApplication类(名字自定义)继承Application类,重写onCreate()方法,创建RequestQueue。
public class MyApplication extends Application {
private static RequestQueue requestQueue;
@Override
public void onCreate() {
super.onCreate();
requestQueue = Volley.
newRequestQueue(getApplicationContext());
}
public static RequestQueue getQueue(){
return requestQueue;
}
}
这种创建的RequestQueue是一个全局的变量,还得再清单文件的application标签中添加name属性,如下所示:
<application
android:name="com.example.imageloader.MyApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
二、创建ImageLoader
ImageLoader的构造方法如下:
ImageLoader(RequestQueue queue, ImageCache imageCache)
第一参数是一个RequestQueue对象,在第一部已经创建好了,第二个参数是ImageCache缓存对象,实现图片的缓存,但是ImageCache是一个接口,这里需要去创建一个类实现该接口,在该接口中有两个方法getBitmap()和putBitmap需要实现,分别是获取缓存中的图片和将图片存入缓存。
使用ImageCache时还需要使用到Android提供的缓存类工具LruCache,将图片以键值对的方式存储在LruCache中,在BitmapCache的构造方法中创建LruCache对象并重写sizeOf()方法。
public class BitmapCache implements ImageCache {
// LruCatch是Android提供的缓存工具
private LruCache<String, Bitmap> mCache;
public BitmapCache(){
// 获取系统给应用程序分配的内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
// 取1/8作为图片缓存
int maxCatch = maxMemory / 8;
// 创建缓存
mCache = new LruCache<String, Bitmap>(maxCatch){
//测量Bitmap的大小 ,便于统计缓存使用总量
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getWidth() * value.getHeight();
}
};
}
/**
* 获取缓存的图片
* url:下载图片时传入的图片网址
*/
@Override
public Bitmap getBitmap(String url) {
}
/**
* 缓存图片,以url作为key值
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
}
sizeOf()方法是用来测量下载的图片的大小,每当下载一个图片后就会在图片使用缓存的总数上加上该图片的大小,以便监控缓存的使用情况,当缓存使用超过设定值后将会自动对缓存进行清理。
下面去实现图片在内存中的存储和读取操作,如下所示:
/**
* 获取缓存的图片
* url:下载图片时传入的图片网址
*/
@Override
public Bitmap getBitmap(String url) {
return mCache.get(url);
}
/**
* 缓存图片,以url作为key值
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
mCache.put(url, bitmap);
}
接下来就可以创建ImageLoader对象,我们也可以在MyApplication中创建ImageCache对象,把ImageCache设定成全局的。
public class MyApplication extends Application {
private static RequestQueue requestQueue;
public static BitmapCache bitmapCatch = new BitmapCache();
@Override
public void onCreate() {
super.onCreate();
requestQueue = Volley.newRequestQueue(getApplicationContext());
}
public static RequestQueue getQueue(){
return requestQueue;
}
}
创建ImageLoader对象。
loader = new ImageLoader(MyApplication.getQueue(),MyApplication.bitmapCatch);
三、创建ImageListener
ImageListener是用来对图片加载的一个监听类,通过ImageListener可以设定图片加载中、加载失败的图片。
ImageListener listener = ImageLoader.getImageListener(iv1, R.drawable.default,
R.drawable.failed);
第一个参数传入显示图片的ImageView空间对象,第二个参数出入加载过程中现实的图片id,第三个参数传入显示加载失败图片的id。
四、调用get()方法
最后只需要利用ImageLoader对象调用get(String requestUrl, final ImageListener listener)方法,分别传入图片的网址和ImageListener对象即可。
下面我们来看下效果,具体的布局因人而异,这里就不做介绍。
虽然按照上面的步骤达到了最终的效果,图片加载成功了,但是其中图片加载与缓存的一个过程是怎样的呢?下面就简单的把它的逻辑过程做一个罗列。
1. UI请求数据,调用BitmapCache中get()方法,使用唯一的Key值索引Memory Cache中的Bitmap,即参入的String参数。
2. 缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
3. 下载图片:启动异步线程,从数据源下载数据(Web)。
4. 若下载成功,将数据同时写入缓存,并将Bitmap显示在UI中。
但是上面并没有将数据缓存到本地,如要再加一级本地缓存只需要再去定义两个方法,一个写入SD,另一个读SD卡,需要注意的是读取缓存时内存缓存优先于硬盘缓存。
1. UI请求数据,调用BitmapCache中get()方法,使用唯一的Key值索引Memory Cache中的Bitmap,即参入的String参数;
2. 缓存搜索,如果能找到Key值对应的Bitmap,则返回数据,否则执行第三步;
3. 使用唯一Key值对应的文件名,检索SDCard上的文件;
4. 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步
5. 启动异步线程下载图片,从数据源下载数据(Web);
6. 若下载成功,将数据同时写入缓存,并将Bitmap显示在UI中。
代码如下:
/**
* 获取缓存的图片
* url:下载图片时传入的图片网址
*/
@Override
public Bitmap getBitmap(String url) {
Bitmap bitmap = mCache.get(url);
Log.d("Tag","获取缓存 = "+bitmap);
// 缓存没有就从SD卡获取
if(bitmap == null){
// 获取图片的名字
String imageName = url.substring(url.lastIndexOf("/") + 1);
// 返回值不为空说明SD有对应图片,并存入缓存
bitmap = getBitmapFromSD(imageName);
if(bitmap != null){
mCache.put(url, bitmap);
}
}
return bitmap;
}
/**
* 缓存图片,以url作为key值
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
Log.d("Tag", "存入緩存");
mCache.put(url, bitmap);
// 网络下载后存入SD
saveBitmapToSD(url.substring(url.lastIndexOf("/")+1), bitmap);
}
/**
* 从SD卡获取图片
* @param imageName
* @return
*/
public Bitmap getBitmapFromSD(String imageName){
Bitmap bitmap = null;
File fileDir = new File(CACHEPATH);
if(fileDir.exists()){
File files[] = fileDir.listFiles();
if(files != null){
for (int i = 0; i < files.length; i++) {
if((files[i].getName()).equals(imageName)){
bitmap = BitmapFactory. decodeFile(CACHEPATH
+"/"+imageName);
break;
}
}
}
}
return bitmap;
}
/**
* 保存数据到sd卡
* @param name
* @param bitmap
*/
public void saveBitmapToSD(final String name,final Bitmap bitmap){
// 开启线程写入SD卡
new Thread(new Runnable() {
@Override
public void run() {
// 图片缓存目录
File fileDir = new File(CACHEPATH);
// 不存在就创建该目录
if(!fileDir.exists()){
fileDir.mkdir();
}
File fileImage = new File(fileDir, name);
FileOutputStream out = null;
try {
out = new FileOutputStream(fileImage);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}