图片的三级缓存相信大家都了解过了,废话不多说,直接入主题:
缓存最主要的类:
package com.text.imgcachetext;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @项目名: ImgCacheText
* @包名: com.text.imgcachetext
* @文件名: ImageCacheUtils
* @创建者: wxr
* @创建时间: 2016/11/2 13:59
* @描述: 图片缓存的工具类,虚拟内存缓存为可用内存的1/4,磁盘缓存为30M,网络连接失败默认3次重连
* @使用方式: 简单的链式调用ImageCacheUtils.with().load(String).into(ImageView);
*/
public class ImageCacheUtils {
private static final String TAG = "ImageCacheUtils";
private static final String DISK_CACHE_SUBDIR = File.separator + "PictureCache";//磁盘缓存的文件夹名称
//没有网络连接
private static final int NETWORN_NONE = 0;
//wifi连接
private static final int NETWORN_WIFI = 1;
//手机网络数据连接类型
private static final int NETWORN_2G = 2;
private static final int NETWORN_3G = 3;
private static final int NETWORN_4G = 4;
private static final int NETWORN_MOBILE = 5;
private static ImageCacheUtils mUtils = null;
private static int mBeats = 3;//重连的总次数
private Context mContext = null;
private LruCache<String, Bitmap> mLruCache = null;
private Resources mRes = null;
private String mImgUrl = null;//图片地址
private Bitmap mBitmap = null;
private Handler mHandler = null;
private ExecutorService mCacheThread = null;//线程池
private File mDiskPath = null;
private static int mLruSize = 4;//可用虚拟内存的大小 4-->1/4
private static long mDiskSize = 30 * 1024 * 1024;//磁盘缓存的大小,这里给30M
private static boolean mIsLoadImg = true;//是否加载图片
/**构造函数*/
private ImageCacheUtils(Context context) {
this.mContext = context;
if (this.mRes == null) { this.mRes = context.getResources(); }
int maxMemory = (int) (Runtime.getRuntime().maxMemory());
int cacheSize = maxMemory / this.mLruSize;
this.mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
this.mHandler = new Handler();
this.mCacheThread = Executors.newCachedThreadPool();
this.mDiskPath = getDiskPath();
}
//------------------>start 虚拟内存缓存方法<----------------
/***
* 读取缓存中的图片
* @return
*/
private Bitmap getMemoryBitmap(String url) {
String fileName = this.getFileMD5(url);
Bitmap bitmap = mLruCache.get(fileName);
return bitmap != null ?
bitmap :
null;
}
/**
* 缓存中存储图片
* @param bitmap
*/
private void addMemoryBitmap(Bitmap bitmap, String url) {
if (getMemoryBitmap(url) == null) {
mLruCache.put(this.getFileMD5(url), bitmap);
}
}
//------------------>end 虚拟内存缓存方法<------------------
//------------------>start 磁盘图片存储和获取<----------------
/**获取磁盘中的图片*/
private Bitmap getDiskCacheBitmap(String url) {
try {
File file = new File(mDiskPath, DISK_CACHE_SUBDIR);
File cacheFile = new File(file, this.getFileMD5(url));
if (!cacheFile.exists()) {return null;}
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
return bitmap != null ?
bitmap :
null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/***
* 把图片存放在本地
* @param bitmap
*/
private void addDiskCacheBitmap(Bitmap bitmap, String url) {
if (this.getDiskCacheBitmap(url) != null) {return;}
try {
String fileName = this.getFileMD5(url);
long size = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
size = bitmap.getAllocationByteCount();
}
File path = this.getDiskDir(size);
if (path == null) { return; }
File file = new File(path, fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判断外置sd卡是否已满,
* 不满就判断文件夹是否存在,不存在就创建文件夹,存在就删除文件
* <code>{@link #deleteFile(File filePath, long fileSize)}</code>方法
* @return 存储图片文件的路径对象
*/
private File getDiskDir(long fileSize) {
File cacheDir = mContext.getCacheDir();
File file = mDiskPath;
File path = null;
if (diskIsFull(file)) {
if (!file.getAbsolutePath().equals(cacheDir.getAbsolutePath())) {
//外置SD卡内存不足
path = new File(file, DISK_CACHE_SUBDIR);
if (path.exists()) {deleteFile(path, fileSize);} else {
path = new File(cacheDir, DISK_CACHE_SUBDIR);
if (diskIsFull(cacheDir)) {
if (path.exists()) {deleteFile(path, fileSize);} else {
//走到这里就说明外置sd卡和内置内存都满了
return null;
}
}
}
} else {
//内置内存满了
path = new File(cacheDir, DISK_CACHE_SUBDIR);
if (diskIsFull(cacheDir)) {
if (path.exists()) {deleteFile(path, fileSize);} else {
//走到这里就说明内置内存满了
return null;
}
}
}
} else {
//内存足够
path = new File(file, DISK_CACHE_SUBDIR);
//如果子文件夹存在时,删除文件。子文件夹不存在时,创建文件夹
if (path.exists()) {
deleteFile(path, fileSize);
} else {path.mkdirs();}
}
return path;
}
/**
* 获取磁盘路径
* @return 当外置sd卡存在时返回外置sd卡的路径,不存在是返回内置内存的路径
*/
private File getDiskPath() {
//判断sd卡是否存在
boolean SDIsExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
return SDIsExist ?
Environment.getExternalStorageDirectory() :
mContext.getCacheDir();
}
/**
* 判断磁盘内存是否已满
* @param file 磁盘路径
* @return true已满,false未满
*/
private boolean diskIsFull(File file) {
//计算磁盘可用大小
long usableSpace = file.getUsableSpace();
if (usableSpace < mDiskSize) {
Log.e(TAG, "外置SD卡内存不足,无法做外置缓存,自动缓存到内置内存");
return true;
}
return false;
}
/**
* 根据修改时间删除文件,首先删除时间最久的。删除文件后重新计算大小
* @param filePath 文件夹路径
* @param fileSize 需要存放的文件大小
*/
private void deleteFile(File filePath, long fileSize) {
//判断文件夹大小
long cacheSize = getCacheDirSize(filePath);
if ((mDiskSize - cacheSize) > fileSize) {return;}
File[] files = filePath.listFiles();
File delFile = null;
//获取第一个文件
for (File fil : files) {
if (fil.isFile()) { delFile = fil; }
}
//根据时间判断时间,保存时间最长的
for (File fil : files) {
if (fil.isFile()) { if (fil.lastModified() < delFile.lastModified()) {delFile = fil;} }
}
if (delFile != null && delFile.isFile()) { delFile.delete(); }
//递归
deleteFile(filePath, fileSize);
}
/**递归方式 计算缓存文件的大小*/
private long getCacheDirSize(final File file) {
if (file.isFile()) { return file.length(); }
final File[] children = file.listFiles();
long total = 0;
if (children != null) {
for (final File child : children) { total += getCacheDirSize(child); }
}
return total;
}
//------------------>end 磁盘图片存储和获取<------------------
//------------------>start 公共方法,对外方法<----------------
/**
* 初始化对象
* @param context 上下文
*/
public static ImageCacheUtils init(Context context) {
if (mUtils == null) {
mUtils = new ImageCacheUtils(context);
}
return mUtils;
}
/**
* 获取对象
* @return 本类对象,链式调用
*/
public static ImageCacheUtils with() {
if (mUtils == null) {
throw new RuntimeException(
"ImageCacheUtils初始化失败,请在Application的onCreate方法中调用init(Context context)方法进行初始化");
}
return mUtils;
}
/**
* 设置可用虚拟内存的缓存大小
* @param size 最少为2(当等于2,表示占可用虚拟内存的1/2,如此类推),不设置默认为4(就是1/4)
* @return 本类对象,链式调用
*/
public ImageCacheUtils setMemorySize(int size) {
if (size < 2) {
this.mLruSize = 2;
return this;
}
this.mLruSize = size;
return this;
}
/**
* 设置磁盘可用空间的缓存大小
* @param size 单位M(默认是30M)
* @return 本类对象,链式调用
*/
public ImageCacheUtils setDiskSize(int size) {
if (size < 0) {return this;}
mDiskSize = size * 1024 * 1024;
return this;
}
/**
* 设置失败重连的次数
* @param beats 默认是3次
* @return 本类对象,链式调用
*/
public ImageCacheUtils setRelinkBeats(int beats) {
if (beats < 0) {return this;}
this.mBeats = beats;
return this;
}
/**
* 设置是否在所有网络下加载图片,先加载磁盘或者是缓存中的,如果都没有才选择是否请求网络
* @param isOnAllNet 默认true在所有网络下都加载,false不加载(在wifi网络下加载,2/3/4G网络下不加载显示默认图片)
* @return 本类对象,链式调用
*/
public ImageCacheUtils setLoadImgOnAllNet(boolean isOnAllNet) {
this.mIsLoadImg = isOnAllNet;
return this;
}
/**
* 设置图片的链接地址
* @param url 图片地址
* @return 本类对象,链式调用
*/
public ImageCacheUtils load(String url) {
mImgUrl = url;
return this;
}
/**
* 默认或网络请求失败后显示的图片
* @param resourceId 图片的资源ID
* @return 本类对象,链式调用
*/
public ImageCacheUtils placeholder(int resourceId) {
mBitmap = BitmapFactory.decodeResource(mRes, resourceId);
return this;
}
/**
* <br>设置图片(必须是ImageView的子类或者 有setImageBitmap(Bitmap bm)这个方法)
* ,调用这个方法之前请先调用<code>{@link #placeholder(int resourceId)}</code>方法,
* 标记已经设置好,不会造成图片错位。(如果有调用<code>{@link #load(String url)}</code>这个方法,
* 在加载前会先显示该图片)
* <br>1.从缓存里面读取图片
* <br>2.从磁盘读取
* <br>3.从网络加载
* <br><b>注:该方法应当在最后调用
* @param img 显示图片的控件
*/
public void into(final ImageView img) {
if (mBitmap != null) { img.setImageBitmap(mBitmap); }
if (TextUtils.isEmpty(mImgUrl)) {
throw new NullPointerException("网络地址为空,请先调用load(String url)方法!");
}
img.setTag(mImgUrl);
mCacheThread.execute(new Runnable() {
@Override
public void run() {
//子线程
new SyncNetWork(img).netWork(mImgUrl);
}
});
}
//------------------>end 公共方法,对外方法<------------------
//------------------>start 其他方法<----------------
/**获取MD5码*/
private String getFileMD5(String info) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(info.getBytes("UTF-8"));
byte[] encryption = md5.digest();
StringBuffer strBuf = new StringBuffer();
for (int i = 0; i < encryption.length; i++) {
if (Integer.toHexString(0xff & encryption[i]).length() == 1) {
strBuf.append("0").append(Integer.toHexString(0xff & encryption[i]));
} else {
strBuf.append(Integer.toHexString(0xff & encryption[i]));
}
}
return strBuf.toString();
} catch (NoSuchAlgorithmException e) {
return "";
} catch (UnsupportedEncodingException e) {
return "";
}
}
/**是否是wifi状态true是*/
private boolean isWifi() {
return getNetworkState() == NETWORN_WIFI;
}
/**
* 获取当前网络连接类型
* @return
*/
private int getNetworkState() {
//获取系统的网络服务
ConnectivityManager connManager = (ConnectivityManager) this.mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
//如果当前没有网络
if (null == connManager) { return NETWORN_NONE; }
//获取当前网络类型,如果为空,返回无网络
NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
return NETWORN_NONE;
}
// 判断是不是连接的是不是wifi
NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (null != wifiInfo) {
NetworkInfo.State state = wifiInfo.getState();
if (null != state) {
if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
return NETWORN_WIFI;
}
}
}
// 如果不是wifi,则判断当前连接的是运营商的哪种网络2g、3g、4g等
NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (null != networkInfo) {
NetworkInfo.State state = networkInfo.getState();
String strSubTypeName = networkInfo.getSubtypeName();
if (null != state) {
if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
switch (activeNetInfo.getSubtype()) {
//如果是2g类型
case TelephonyManager.NETWORK_TYPE_GPRS: // 联通2g
case TelephonyManager.NETWORK_TYPE_CDMA: // 电信2g
case TelephonyManager.NETWORK_TYPE_EDGE: // 移动2g
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_IDEN:
return NETWORN_2G;
//如果是3g类型
case TelephonyManager.NETWORK_TYPE_EVDO_A: // 电信3g
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
case TelephonyManager.NETWORK_TYPE_EHRPD:
case TelephonyManager.NETWORK_TYPE_HSPAP:
return NETWORN_3G;
//如果是4g类型
case TelephonyManager.NETWORK_TYPE_LTE:
return NETWORN_4G;
default:
//中国移动 联通 电信 三种3G制式
if (strSubTypeName.equalsIgnoreCase(
"TD-SCDMA") || strSubTypeName.equalsIgnoreCase(
"WCDMA") || strSubTypeName.equalsIgnoreCase("CDMA2000"))
{
return NETWORN_3G;
} else {
return NETWORN_MOBILE;
}
}
}
}
}
return NETWORN_NONE;
}
//------------------>end 其他方法<------------------
private class SyncNetWork {
private int mFlag = 0;//重连标记
private ImageView mImg = null;
private Bitmap mCacheBitmap = null;
public SyncNetWork(ImageView img) {
this.mImg = img;
}
/**
* 连接网络加载图片
* @param imgUrl 图片的URL
*/
public void netWork(final String imgUrl) {
//读取缓存中的图片
mCacheBitmap = getMemoryBitmap(imgUrl);
if (mCacheBitmap != null) {
runOnUiThread(imgUrl);
return;
}
//读取磁盘中的图片
mCacheBitmap = getDiskCacheBitmap(imgUrl);
if (mCacheBitmap != null) {
runOnUiThread(imgUrl);
return;
}
if (!mIsLoadImg) {
//只在wifi下加载
if (isWifi()) {
//加载
loadImg(imgUrl);
}
return;
}
loadImg(imgUrl);
}
/**
* 加载网络图片
* @param imgUrl 图片网络地址
*/
private void loadImg(final String imgUrl) {
InputStream is = null;
try {
URL url = new URL(imgUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
mFlag = 0;//清零
is = conn.getInputStream();
if (is == null) {
this.reLink(imgUrl);
return;
}
final Bitmap bitmap = BitmapFactory.decodeStream(is);
if (bitmap == null) {return;}
addMemoryBitmap(bitmap, imgUrl);
addDiskCacheBitmap(bitmap, imgUrl);
mHandler.post(new Runnable() {
@Override
public void run() {
//主线程
if (mImg != null && mImg.getTag().equals(imgUrl)) {
mImg.setImageBitmap(bitmap);
} else {
if (mBitmap != null) { mImg.setImageBitmap(mBitmap); }
reLink(imgUrl);
}
}
});
} else {
//需要重新连接
this.reLink(imgUrl);
}
if (is != null) {is.close();}
} catch (Exception e) {
//需要重新连接
this.reLink(imgUrl);
}
}
/**失败重连*/
private void reLink(String imgUrl) {
if (mFlag < mBeats) {
SystemClock.sleep(1000);
Log.d(TAG, "正在重新连接...(" + mFlag + ")" + ",当前线程" + Thread.currentThread().getName());
mFlag++;
this.netWork(imgUrl);
}
}
/**
* 主线程运行
* @param imgUrl 图片的URL 用来做比较比较
*/
private void runOnUiThread(String imgUrl) {
if (mImg != null && imgUrl.equals(mImg.getTag())) {
mHandler.post(new Runnable() {
@Override
public void run() {
mImg.setImageBitmap(mCacheBitmap);
}
});
}
}
}
}
ImageCacheUtils init = ImageCacheUtils.init(getApplicationContext());
init.setDiskSize(20);//设置磁盘大小,按需要 默认30M
init.setLoadImgOnAllNet(false);//设置网络加载图片,按需要 默认true,所有网络都加载
init.setMemorySize(2);//设置缓存大小,按需要 这里1/2默认1/4
init.setRelinkBeats(3);//设置重连次数,按需要 默认是3次
AndroidManifest.xml清单文件:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
也别忘了在applocation节点加上:android:name="xxxx"这句。
调用方式:
ImageCacheUtils.with().load(图片地址).placeholder(默认的图片).into(ImageView);
有点类似于Grild,是的,用了Grild的变量名
其他公开的方法上面都有注释,这里就不说了。说说这流程吧
获取对象(单例)-->设置属性-->设置URL-->设置图片
反复调用:设置URL-->设置图片------>内部类执行,调用一次创建一个内部类对象
内部类对象:
1.检查内存,内存有,用内存,如果没有↓
2.检查磁盘,磁盘有,用磁盘,如果没有↓
3.网络请求,获取图片,先存到内存,再存到磁盘
磁盘的储存流程请看简单的流程图:
好了,就说到这里吧,如有问题,请发邮件到:binary_56@foxmail.com
转载时请标明出处,谢谢!