最近刚完成一个仿微信项目,刚闲下来,写篇文章,方便大家理解Android的图片加载模式。
有人会问:为什么要自定义,三方库不是很完善吗?
我说:是的,现在有Picasso,Glide等很多优秀的封装,可以完成。
但重点是:
1、通过自定义了解原理和本质才是最主要的。
2、三方库少则十多个类,多则几十个甚至上百个类,方法更是不计其数,要是完全掌握恐怕至少也要一个月时间,这样对我们的学习效率影响很大,试想我们是在最有限的时间掌握原理好呢,还是一个劲的钻进无限的回调,参数拼接... ..好呢。
3、也是上班原因,由于本人所在单位对外部访问严格的审核,所以开发中的很多技术点,很多都是自己封装完成,在自己编写的过程中不断完善和探索,以达到能公用的目标为己任。
好了,闲话不啰嗦,我们切入正题。
一、原理:图片加载的缓存机制,相信大家都知道,但还是唠叨几句,希望对初学的人也有个认知。
因为APP端用户是流量有限的,所以能最大限度的减下用户流量的消耗,是用户很看重的一点,所以要把一些图片呀,音频视频,集合呀等一些过大的数据,缓存起来,等到用户再次登录,就可以从本地获取,极大节约了资源。
三级缓存:

好了,以目了然,现在就开始具体的代码实现了
二:代码
ImageLoader.java(图片加载类)
ImageLoadListener.java(图片加载完成回调接口)
/**
* Created by jambestwick on 2018/1/10.
* <p>
* 三级缓存
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
// LRUCahce 池子
private static LruCache<String, Bitmap> mCache;
private static Handler mHandler;
private static Map<ImageView, Future<?>> mTaskTags = new LinkedHashMap<ImageView, Future<?>>();
private Context mContext;
private static ImageLoader imageLoader;
public ImageLoader(Context context) {
mContext = context;
if (mCache == null) {
// 防止OOM,mCache存放
int maxSize = (int) (Runtime.getRuntime().freeMemory() / 2);
mCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
}
if (mHandler == null) {
mHandler = new Handler();
}
}
public static ImageLoader getImageLoader() {
return null == imageLoader ? new ImageLoader(BaseApplication.getBaseApplication()) : imageLoader;
}
public LruCache<String, Bitmap> getmCache() {
return mCache;
}
public void display(ImageView iv, String url, ImageLoadListener loadListener) {
// 1.去内存中取
Bitmap bitmap;
bitmap = mCache.get(url);
if (bitmap != null) {
Log.e("1内存读取:", url);
iv.setImageBitmap(bitmap);
if (null != loadListener) {//单个朋友圈图片重置layoutParams
invokeRelayoutBitmap(loadListener, bitmap, iv, url);
}
return;
}
// 2.去硬盘上取
bitmap = loadBitmapFromLocal(url);
if (bitmap != null) {
Log.e("2文件读取:", url);
iv.setImageBitmap(bitmap);
if (null != loadListener) {//单个朋友圈图片重置layoutParams
invokeRelayoutBitmap(loadListener, bitmap, iv, url);
}
return;
}
// 3. 去网络获取图片
loadBitmapFromNet(iv, url, loadListener);
}
private void invokeRelayoutBitmap(ImageLoadListener loadListener, Bitmap bitmap, ImageView iv, String url) {
mHandler.post(new RefreshBitmap(loadListener, bitmap, url, iv));
}
private void loadBitmapFromNet(ImageView iv, String url, ImageLoadListener loadListener) {
// 判断是否有线程在为 imageView加载数据
Future<?> futrue = mTaskTags.get(iv);
if (futrue != null && !futrue.isCancelled() && !futrue.isDone()) {
Log.d(TAG, "取消任务");
// 线程正在执行
futrue.cancel(true);
}
futrue = ThreadPool.getCachedInstance().submit(new ImageLoadTask(iv, url, loadListener));
mTaskTags.put(iv, futrue);
}
class ImageLoadTask implements Runnable {
private String mUrl;
private ImageView iv;
private ImageLoadListener loadListener;
public ImageLoadTask(ImageView iv, String url, ImageLoadListener loadListener) {
this.mUrl = url;
this.iv = iv;
this.loadListener = loadListener;
}
@Override
public void run() {
try {
Log.e("网络异步", "图片" + mUrl);
// 获取连接
//HttpsURLConnection conn = HttpsFormUtil.getInstance().getHttpURLConnection(new URL(mUrl));
HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();
conn.setConnectTimeout(30 * 1000);// 设置连接服务器超时时间
conn.setReadTimeout(30 * 1000);// 设置读取响应超时时间
// 连接网络
conn.connect();
// 获取响应码
int code = conn.getResponseCode();
if (200 == code) {
InputStream is = conn.getInputStream();
// 将流转换为bitmap
final Bitmap bitmap = BitmapFactory.decodeStream(is);
// 存储到本地
write2Local(mUrl, bitmap);
// 存储到内存
mCache.put(mUrl, bitmap);
mHandler.post(new Runnable() {
@Override
public void run() {
if (null != loadListener) {
loadListener.onLoadingComplete(mUrl, iv, bitmap);
}
display(iv, mUrl, loadListener);
}
});
}
} catch (Exception e) {
Log.e(TAG, "download failed:" + e.toString());
}
}
}
/**
* 本地找图片
*
* @param url
*/
private Bitmap loadBitmapFromLocal(String url) {
// 去找文件,将文件转换为bitmap
String name;
try {
name = MD5Encode.encode(url);
File file = new File(getCacheDir(), name);
if (file.exists()) {
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
// 存储到内存
mCache.put(url, bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO Auto-generated catch block
Log.e(TAG, "load LocalImage Failed:" + e.toString());
}
return null;
}
private void write2Local(String url, Bitmap bitmap) {
String name;
FileOutputStream fos = null;
try {
name = MD5Encode.encode(url);
File file = new File(getCacheDir(), name);
fos = new FileOutputStream(file);
// 将图像写到流中,放缩略,减下存储
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
Log.e(TAG, "io exception:" + e.toString());
}
}
}
}
private String getCacheDir() {
String state = Environment.getExternalStorageState();//外置sd卡路径
File dir;
if (Environment.MEDIA_MOUNTED.equals(state)) {
// 有sd卡
dir = new File(Environment.getExternalStorageDirectory(), "/zhanglong/data/" + mContext.getPackageName()
+ "/icon");
} else {
// 没有sd卡
dir = new File(mContext.getCacheDir(), "/icon");
}
if (!dir.exists()) {
dir.mkdirs();
}
return dir.getAbsolutePath();
}
static class RefreshBitmap implements Runnable {
private ImageLoadListener loadListener;
private Bitmap bitmap;
private String url;
private ImageView iv;
public RefreshBitmap(ImageLoadListener loadListener, Bitmap bitmap, String url, ImageView iv) {
this.loadListener = loadListener;
this.bitmap = bitmap;
this.url = url;
this.iv = iv;
}
@Override
public void run() {
loadListener.onLoadingComplete(url, iv, bitmap);
}
}
}
核心方法就是一个display,
/**
* Created by jambestwick on 2018/1/10.
* 下载完成监听
*/
public interface ImageLoadListener {
void onLoadingComplete(String imageUri, View view, Bitmap bitmap);
}
就这么简单