对于社交和资讯类应用,从网络获取图片并显示的功能必不可少了,异步加载图片和缓存,可以加快图片的显示和操作的流畅度,提高用户体验。
我先写了一个图片加载管理器ImageLoaderManager.java
代码如下:
package com.xhq.common;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v4.util.LruCache;
import android.util.Log;
public class ImageLoaderManager
{
private final String TAG = "ImageLoaderManager";
private LruCache<String, Bitmap> mCache = null;
private String cacheDir = null;
public ImageLoaderManager(LruCache<String, Bitmap> cache)
{
this.mCache = cache;
}
public void setCacheDir(String dir)
{
Log.d(TAG, "缓存目录:" + dir);
this.cacheDir = dir;
}
public Bitmap getBitmapFromUrl(String url)
{
Log.d(TAG, "getBitmapFromUrl:" + url);
Bitmap bitmap = null;
try
{
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
Log.d(TAG, "save to cache:" + url);
mCache.put(url, bitmap);
String fileName = getMD5Str(url);
String filePath = this.cacheDir + fileName;
try
{
FileOutputStream fos = new FileOutputStream(filePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
Log.d(TAG, "save to file:" + filePath);
}catch(Exception e)
{
Log.d(TAG, " fail to save file:" + filePath);
}
is.close();
conn.disconnect();
return bitmap;
} catch (IOException e)
{
e.printStackTrace();
return null;
}
}
public Bitmap getBitmapFromCache(String url)
{
Bitmap bitmap = null;
synchronized (mCache)
{
if (mCache.get(url) != null)
{
bitmap = mCache.get(url);
if (bitmap != null)
{
return bitmap;
}
}
Log.d(TAG, "get from cache:" + url);
bitmap = getBitmapFromFile(url);
if (bitmap != null)
{
mCache.put(url, bitmap);
}
return bitmap;
}
}
private Bitmap getBitmapFromFile(String url)
{
Bitmap bitmap = null;
String fileName = getMD5Str(url);
if (fileName == null)
return null;
String filePath = cacheDir + "/" + fileName;
try
{
FileInputStream fis = new FileInputStream(filePath);
bitmap = BitmapFactory.decodeStream(fis);
} catch (FileNotFoundException e)
{
e.printStackTrace();
bitmap = null;
}
return bitmap;
}
/**
* MD5 加密
*/
private static String getMD5Str(String str)
{
MessageDigest messageDigest = null;
try
{
messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update(str.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e)
{
System.out.println("NoSuchAlgorithmException caught!");
return null;
} catch (UnsupportedEncodingException e)
{
e.printStackTrace();
return null;
}
byte[] byteArray = messageDigest.digest();
StringBuffer md5StrBuff = new StringBuffer();
for (int i = 0; i < byteArray.length; i++)
{
if (Integer.toHexString(0xFF & byteArray[i]).length() == 1)
md5StrBuff.append("0").append(
Integer.toHexString(0xFF & byteArray[i]));
else
md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
}
return md5StrBuff.toString();
}
}
这个图片加载管理器功能主要是通过图片URL加载图片,分为从内存中加载图片和从网络加载图片,内存加载又分为从cache加载和从文件加载,先从cache加载,如果没有该图片就从文件加载。
从网络加载图片后会把图片保存到文件中,下次再加载相同的url图片时就不用从网络加载了。这里主要说一下LruCache 和MD5加密。
LruCache 使用了硬引用和Lru算法,Cache保存一个强引用来限制内容数量,每当Item被访问的时候,此Item就会移动到队列的头部。当cache已满的时候加入新的item时,在队列尾部的item会被回收。
这里对图片的URL进行MD5加密主要是为了更方便的在文件中保存和获取图片,因为url的和文件的路径比较相似,系统会识别不了正确的图片路径。
有了图片加载管理器,我们就进行图片的异步加载AsyncImageLoader.java
package com.xhq.common;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.xhq.xweibo.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
public class AsyncImageLoader
{
private final String TAG = "AsyncImageLoader";
private static HashSet<String> mLoadingUrlSet;
private LruCache<String, Bitmap> mMemoryCache;
private static ImageLoaderManager mImldmg;
private static ExecutorService mExecutorService;
private Handler mHandler;
private Activity mActitity;
public interface onLoadCompleteListener
{
public void onLoadSuccess(View view,Bitmap bmp, String url);
public void onLaodFail(View view,String url);
}
public AsyncImageLoader(Context context)
{
mLoadingUrlSet = new HashSet<String>();
mHandler = new Handler();
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;// 给LruCache分配1/8 4M
mMemoryCache = new LruCache<String, Bitmap>(cacheSize)
{
// 必须重写此方法,来测量Bitmap的大小
@Override
protected int sizeOf(String key, Bitmap value)
{
return value.getRowBytes() * value.getHeight();
}
};
mImldmg = new ImageLoaderManager(mMemoryCache);
Log.d(TAG, "cachesize:" + cacheSize/1024/1024+"MB");
startThreadPool(2);
String appname = context.getResources().getString(R.string.app_name);
String sdDir = "/sdcard/"+appname+"/cache/";
String dataDir = context.getCacheDir().getAbsolutePath()+"/";
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
if(FileUtil.isDirExist(sdDir, true))
{
setCacheDir(sdDir);
}
}else
{
setCacheDir(dataDir);
}
}
public void setCacheDir(String dir)
{
mImldmg.setCacheDir(dir);
}
/** 开启线程池 */
public static void startThreadPool(int poolNum)
{
if (mExecutorService == null || mExecutorService.isShutdown()
|| mExecutorService.isTerminated())
{
mExecutorService = Executors.newFixedThreadPool((poolNum == 0) ? 1
: poolNum);
}
}
public void loadImage(final ImageView image, final String url,
final onLoadCompleteListener onLoadComplete)
{
if (mLoadingUrlSet.contains(url))
{
Log.d(TAG, url + " is loading");
return;
}
Bitmap bitmap = null;
bitmap = mImldmg.getBitmapFromCache(url);
if (bitmap != null)
{
if (onLoadComplete != null)
{
onLoadComplete.onLoadSuccess(image, bitmap, url);
}
} else
{
// 从网络端下载图片
mLoadingUrlSet.add(url);
mExecutorService.submit(new Runnable()
{
@Override
public void run()
{
final Bitmap bitmap = mImldmg.getBitmapFromUrl(url);
mHandler.post(new Runnable()
{
@Override
public void run()
{
if (onLoadComplete != null)
{
if(bitmap != null)
{
onLoadComplete.onLoadSuccess(image,bitmap, url);
}
else
{
onLoadComplete.onLaodFail(image,url);
}
}
mLoadingUrlSet.remove(url);
}
});
}
});
}
}
}
这个图片异步加载类主要使用了线程、线程池和Handler,主要步骤:
1、设置LruCache的大小cachesize,当缓存的图片大于cachesize时,就会把队列未不的图片释放掉
2、设置图片文件的缓存路径,如果有SD卡,我们就设置缓存路径为 sdDir = "/sdcard/"+appname+"/cache/";
否则就设置缓存路径为 dataDir = context.getCacheDir().getAbsolutePath()+"/";
我们可以通过 Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 来判定是否有SD卡
3、开启线程池,由于微博从网络加载图片是比较频繁的,每加载一张图片都和服务器建立一个连接,那么服务器的压力就很大了,所以这里运用线程池来限制连接数
线程池我们用了ExecutorService 来管理连接线程
4、最后就是在线程中加载图片了,这里运用了Thread 和Handler 来完成异步通知,当加载图片成功或者失败会通过Handler来通知UI线程更新界面