图片三级缓存类原理:
- 先从缓存中查找,缓冲中没有,再从SD卡中查找,SD卡没有,从网络查找下载,下载完成,在SD卡和缓存中分别保存一份
- Lruch : 类似于内存强引用的缓存,存储方式为键值对存值(K,V),取值 方便,当缓存中内存的大小超出本身的内存的时候,它会自动将最前面的内存强制抛出交给垃圾回收机制(GC)进行回收
- set集合在安卓中的使用:去重的功能
- 如何避免图片错位的问题,给set集合中的imagview添加唯一 的 Tag值, 图片下载完成之后,再将图片值给imageview 的时候遍历整个set进行进行对比,符合的再设置
- 一般使用的话,结合application使用
直接贴代码把,懒的写,我要说一下为什么写这个,那是因为有时候写个小demo,从网上依赖图片框架太麻烦,索性直接写个工具类完事
MainActivity.java
String url = "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3483712804,2789478993&fm=27&gp=0.jpg";
private ImageView mImg;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImg = findViewById(R.id.img);
}
public void load(View view) {
initLoad();
}
private void initLoad() {
ImageLoaderUtils utils = new ImageLoaderUtils();
utils.Load(url, mImg, this);
}
ImageLoaderUtils.java
**
* Created by ${zk} on 2018/4/18 0018.
* 欢迎每一天
* 图片的三级缓存类
*/
public class ImageLoaderUtils {
// private Context mContext;
private final static int MAX_POOLS = 5;//声明一个最大的开启线程数
private ExecutorService thread_pool;//声明一个线程池
private FileUtils mFileUtils;//图片保存和读取工具类
private Set<ImageView> imgs = new HashSet<>();//创建一个存储图片控件的Set集合
private int max_size = (int) (Runtime.getRuntime().maxMemory()) / 1024 / 5;//规定每个线程最大内存
//LruCache 类似于强引用缓存的,一旦超出最大值,会自动将前面的扔出缓存,便于垃圾回收机制回收
//存储方式和Map相似,用的K,V存值的方式,便于图片查找
private LruCache<String, Bitmap> lru = new LruCache<String, Bitmap>(max_size) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};
/***
* 三级判断
* @param name
* @param img
*/
public void Load(String name, ImageView img, Context context) {
Bitmap mBitmap;
// mContext=context;
//为空直接跳出
if (name == null)
return;
if (img == null)
return;
//把name网址过滤
String newFileName = name.replace("/", "").replace(".", "").replace(":", "")
.replace("_", "").replace("=","");
//img 添加tag
img.setTag(newFileName);//为避免出现图片错位,将图片存储在其相对应的控件中,方便以后进行对比设置
//imgs添加到Set集合
imgs.add(img);
//从lru中获取
mBitmap = lru.get(newFileName);//K.V --->从lru中获取图片
if (mBitmap != null) {
img.setImageBitmap(mBitmap);
return;
}
//从本地sd卡中获取图片
if (mFileUtils == null) {
mFileUtils = new FileUtils(context);
mBitmap = mFileUtils.readBitmap(newFileName);
if (mBitmap != null) {
img.setImageBitmap(mBitmap);
//将图片添加到lru中
lru.put(newFileName, mBitmap);
return;
}
}
//从网络下载
if (thread_pool == null) {
thread_pool = Executors.newFixedThreadPool(MAX_POOLS);
}
//准备网络加载图片,传过去的是网址name
thread_pool.execute(new ImgThread(name));
}
/**
* 图片异步加载内部类
*/
private class ImgThread implements Runnable {
private String name;
public ImgThread(String name) {
this.name = name;
}
@Override
public void run() {
//开始网络下载
try {
URL url = new URL(name);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置联网的参数
conn.setConnectTimeout(30 * 1000);
conn.setReadTimeout(30 * 1000);
conn.setDoInput(true);
// conn.setDoOutput(true); 重要说明 这里是个梗,浪费我半天的时间,默认是false
conn.setRequestMethod("GET");
Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream());
//把网址过滤
String newFileName = name.replace("/", "").replace(".", "").replace(":", "")
.replace("_", "").replace("=","");
//将下载下来的图片再次添加到SD卡和lru中
if (bitmap != null) {
mFileUtils.saveBitmap(newFileName, bitmap);
lru.put(newFileName, bitmap);
}
Message msg = hand.obtainMessage();
msg.what = 200;
msg.obj = bitmap;
Bundle bundle = new Bundle();
bundle.putString("imgname", newFileName);
msg.setData(bundle);
hand.sendMessage(msg);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@SuppressLint("HandlerLeak")
Handler hand = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 200) {
Bitmap bitmap = (Bitmap) msg.obj;
String imgname = msg.getData().getString("imgname");
//遍历Set集合,进行对比
Iterator<ImageView> it = imgs.iterator();
while (it.hasNext()) {
ImageView img = it.next();
String tag = (String) img.getTag();
//将子线程出来的图片名称和控件保存的相对应的图片名称进行比较,如果相同就设置
if (tag.equals(imgname)) {
img.setImageBitmap(bitmap);
return;
}
}
}
}
};
}
FileUtils.java
/**
* Created by ${zk} on 2018/4/18 0018.
* 欢迎每一天
* 文件操作工具类,存储和读取图片
*/
public class FileUtils {
private Context ctx;
public FileUtils(Context ctx) {
this.ctx = ctx;
}
/**
* 判断SD卡是否存在的方法----》 存在返回 true ,不存在返回false
* Environment.getExternalStorageState():获取sd卡的状态 Environment.MEDIA_MOUNTED:当前SD存在
*/
private boolean isSdcard() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return true;
}
return false;
}
/**
* 获取文件目录的方法 /mnt/sdcard/android/data/catch/name;
*
* @return
*/
private String getFile() {
String str = null;
str = ctx.getExternalCacheDir().getAbsolutePath();
return str;
}
/**
* 存储图片的方法
*/
public void saveBitmap(String name, Bitmap bitmap) {
//判断SDK是否存在,不存在直接return
if (!isSdcard()) {
return;
}
//获取图片完整存储路径:以图片网址为图片名称存储 /mnt/sdcard/android/data/catch/name
String fileName = getFile() + "/" + name;
Log.e("zk", "fileName++++" + fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//图片压缩
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
File file = new File(fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
//写文件
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取图片的方法
*
* @param name 本地图片名字
* @return 图片
*/
public Bitmap readBitmap(String name) {
Bitmap bitmap = null;
if (!isSdcard())
//没有sd卡
return bitmap;
//获取图片存储的完整路径
String fileName = getFile() + "/" + name;
bitmap = BitmapFactory.decodeFile(fileName);
return bitmap;
}
}
好了,conn.setDoOutput()方法,最好不要写,写了之后网上下载不出来图片,困惑了我半天的Bug