/**
* GridView的适配器,负责异步从网络上下载图片展示在照片墙上。
*/
public class PhotoWallAdapter extends BaseAdapter {
private String[] urls;
private Context context;
/**记录所有正在下载或等待下载的任务 */
private Set<BitmapWorkerTask> taskCollection;
/**图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉 */
private LruCache<String, Bitmap> mMemoryCache;
/**图片硬盘缓存核心类 */
private DiskLruCache mDiskLruCache;
/**记录每个子项的高度 */
private int mItemHeight = 0;
/** GridView的实例 */
private GridView mPhotoWall;
//******************************************************************************************************************************
public PhotoWallAdapter(Context context, String[] urls, GridView mPhotoWall) {
this.context = context;
this.urls = urls;
this.mPhotoWall = mPhotoWall;
taskCollection = new HashSet<BitmapWorkerTask>();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
// 设置图片缓存大小为程序最大可用内存的1/8
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
// 获取图片缓存路径
File cacheDir = getDiskCacheDir(context, "thumb");
if (!cacheDir.exists()) cacheDir.mkdirs();
// 创建DiskLruCache实例,初始化缓存数据
try {
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
//******************************************************************************************************************************
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) view = LayoutInflater.from(context).inflate(R.layout.photo_layout, null);
else view = convertView;
ImageView imageView = (ImageView) view.findViewById(R.id.photo);
imageView.setTag(urls[position]);// 给ImageView设置一个Tag,保证异步加载图片时不会乱序
imageView.setImageResource(R.drawable.ic_launcher);
//每次加载图片的时候都优先去内存缓存当中读取,当读取不到的时候则回去硬盘缓存中读取,而如果硬盘缓存仍然读取不到的话,就从网络上请求原始数据
//不管是从硬盘缓存还是从网络获取,读取到了数据之后都添加到内存缓存当中,这样的话我们下次再去读取图片的时候就能迅速从内存当中读取到
loadBitmaps(imageView, urls[position]);
return view;
}
@Override
public int getCount() {
return urls == null ? 0 : urls.length;
}
@Override
public Object getItem(int position) {
return urls == null ? null : urls[position];
}
@Override
public long getItemId(int position) {
return position;
}
//******************************************************************************************************************************
/**
* 将一张图片存储到LruCache中。
* @param key LruCache的键,这里传入图片的URL地址。
* @param bitmap LruCache的值,这里传入从网络上下载的Bitmap对象。
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) mMemoryCache.put(key, bitmap);
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
* @param key LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
*/
public void loadBitmaps(ImageView imageView, String imageUrl) {
try {
//从内存中获取缓存,如果获取到了则直接调用ImageView的setImageBitmap()方法将图片显示到界面上
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap != null) imageView.setImageBitmap(bitmap);
else {//如果内存中没有获取到,则开启一个BitmapWorkerTask任务来去异步加载图片
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);//记录所有正在下载或等待下载的任务
task.execute(imageUrl);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 取消所有正在下载或等待下载的任务。
*/
public void cancelAllTasks() {
if (taskCollection != null) {
for (BitmapWorkerTask task : taskCollection) {
task.cancel(false);
}
}
}
/**
* 根据传入的uniqueName获取硬盘缓存的路径地址。
*/
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
//当SD卡【存在】或者SD卡【不可被移除】时
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();//【SDCard/Android/data/包名/cache/】目录
} else cachePath = context.getCacheDir().getPath();//【/data/data/包名/cache/】目录
return new File(cachePath + File.separator + uniqueName);
}
/**
* 获取当前应用程序的版本号。
*/
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 设置item子项的高度。
*/
public void setItemHeight(int height) {
if (height == mItemHeight) return;
mItemHeight = height;
notifyDataSetChanged();
}
/**
* 使用MD5算法对传入的key进行加密并返回。
*/
public String hashKeyForDisk(String key) {
String cacheKey;
try {
//为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());//使用指定的 byte 数组更新摘要
byte[] bytes = mDigest.digest();//通过执行诸如填充之类的最终操作完成哈希计算,返回存放哈希值结果的 byte 数组
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);//以十六进制无符号整数形式返回一个整数参数的字符串表示形式
if (hex.length() == 1) sb.append('0');
sb.append(hex);
}
cacheKey = sb.toString();
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
/**
* 将缓存记录同步到journal文件中。
*/
public void fluchCache() {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//**********************************************************************************************************************
// 异步下载图片,并使用DiskLruCache进行缓存
//**********************************************************************************************************************
/**
* 异步下载图片的任务。
*/
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
/**图片的URL地址 */
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
FileDescriptor fileDescriptor = null;//与此流有关的文件描述符对象,主要实际用途是创建一个包含该结构的 FileInputStream 或 FileOutputStream
Snapshot snapShot = null;
FileInputStream fileInputStream = null;//缓存文件的输入流
try {
// 首先根据图片的URL生成对应的MD5
final String key = hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
if (snapShot == null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) editor.commit();
else editor.abort();
}
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
}
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();//返回表示到文件系统中实际文件的连接的 FileDescriptor 对象,该文件系统正被此 FileInputStream 使用
}
// 将缓存数据解析成Bitmap对象
Bitmap bitmap = null;
if (fileDescriptor != null) bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
// 将Bitmap对象添加到内存缓存当中
if (bitmap != null) addBitmapToMemoryCache(params[0], bitmap);
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
// 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if (imageView != null && bitmap != null) imageView.setImageBitmap(bitmap);
taskCollection.remove(this);
}
/**
* 建立HTTP请求,并获取Bitmap对象。
* @param imageUrl 图片的URL地址
* @return 解析后的Bitmap对象
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
}
}