写的一个图片缓存的demo,包括内存缓存和硬盘缓存,加载大量图片的时候感觉效果还是挺好的。
直接上代码吧:
package com.hongri.recyclerview.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.hongri.recyclerview.R;
import com.hongri.recyclerview.adapter.DetailLoadPicsTestAdapter;
import com.hongri.recyclerview.common.APPConstants;
import com.hongri.recyclerview.utils.DataUtil;
/**
* @author:zhongyao on 2016/7/22 10:52
* @description:网络加载图片性能测试
*/
public class DetailLoadPicsTestFragment extends Fragment{
private static DetailLoadPicsTestFragment loadPicsTestFragment;
private RecyclerView rv;
private DetailLoadPicsTestAdapter mAdapter;
private DetailLoadPicsTestFragment(){}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_detail_loadpicstest,container,false);
initView(view);
return view;
}
private void initView(View view) {
rv = (RecyclerView) view.findViewById(R.id.rv);
rv.setLayoutManager(new GridLayoutManager(getActivity(), APPConstants.Column_Nums));
mAdapter = new DetailLoadPicsTestAdapter(getActivity(), DataUtil.getImageThumbUrls());
rv.setAdapter(mAdapter);
}
public static Fragment newInstance() {
if (loadPicsTestFragment == null){
synchronized (DetailLoadPicsTestFragment.class){
if (loadPicsTestFragment == null){
loadPicsTestFragment = new DetailLoadPicsTestFragment();
}
}
}
return loadPicsTestFragment;
}
}
package com.hongri.recyclerview.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.hongri.recyclerview.R;
import com.hongri.recyclerview.cache.BitmapCache;
import com.hongri.recyclerview.cache.BitmapDiskCache;
import com.hongri.recyclerview.cache.ImageWorker;
import com.hongri.recyclerview.https.HttpRequest;
import com.hongri.recyclerview.https.IHttpRequest;
import com.hongri.recyclerview.utils.Logger;
import java.util.ArrayList;
/**
* @author:zhongyao on 2016/7/22 11:09
* @description:
*/
public class DetailLoadPicsTestAdapter extends RecyclerView.Adapter<DetailLoadPicsTestAdapter.LoadPicsViewHolder> {
private Context context;
private ArrayList<String> imageUrls;
private LayoutInflater inflater;
private Handler mHandler = new Handler();
public DetailLoadPicsTestAdapter(Context context, ArrayList<String> imageUrls) {
this.context = context;
this.imageUrls = imageUrls;
this.inflater = LayoutInflater.from(context);
}
@Override
public LoadPicsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.fragment_detail_loadpicstest_item, parent, false);
return new LoadPicsViewHolder(view);
}
@Override
public void onBindViewHolder(final LoadPicsViewHolder holder, final int position) {
/**
* 使用自己写的内存缓存+SD卡缓存(貌似效果甚至比使用DiskLruCache的效果好,这就比较蛋疼了,没看出DiskLruCache的优点)
*/
ImageWorker.loadImage(holder.iv,imageUrls.get(position) ,mHandler);
}
@Override
public int getItemCount() {
return imageUrls.size();
}
public class LoadPicsViewHolder extends ViewHolder {
private ImageView iv;
public LoadPicsViewHolder(View itemView) {
super(itemView);
iv = (ImageView) itemView.findViewById(R.id.iv);
}
}
}
以上就是使用RecyclerView实现的GridView,真正加载缓存图片的是:ImageWorker.loadImage(holder.iv,imageUrls.get(position),mHandler);这行代码。
下面我们着重介绍ImageWorker等类,内存缓存用的是LruCache类,sd卡缓存是自己写的,测试效果貌似不比用DiskLruCache差。(最后我会给出demo,demo中也用到了DiskLruCache做对比,有兴趣的可以自己测试下性能,对比下,欢迎交流)。
package com.hongri.recyclerview.cache;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
import com.hongri.recyclerview.https.DownloadImageFromNetwork;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 图片缓存类ImageWorker:
* 与ImageTask类相似
*/
public class ImageWorker {
private static final int lruSizeMaxSize = 10*1024*1024;// 图片内存缓存最大空间10M
public final static LruCache<String,Bitmap> mLruCache = new LruCache<String,Bitmap>(lruSizeMaxSize);
private static ExecutorService mExecutorService = null;// 下载图片的线程管理类
private static final int threadPoolMaxSize = 6;// 同时下载图片的 最大线程数
private ImageWorker() {
}
private static ImageWorker mImageWorker = null;
private static String bitmapPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/zhong/";// 图片在硬盘中的存储地址
/**
* 初始化,使用单例获得ImageWorker1的对象
*/
public static synchronized ImageWorker getImageWorkerInstance(){
if(mImageWorker == null){
mImageWorker = new ImageWorker();
mExecutorService = Executors.newFixedThreadPool(threadPoolMaxSize);
}
return mImageWorker;
}
/**
* (简单来说)加载图片:
* 1 先从内存缓存中查找,如果没有则执行下一步。
* 2 从磁盘中查找,如果没有执行下一步。
* 3从网络下载
*/
/**
* 1、首先判断内存缓存是否为空(不为空直接显示)
* 2、内存缓存为空时,再从磁盘中读取后,将图片添加到内存缓存中(然后显示)
* 3、磁盘为空时,只能重新网络加载图片(下载下来的图片,会在内存缓存、磁盘中各存储一份,然后显示)
* @param mImageView
* @param imgUrl
* @param mHandler
*/
public static void loadImage(ImageView mImageView, String imgUrl,
Handler mHandler) {
getImageWorkerInstance();
Bitmap bitmapCache = getBitmapFromCache(imgUrl);
if(bitmapCache!=null){
mImageView.setImageBitmap(bitmapCache);
}else {
Bitmap bitmapDisk = getBitmapFromDisk(imgUrl);
if(bitmapDisk!=null){
addBitmapToLruCache(imgUrl,bitmapDisk);
mImageView.setImageBitmap(bitmapDisk);
}else{
loadImageFromNetwork(mImageView,imgUrl,mHandler,mLruCache,bitmapPath);
}
}
}
private static void loadImageFromNetwork(ImageView mImageView, String imgUrl, Handler mHandler, LruCache<String, Bitmap> mLruCache, String bitmapPath) {
mExecutorService.submit(new DownloadImageFromNetwork(mImageView,imgUrl,mHandler,mLruCache,bitmapPath));
}
private static void addBitmapToLruCache(String imgUrl, Bitmap bitmapDisk) {
mLruCache.put(imgUrl,bitmapDisk);
}
private static Bitmap getBitmapFromDisk(String imgUrl) {
InputStream inputStream = null;
String[] fileNames = imgUrl.split("/");
String fileName = fileNames[fileNames.length-1];
try {
inputStream = new FileInputStream(bitmapPath+fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
/**
* 第一步:尝试从缓存中获取图片
* @param imgUrl
* @return
*/
private static Bitmap getBitmapFromCache(String imgUrl) {
Bitmap bitmap = mLruCache.get(imgUrl);
return bitmap;
}
}
说的很清晰了,还是那一套:先内存找,没有则sd,sd没有,则网络加载。
这里用到了ExecutorService线程管理类,可以设置启动最大的线程数,线程对象可重复利用,避免过多的重复创建线程对象,浪费内存空间。
package com.hongri.recyclerview.https;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author:zhongyao on 2016/7/11 10:39
* @description:从网络下载图片,并将下载好的图片存储到内存缓存、SD卡缓存中
*/
public class DownloadImageFromNetwork implements Runnable {
private ImageView mImageView;
private String imgUrl;
private Handler mHandler;
private LruCache<String, Bitmap> mLruCache;
private String bitmapPath;
public DownloadImageFromNetwork(ImageView mImageView, String imgUrl,
Handler mHandler, LruCache<String, Bitmap> mLruCache, String bitmapPath) {
this.mImageView = mImageView;
this.imgUrl = imgUrl;
this.mHandler = mHandler;
this.mLruCache = mLruCache;
this.bitmapPath = bitmapPath;
}
@Override
public void run() {
try {
InputStream inputStream;
URL url = new URL(imgUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(10 * 1000);
conn.setConnectTimeout(10 * 1000);
conn.setDoInput(true);
conn.connect();// Starts the query
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
inputStream = conn.getInputStream();
if (inputStream != null) {
final Bitmap bitmapNetwork = BitmapFactory.decodeStream(inputStream);
mHandler.post(new Runnable() {
@Override
public void run() {
mImageView.setImageBitmap(bitmapNetwork);
}
});
/**
* 此时输入流inputStream不存在了(输入流使用一次后就没有了),
* 所以以下两行传送的代码是Bitmap对象
*/
addBitmapToCache(imgUrl, bitmapNetwork);
addBitmapToDisk(imgUrl, bitmapNetwork);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void addBitmapToDisk(String imgUrl, Bitmap bitmapNetwork) {
OutputStream outputStream = null;
BufferedOutputStream bos = null;
InputStream inputStream = null;
File file = new File(bitmapPath);
if (!file.exists()) {
boolean result = file.mkdirs();
}
try {
String[] fileNames = imgUrl.split("/");
String fileName = fileNames[fileNames.length - 1];
outputStream = new FileOutputStream(bitmapPath + fileName);
bos = new BufferedOutputStream(outputStream);
/**
* 以下三行的代码表示:
* 将Bitmap对象转换为inputStream
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmapNetwork.compress(Bitmap.CompressFormat.JPEG, 100, baos);
inputStream = new ByteArrayInputStream(baos.toByteArray());
byte[] data = new byte[1024];
while (inputStream.read(data) != -1) {
bos.write(data);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bos.close();
outputStream.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void addBitmapToCache(String imgUrl, Bitmap bitmapNetwork) {
mLruCache.put(imgUrl, bitmapNetwork);
}
}
该类需要注意的是,下载完图片并展示的时候,需要将其加载到内存和sd卡缓存中。以后再次加载的时候直接get就可以了。
效果图:
github上的demo:点击查看源码