最近想把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;
}
}