Android加载图片列表,估计在很多应用中都会涉及到;固然,有许多开源框架提供了很方便的使用方法;并且相当稳定,但是如果不会自己实现,那么我相信遇到问题后就不会自己解决,只会依托于百度或者谷歌;这样,我认为不是你想要的结果吧;这几天我就自己设计实现了一下这个需求:
首先,分析如下:需求是加载图片列表,那么我需要完成以下的基本过程:
(一):下载图片
(二):显示图片
结合安卓自身的特点,下载图片过程不能在主线程;列表经过复用组件后,前面下载的东西如果不经过保存在本地那么就会造成二次下载;因此也需要缓存;缓存方案选择现在成熟的内存缓存方案和磁盘缓存方案;显示图片时需要根据页面对图片进行一定的优化,以优化内存占用;
因此我的具体实现如下:
首先将下载,下载回调;本地缓存寻找,寻找回调;分别实现在2个不同的线程实例;
因此实现了以下几个接口
//下载接口
public interface DownloadPictureTaskinf {
public Bitmap download(String url);
}
//下载回调
public interface DownloadPictureCallbackinf {
public void downloadCallback(Bitmap bitmap,String url);
}
//本地查找接口
public interface GetLoaclPictureTaskinf {
public Bitmap getLocalBitmap();
}
//本地查找回调接口
public interface GetLocalPictureCallbackinf {
public void getLocalCallback(Bitmap bitmap, String url);
}
线程池设计如下:
public class ThreadPool {
private static ThreadPool instance = new ThreadPool();
public static ThreadPool getInstance() {
return instance;
}
private ThreadPool() {
//下载的线程池
downloadservice = Executors.newFixedThreadPool(8);
//获取本地的线程池
getLocalservice = Executors.newFixedThreadPool(1);
}
private ExecutorService downloadservice, getLocalservice;
private Set<String> downloadSet = new HashSet<String>();
private Set<String> getlocalTask = new HashSet<String>();
//提交下载任务
public void submitDownloadRunnable(
final DownloadPictureTaskinf downloadPictureTaskinf,
final DownloadPictureCallbackinf downloadPictureCallbackinf,
final String url) {
if (!downloadSet.contains(url)) {
synchronized (downloadSet) {
if (!downloadSet.contains(url)) {
downloadservice.submit(new Runnable() {
@Override
public void run() {
Bitmap temp = null;
temp = downloadPictureTaskinf.download(url);
downloadPictureCallbackinf.downloadCallback(temp,
url);
downloadSet.remove(url);
}
});
}
}
}
}
//提交查找本地任务
public void submitgetLocalRunnable(
final GetLoaclPictureTaskinf getLoaclPictureTaskinf,
final GetLocalPictureCallbackinf getLocalPictureCallbackinf,
final String url) {
if (!getlocalTask.contains(url)) {
synchronized (getlocalTask) {
if (!getlocalTask.contains(url)) {
getlocalTask.add(url);
getLocalservice.submit(new Runnable() {
@Override
public void run() {
Bitmap temp = null;
temp = getLoaclPictureTaskinf.getLocalBitmap();
getLocalPictureCallbackinf.getLocalCallback(temp,
url);
getlocalTask.remove(url);
}
});
}
}
}
}
}
下载图片类如下:
public class DownloadPictureTask implements DownloadPictureTaskinf,
GetLoaclPictureTaskinf, DownloadPictureCallbackinf,
GetLocalPictureCallbackinf {
private Handler handler;
private FileCache cache;
private String url;
private DownloadPictureCallbackinf callbackinf;
private GetLocalPictureCallbackinf callbackinf2;
public DownloadPictureTask(Context context, FileCache cache, String url,
DownloadPictureCallbackinf callbackinf,
GetLocalPictureCallbackinf callbackinf2) {
this.cache = cache;
this.url = url;
this.callbackinf = callbackinf;
this.callbackinf2 = callbackinf2;
handler = new Handler(context.getMainLooper());
}
//当本地没有时,将下载任务提交到下载的线程池中
@Override
public void getLocalCallback(final Bitmap bitmap, final String url) {
Log.v("本地==", bitmap + ":" + url);
if (bitmap == null) {
Log.v("本地为空", url);
// 启动下载任务
ThreadPool.getInstance().submitDownloadRunnable(this, this, url);
} else {
handler.post(new Runnable() {
@Override
public void run() {
callbackinf2.getLocalCallback(bitmap, url);
}
});
}
}
//下载成功回调
@Override
public void downloadCallback(final Bitmap bitmap, final String url) {
handler.post(new Runnable() {
@Override
public void run() {
callbackinf.downloadCallback(bitmap, url);
}
});
}
//从本地缓存查找实现
@Override
public Bitmap getLocalBitmap() {
Log.v("本地任务开启", url);
Bitmap bmp = cache.getCacheFromMemory(url);
if (bmp == null) {
bmp = BitmapFactory.decodeStream(cache.getCacheFromDisk(url));
}
return bmp;
}
//下载实现
@Override
public Bitmap download(String url) {
Log.v("下载任务开启", url);
Bitmap bmp = null;
try {
URL mUrl = new URL(url);
InputStream ins = mUrl.openConnection().getInputStream();
//加入到本地缓存
bmp = cache.addCache(url, ins);
} catch (Exception e) {
e.printStackTrace();
}
return bmp;
}
//启动查找本地任务
public void execute() {
ThreadPool.getInstance().submitgetLocalRunnable(this, this, url);
}
}
下面是缓存实现类:
/**
* 借用了LruCache和DiskLruCache
* 文件名就是下载链接的MD5值
* @author 徐晔
* @note 文件缓存类,集合了内存缓存和磁盘缓存
*/
public class FileCache {
private final static int MEMMAXSIZE = 1024 * 30;
private final static int DISKMAXSIZE = 10*1024 * 1024;
/** 内存缓存 */
private LruCache<String, Bitmap> memoryCache;
/** 磁盘缓存 */
private DiskLruCache diskCache;
public FileCache(Context context, int appVersion) {
memoryCache = new LruCache<String, Bitmap>(MEMMAXSIZE);
try {
diskCache = DiskLruCache.open(
Util.getDiskCacheDir(context, "cache"), appVersion, 1,
DISKMAXSIZE);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 添加缓存
*
* @note 首先添加到磁盘中,然后添加到内存中
*/
public Bitmap addCache(String key, InputStream in) {
if(in==null){
return null;
}
key = MD5Utils.getMD5(key);
DiskLruCache.Editor editor = null;
Bitmap bitmap = null;
ByteArrayOutputStream baos=null;
OutputStream mOutputStream=null;
try {
editor = diskCache.edit(key);
baos=new ByteArrayOutputStream();
if (editor != null) {
mOutputStream = editor.newOutputStream(0);
}
int b;
while ((b = in.read()) != -1) {
if (editor != null) {
mOutputStream.write(b);
}
baos.write(b);
}
editor.commit();
bitmap=BitmapDecoder.decoderFromByteArray(baos.toByteArray(), 300, 300);
memoryCache.put(key, bitmap);
} catch (Exception e) {
try {
editor.abort();
} catch (Exception e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
// diskCache.flush();
baos.close();
in.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
return bitmap;
}
/**
* 在内存中取出缓存
*
*/
public InputStream getCacheFromDisk(String key) {
key = MD5Utils.getMD5(key);
InputStream ins = null;
try {
Snapshot snapshot = diskCache.get(key);
if (snapshot != null) {
ins = snapshot.getInputStream(0);
}
} catch (Exception e) {
e.printStackTrace();
}
return ins;
}
/**
* 在磁盘中取出缓存
*/
public Bitmap getCacheFromMemory(String key) {
key = MD5Utils.getMD5(key);
return memoryCache.get(key);
}
}
图片配置类:
/**
*
* @author 徐晔
* @note 根据配置对图片进行编码
*/
public class BitmapDecoder {
/**
* 将流转化为Bitmap文件
*
* @param inputStream
* :输入流
* @param rect
* :
* @param realwidth
* :想要获取图片的宽度
* @param realheight
* :想要获取的图片的高度
* @return
* @throws Exception
* :主要指内存溢出异常
*/
public static Bitmap decoderFromByteArray(byte[] butearray,
int realwidth, int realheight) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
BitmapFactory.decodeByteArray(butearray, 0, butearray.length, options);
options.inJustDecodeBounds = false;
options.inSampleSize = calculateInSampleSize(options, realwidth,
realheight);
try {
return BitmapFactory.decodeByteArray(butearray, 0, butearray.length, options);
} catch (Exception e) {
return null;
}
}
/**
* 转化文件大小
*/
private static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
final float totalPixels = width * height;
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
}
适配器:
public class ImageAdapter extends BaseAdapter {
private LayoutInflater inflater;
private FileCache cache;
private DownloadPictureTask task;
private Context context;
public ImageAdapter(Context context) {
this.context = context;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
cache = new FileCache(context, 1);
}
@Override
public int getCount() {
return Consts.image.length;
}
@Override
public String getItem(int position) {
return Consts.image[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.image_item, null);
viewHolder.ii_item = (ImageView) convertView
.findViewById(R.id.ii_item);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
final String key = (String) getItem(position);
viewHolder.ii_item.setTag(key);
viewHolder.ii_item.setBackgroundDrawable(null);
//启动异步任务
task = new DownloadPictureTask(context, cache, key,
new DownloadPictureCallbackinf() {
@Override
public void downloadCallback(Bitmap bitmap, String url) {
if (viewHolder.ii_item != null
&& viewHolder.ii_item.getTag().toString()
.equals(url)) {
viewHolder.ii_item.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
}
}, new GetLocalPictureCallbackinf() {
@Override
public void getLocalCallback(Bitmap bitmap, String url) {
if (viewHolder.ii_item != null
&& viewHolder.ii_item.getTag().toString()
.equals(url)) {
viewHolder.ii_item.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
}
});
task.execute();
return convertView;
}
class ViewHolder {
ImageView ii_item;
}
}
主类文件:
public class ImageActiviy extends Activity {
private GridView ai_list;
private ImageAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
ai_list = (GridView) findViewById(R.id.ai_list);
adapter = new ImageAdapter(this);
ai_list.setAdapter(adapter);
}
}
以下是截图:
这个就是加载图片列表的大概过程;其中缓存借用了LruCache和DiskLruCache,这样不但节省了网络流量,还大大的加快了图片的加载速度;优化了用户体验;需要注意的是,在对异步任务进行提交时,有没有现有的任务在运行;不管是下载图片还是从本地磁盘取文件,需要避免重复下载和重复读取一个文件的问题;这两者一个浪费流量;另一个会造成系统卡顿,因为同时提交多个读取文件的操作特别容易引起APP卡顿;因此我这边从本地缓存读取文件的异步任务最大运行数量为1;