学习目标:
自己实现一个三级缓存的图片加载框架(模仿Glide)ps:但肯定没Glide功能强大。
说明:
- 三级缓存的概念:内存缓存,磁盘缓存,网络缓存
- 就是简单的实现,没有网上的图片加载框架牛逼
- 模仿Glide的链式调用
开始
先来讲一下我们要创建的类的功能和作用
类名 | 功能 |
---|---|
DoubleCache.java | 实现二级缓存(内存&磁盘) |
DiskCache.java | 实现磁盘缓存 |
MemoryCache.java | 实现磁盘缓存 |
MD5Utils.java | MD5Utils加密图片缓存文件名 |
Glide.java | 暴露接口给App层调用 |
BitmapRequest.java | 图片请求request |
RequestCallBack.java | 图片请求request回调接口 |
RequestManager.java | 管理请求队列 |
BitmapDispatcher.java | 请求图片的线程 |
申明权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
我这里因为懒得重复早申请权限的轮子,就掠过了,我是安装APP后,切换到设置界面,手动把应用权限打开的。你不打开,就没法下图片和缓存图片哦!
package com.suyong.myglide.Glide;
import android.content.Context;
import android.graphics.Bitmap;
public class DoubleCache {
//内存缓存
private MemoryCache lruCache;
//磁盘缓存
private DiskCache diskCache;
//初始化
public DoubleCache(Context context){
diskCache = DiskCache.getInstance(context);
lruCache = MemoryCache.getInstance();
}
//存储图片
public void put(String key, Bitmap bitmap){
lruCache.put(key,bitmap);
diskCache.put(key,bitmap);
}
//获取图片
public Bitmap get(String key){
Bitmap bitmap = lruCache.get(key);
if(bitmap != null) return bitmap;
return diskCache.get(key);
}
}
package com.suyong.myglide.Glide;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.LruCache;
import java.io.File;
//内存缓存
public class MemoryCache {
private LruCache<String, Bitmap> lruCache;
private static volatile MemoryCache instance;
//锁
private static final byte[]lock = new byte[0];
//单列
public static MemoryCache getInstance(){
if(instance == null){
synchronized (lock){
if(null == instance){
instance = new MemoryCache();
}
}
}
return instance;
}
//初始化大小
private MemoryCache(){
//初始化当前运行环境的16分支1大小
int maxMemorySize = (int) (Runtime.getRuntime().maxMemory()/16);
if(maxMemorySize < 0){
maxMemorySize = 10*1024*1024;
}
lruCache = new LruCache<String,Bitmap>(maxMemorySize){
@Override
protected int sizeOf(String key,Bitmap value){
return super.sizeOf(key,value);
}
};
}
//存储图片
public void put(String key,Bitmap value){
if(value!=null){
lruCache.put(key,value);
}
}
//获取图片
public Bitmap get(String key){
return lruCache.get(key);
}
}
package com.suyong.myglide.Glide;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
//磁盘缓存
public class DiskCache {
private static volatile DiskCache instance;
//缓存图片的文件夹名称
private String cachePath = "Cache";
private static final byte[] lock = new byte[0];
private File cacheFile;
public static DiskCache getInstance(Context context) {
if (instance == null) {
synchronized (lock) {
if (null == instance) {
instance = new DiskCache(context);
}
}
}
return instance;
}
private DiskCache(Context context) {
cacheFile = getImageCacheFile(context, cachePath);
if (!cacheFile.exists()) cacheFile.mkdirs();
}
//创建图片缓存文件夹
private File getImageCacheFile(Context context, String cachePath) {
//data/data/包名/files/Cache
File file = new File(context.getFilesDir() + "/" + cachePath);
return file;
}
//存储图片
public void put(String key, Bitmap value){
if(value != null){
FileOutputStream fos = null;
try {
File temp = new File(cacheFile.getAbsolutePath()+ "/"+key+".png");
if(temp.exists()) temp.delete();
temp.createNewFile();
fos = new FileOutputStream(temp);
value.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (Exception e) {
e.printStackTrace();
if(fos!=null){
try {
fos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
//获取图片
public Bitmap get(String key){
return BitmapFactory.decodeFile(cacheFile.getAbsolutePath()+ "/"+key+".png");
}
}
package com.suyong.myglide.Glide;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//MD5加密工具类
//用来给文件名称加密
public class MD5Utils {
private static MessageDigest digest;
static {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public static String encode(String key){
if(null == key) throw new Exception(" Invalid image url can not be null !");
digest.update(key.getBytes());
return convertToHexString(digest.digest());
}
private static String convertToHexString(byte[] bytes){
StringBuffer sb = new StringBuffer();
for(byte b:bytes){
String hex = Integer.toHexString(0xFF & b );
if(hex.length() == 1) sb.append(hex);
sb.append(hex);
}
return sb.toString();
}
}
package com.suyong.myglide.Glide;
import android.graphics.Bitmap;
//请求回调接口
public interface RequestCallBack {
//请求成功
void onSucess(Bitmap bitmap);
//请求失败
void onFailed();
}
package com.suyong.myglide.Glide;
import android.content.Context;
import android.graphics.Bitmap;
//给APP层调用
public class MyGlide {
public static BitmapRequest with(Context context){
return new BitmapRequest(context);
}
}
package com.suyong.myglide.Glide;
import android.content.Context;
import android.widget.ImageView;
import java.lang.ref.SoftReference;
//图片请求
public class BitmapRequest {
//上下文
private Context mContext;
//图片的路径
private String mUrl;
//显示图片的控件
private SoftReference<ImageView> mImageView;
//占位图片的资源id
private int mLoadingResId;
//回调接口
private RequestCallBack mCallBack;
//请求标识
private String MD5_Tag;
public BitmapRequest(Context mContext) {
this.mContext = mContext;
}
//设置加载图片的url
public BitmapRequest load(String url){
this.mUrl = url;
this.MD5_Tag = MD5Utils.encode(this.mUrl);
return this;
}
//设置图片的正在加载的占位图
public BitmapRequest setLoadingResId(int mLoadingResId) {
this.mLoadingResId = mLoadingResId;
return this;
}
//设置回调接口
public BitmapRequest setCallBack(RequestCallBack mCallBack) {
this.mCallBack = mCallBack;
return this;
}
//设置需要设置图片的目标ImageView
public void into(ImageView imageView){
imageView.setTag(this.MD5_Tag);
this.mImageView = new SoftReference<>(imageView);
RequestManager.getInstance().addBitmapRequest(this);
}
public String getUrl() {
return mUrl;
}
public ImageView getImageView() {
return mImageView.get();
}
public int getLoadingResId() {
return mLoadingResId;
}
public String getMD5_Tag() {
return MD5_Tag;
}
}
package com.suyong.myglide.Glide;
import java.util.concurrent.LinkedBlockingQueue;
//管理请求队列
public class RequestManager {
//请求阻塞队列
private LinkedBlockingQueue <BitmapRequest> requestQueue ;
private static RequestManager requestManager = new RequestManager();
private BitmapDispatcher[] bitmapDispatchers;
private RequestManager(){
requestQueue = new LinkedBlockingQueue<>();
//停止所有线程
stopAllThread();
//初始化线程
createAndStart();
}
//创建线程并且启动
private void createAndStart() {
//获取当前环境允许的最大线程数
int count = Runtime.getRuntime().availableProcessors();
bitmapDispatchers = new BitmapDispatcher[count];
//创建线程
for(int x =0;x<count;x++){
BitmapDispatcher bitmapDispatcher = new BitmapDispatcher(requestQueue);
bitmapDispatcher.start();
bitmapDispatchers[x] = bitmapDispatcher;
}
}
public static RequestManager getInstance(){
return requestManager;
}
/**
* 将请求对象添加到请求队列
* @param request,
*/
public void addBitmapRequest(BitmapRequest request){
if(!requestQueue.contains(request)){
requestQueue.add(request);
}
}
//停止所有线程
public void stopAllThread(){
if(bitmapDispatchers!=null && bitmapDispatchers.length > 0) {
for (BitmapDispatcher bitmapDispatcher : bitmapDispatchers) {
if(!bitmapDispatcher.isInterrupted()) bitmapDispatcher.isInterrupted();
}
}
}
}
package com.suyong.myglide.Glide;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import com.suyong.myglide.MyApplication;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 请求图片
*/
public class BitmapDispatcher extends Thread {
//请求队列
private LinkedBlockingQueue<BitmapRequest> requestQueue;
private DoubleCache doubleCache = new DoubleCache(MyApplication.getInstance());
private Handler mHandler = new Handler(Looper.getMainLooper());
public BitmapDispatcher(LinkedBlockingQueue<BitmapRequest> requestQueue) {
this.requestQueue = requestQueue;
}
@Override
public void run() {
super.run();
while(!this.isInterrupted()&&!requestQueue.isEmpty()) {
try {
BitmapRequest request = requestQueue.take();
showLoadingImage(request);
Bitmap bitmap = doubleCache.get(request.getMD5_Tag());
if(bitmap == null){
bitmap = loadBitmap(request);
doubleCache.put(request.getMD5_Tag(),bitmap);
}
showImageResult(request, bitmap);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void showImageResult(BitmapRequest request, final Bitmap bitmap) {
if (bitmap != null && request.getImageView() != null && request.getMD5_Tag().equals(request.getImageView().getTag())) {
final ImageView imageView = request.getImageView();
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}
private Bitmap loadBitmap(BitmapRequest request) {
return download(request.getUrl());
}
//下载图片
private Bitmap download(String uri) {
FileOutputStream fos = null;
InputStream is = null;
Bitmap bitmap = null;
try {
URL url = new URL(uri);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
is = con.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) fos.close();
if (is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
//显示占位图
private void showLoadingImage(final BitmapRequest request) {
if (request.getLoadingResId() > 0 && request.getImageView() != null) {
final ImageView imageView = request.getImageView();
final int resId = request.getLoadingResId();
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageResource(resId);
}
});
}
}
}
最后我们再App层调用的时候只要下面一句代码就可以
ImageView image = findViewById(R.id.xxx);
//设置了占位图
MyGlide.with(this).load("https://profile.csdnimg.cn/D/B/3/3_sunlifeall").setLoadingResId(R.drawable.ic_launcher_background).into(image);
//没有设置占位图
MyGlide.with(this).load("https://profile.csdnimg.cn/D/B/3/3_sunlifeall").into(image);
ok,到这儿就实现了建议的三级缓存,但是我没有使用LruDiskCache,所以应用会随着时间的推移变大,有能力的小伙伴可以使用DiskLruCache实现磁盘缓存。
拜了个拜!
运行结果:
第二次加载注意看图片的加载速度,很快就夹在好了,因为读取的是图片缓存。
附:Demo链接