Android的三級緩存,其中主要的就是記憶體緩存和硬碟緩存,分别是LruCache和DisLruCache。
LruCache是Android 3.1所提供的一個緩存類,是以在Android中可以直接使用LruCache實作記憶體緩存。而DisLruCache目前在Android 還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來實作硬碟緩存。
簡單使用
var listJob = ArrayList<Job>()
KotlinCacheUtil.INSTANCE.init(application)
val imageJob = KotlinCacheUtil.INSTANCE.setImageBitmap(
"https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
imageView,
R.drawable.ic_launcher
)
listJob.add(imageJob)
取消協程防止記憶體洩漏
override fun onDestroy() {
super.onDestroy()
for (job in listJob){
job.cancel()
}
}
本文使用了Kotlin協程需要導入kotlinx-coroutines,網絡請求架構okhttp
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation 'com.squareup.okhttp3:okhttp:3.14.1'
implementation 'com.squareup.okio:okio:2.2.2'
混淆代碼
##okhttp3混淆
-keep class okhttp3.** {*;}
-dontwarn okhttp3.**
##okio混淆
-keep class okio.** {*;}
-dontwarn okio.**
權限
6.0以上使用sd卡需要動态申請權限,自行百度
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
一、LruCache
不廢話直接上工具類,初始化會在另一個管理緩存的工具類中使用,這裡不需要操心
public class LruCacheUtil {
private static volatile LruCacheUtil instance;
private LruCacheUtil() {}
public static LruCacheUtil getInstance() {
if (instance == null) {
synchronized (LruCacheUtil.class) {
if (instance == null) {
instance = new LruCacheUtil();
}
}
}
return instance;
}
private LruCache<String, Bitmap> lruCache;
/**
* 初始化
*/
public void init() {
long maxMemory = Runtime.getRuntime().maxMemory();
int cacheSize = (int) (maxMemory / 8);
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(@NotNull String key, @NotNull Bitmap value) {
return value.getByteCount();
}
};
}
/**
* 把Bitmap對象加入到緩存中
* @param imageUrl 圖檔url
* @param bitmap 加入緩存的圖檔
*/
public void addBitmapToMemory(String imageUrl, Bitmap bitmap) {
String key = Md5Util.getInstance().MD5(imageUrl);
if (getBitmapFromMemCache(key) == null) {
lruCache.put(key, bitmap);
}
}
/**
* 從緩存中得到Bitmap對象
* @param imageUrl 圖檔url
* @return Bitmap
*/
public Bitmap getBitmapFromMemCache(String imageUrl) {
String key = Md5Util.getInstance().MD5(imageUrl);
return lruCache.get(key);
}
/**
* 從緩存中删除指定的Bitmap
* @param imageUrl 圖檔url
*/
public void removeBitmapFromMemory(String imageUrl) {
String key = Md5Util.getInstance().MD5(imageUrl);
lruCache.remove(key);
}
/**
* 清除所有緩存
*/
public void evictAll(){
lruCache.evictAll();
}
}
二、DisLruCache
今年Kotlin語言從安卓一等公民更新為第一等公民,是時候重新學習一波Kotlin了,DisLruCache工具類使用了新特性“協程”,沒有了解的小夥伴可以先了解一下,用過後都說好;Kotlin的IO流值得吹一波,實在太好用了,inputStream.copyTo(outputStream)一行代碼解決從一個流寫到另一個流;最後吹一波Kotlin的單例模式,實在太好用了。
不要忘了這個
implementation 'com.jakewharton:disklrucache:2.0.2'
工具類
enum class DiskLruCacheUtil {
INSTANCE;
private lateinit var application: Application
private lateinit var diskLruCache: DiskLruCache
fun init(application: Application) {
this.application = application
try {
diskLruCache = DiskLruCache.open(directory("bitmap"), getAppVersion(), 1, (10 * 1024 * 1024).toLong())
} catch (e: IOException) {
e.printStackTrace()
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
/**
* 把Bitmap對象加入到緩存中
* @param imageUrl 圖檔url,如https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg
* @param bitmap 加入緩存的圖檔
*/
fun addBitmapToMemory(imageUrl: String){
try {
val key = Md5Util.getInstance().MD5(imageUrl)
val editor = diskLruCache.edit(key)
if (editor != null) {
val outputStream = editor.newOutputStream(0)
if (downloadUrlStream(imageUrl, outputStream)){
editor.commit()
}else{
editor.abort()
}
}
diskLruCache.flush()
} catch (e: IOException) {
e.printStackTrace()
}
}
/**
* 網絡下載下傳
* @param imageUrl 圖檔url
* @param outputStream diskLruCache的IO流
* @return 是否下載下傳成功
*/
private fun downloadUrlStream(imageUrl: String, outputStream: OutputStream) :Boolean{
val request = Request.Builder()
.url(imageUrl)
.build()
try {
val response = HttpUtil.getInstance().client.newCall(request).execute()
if (response.isSuccessful) {
if (response.body()!= null){
val bis = response.body()!!.byteStream().buffered()
val bos = outputStream.buffered()
try {
bis.copyTo(bos)
return true
}catch (e: IOException) {
e.printStackTrace()
}finally {
bis.close()
bos.close()
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
/**
* 從緩存中得到Bitmap對象
* @param imageUrl 圖檔url
* @return Bitmap
*/
fun getBitmapFromMemCache(imageUrl: String,maxWidth:Int,maxHeight:Int): Bitmap? {
val key = Md5Util.getInstance().MD5(imageUrl)
val snapShot = diskLruCache.get(key)
if (snapShot!=null){
val inputStream = snapShot.getInputStream(0)
return BitmapUtil.getInstance().getBitmap(inputStream,maxWidth,maxHeight,false)
}
return null
}
/**
* 從緩存中删除指定的Bitmap
* @param imageUrl 圖檔url
*/
fun removeBitmapFromMemory(imageUrl: String) {
val key = Md5Util.getInstance().MD5(imageUrl)
diskLruCache.remove(key)
}
/**
* 所有緩存資料的總位元組數
*/
fun getSize():Long{
return diskLruCache.size()
}
/**
* 同步資料,在Activity的onPause()方法中去調用一次flush()方法
*/
fun flush(){
diskLruCache.flush()
}
/**
* 在Activity的onDestroy()方法中去調用close()方法
*/
fun close(){
diskLruCache.close()
}
/**
* 将所有的緩存資料全部删除
*/
fun delete(){
diskLruCache.delete()
}
/**
* 緩存目錄
*/
private fun directory(fileName: String): File {
val path: String
//判斷SD卡是否可用
if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !Environment.isExternalStorageRemovable()) {
path = application.externalCacheDir!!.absolutePath
} else {
path = application.cacheDir.absolutePath
}
val file = File(path, fileName)
if (!file.exists()) {
file.mkdirs()
}
return file
}
/**
* app版本
*/
@Throws(PackageManager.NameNotFoundException::class)
private fun getAppVersion(): Int {
val info = application.packageManager.getPackageInfo(application.packageName, 0)
return info.versionCode
}
}
如果你有使用okhttp,那麼也可以使用okhttp的DiskLruCache
enum class OKDiskLruCacheUtil {
INSTANCE;
private lateinit var application: Application
private lateinit var diskLruCache: DiskLruCache
fun init(application: Application) {
this.application = application
try {
diskLruCache = DiskLruCache.create(
FileSystem.SYSTEM,
directory("bitmap"),
getAppVersion(),
1,
10 * 1024 * 1024
)
} catch (e: IOException) {
e.printStackTrace()
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
/**
* 把Bitmap對象加入到緩存中
* @param imageUrl 圖檔url,如https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg
* @param bitmap 加入緩存的圖檔
*/
fun addBitmapToMemory(imageUrl: String){
try {
val key = Md5Util.getInstance().MD5(imageUrl)
val editor = diskLruCache.edit(key)
if (editor != null) {
val outputStream= editor.newSink(0).buffer().outputStream()
if (downloadUrlStream(imageUrl, outputStream)){
editor.commit()
}else{
editor.abort()
}
}
diskLruCache.flush()
} catch (e: IOException) {
e.printStackTrace()
}
}
/**
* 網絡下載下傳
* @param imageUrl 圖檔url
* @param outputStream diskLruCache的IO流
* @return 是否下載下傳成功
*/
private fun downloadUrlStream(imageUrl: String, outputStream: OutputStream) :Boolean{
val request = Request.Builder()
.url(imageUrl)
.build()
try {
val response = HttpUtil.getInstance().client.newCall(request).execute()
if (response.isSuccessful) {
if (response.body()!= null){
val bis = response.body()!!.byteStream().buffered()
val bos = outputStream.buffered()
try {
bis.copyTo(bos)
return true
}catch (e: IOException) {
e.printStackTrace()
}finally {
bis.close()
bos.close()
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return false
}
/**
* 從緩存中得到Bitmap對象
* @param imageUrl 圖檔url
* @return Bitmap
*/
fun getBitmapFromMemCache(imageUrl: String,maxWidth:Int,maxHeight:Int): Bitmap? {
val key = Md5Util.getInstance().MD5(imageUrl)
val snapShot = diskLruCache.get(key)
if (snapShot!=null){
val inputStream = snapShot.getSource(0).buffer().inputStream()
return BitmapUtil.getInstance().getBitmap(inputStream,maxWidth,maxHeight,false)
}
return null
}
/**
* 從緩存中删除指定的Bitmap
* @param imageUrl 圖檔url
*/
fun removeBitmapFromMemory(imageUrl: String) {
val key = Md5Util.getInstance().MD5(imageUrl)
diskLruCache.remove(key)
}
/**
* 所有緩存資料的總位元組數
*/
fun getSize():Long{
return diskLruCache.size()
}
/**
* 同步資料,在Activity的onPause()方法中去調用一次flush()方法
*/
fun flush(){
diskLruCache.flush()
}
/**
* 在Activity的onDestroy()方法中去調用close()方法
*/
fun close(){
diskLruCache.close()
}
/**
* 将所有的緩存資料全部删除
*/
fun delete(){
diskLruCache.delete()
}
/**
* 緩存目錄
*/
private fun directory(fileName: String): File {
val path: String
//判斷SD卡是否可用
if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !Environment.isExternalStorageRemovable()) {
path = application.externalCacheDir!!.absolutePath
} else {
path = application.cacheDir.absolutePath
}
val file = File(path, fileName)
if (!file.exists()) {
file.mkdirs()
}
return file
}
/**
* app版本
*/
@Throws(PackageManager.NameNotFoundException::class)
private fun getAppVersion(): Int {
val info = application.packageManager.getPackageInfo(application.packageName, 0)
return info.versionCode
}
}
三、三級緩存工具類
enum class KotlinCacheUtil {
INSTANCE;
private lateinit var application: Application
/**
* 初始化
* @param application
*/
fun init(application: Application) {
this.application = application
screenWidth()
//記憶體緩存初始化
LruCacheUtil.getInstance().init()
//磁盤緩存初始化
OKDiskLruCacheUtil.INSTANCE.init(application)
//http初始化
HttpUtil.getInstance().init()
}
/**
* 三級緩存
* @param imageUrl 圖檔url
* @param imageView 顯示控件
* @param placeholderBitmap 占位圖
*/
private fun setImageBitmap(imageUrl: String, imageView: ImageView, placeholderBitmap: Bitmap) :Job{
//設定占位圖
imageView.setImageBitmap(placeholderBitmap)
return GlobalScope.launch {
//1.先從記憶體讀取
var bitmap: Bitmap? = LruCacheUtil.getInstance().getBitmapFromMemCache(imageUrl)
if (bitmap == null) {
//2.如果沒有則從磁盤讀取
bitmap = OKDiskLruCacheUtil.INSTANCE.getBitmapFromMemCache(imageUrl,width,height)
if (bitmap == null) {
if (isNetworkConnected()) {
//3.從網絡擷取并加入磁盤緩存
OKDiskLruCacheUtil.INSTANCE.addBitmapToMemory(imageUrl)
//從磁盤緩存中擷取bitmap
bitmap = OKDiskLruCacheUtil.INSTANCE.getBitmapFromMemCache(imageUrl,width,height)
if (bitmap == null) {
launch (Dispatchers.Main){
show("沒有可用網絡")
}
[email protected]
}
//加入記憶體緩存
LruCacheUtil.getInstance().addBitmapToMemory(imageUrl, bitmap)
} else {
//4.如果沒有網絡提示
launch (Dispatchers.Main){
show("沒有可用網絡")
}
[email protected]
}
} else {
LruCacheUtil.getInstance().addBitmapToMemory(imageUrl, bitmap)
}
}
launch (Dispatchers.Main){
imageView.setImageBitmap(bitmap)
}
}
}
/**
* 三級緩存
* @param imageUrl 圖檔url
* @param imageView 顯示控件
* @param resId 占位圖的資源id
*/
fun setImageBitmap(imageUrl: String, imageView: ImageView, resId: Int):Job {
//設定占位圖
val bitmap = BitmapUtil.getInstance().getBitmapResources(application.resources,resId,width,height,false)
return setImageBitmap(imageUrl,imageView,bitmap)
}
/**
* 三級緩存
* @param imageUrl 圖檔url
* @param imageView 顯示控件
* @param file sd卡占位圖
*/
fun setImageBitmap(imageUrl: String, imageView: ImageView, file: File):Job {
//設定占位圖
val bitmap = BitmapUtil.getInstance().getBitmapFile(file,width,height,false)
return setImageBitmap(imageUrl,imageView,bitmap)
}
/**
* 三級緩存
* @param imageUrl 圖檔url
* @param imageView 顯示控件
* @param path 占位圖檔案路徑
* @param fileName 占位圖檔案名字
*/
fun setImageBitmap(imageUrl: String, imageView: ImageView, path:String , fileName:String):Job {
//設定占位圖
val bitmap = BitmapUtil.getInstance().getBitmapFile(path,fileName,width,height,false)
return setImageBitmap(imageUrl,imageView,bitmap)
}
/**
* 清除所有緩存
*/
private fun delete() {
LruCacheUtil.getInstance().evictAll()
OKDiskLruCacheUtil.INSTANCE.delete()
}
/**
* 檢測是否有網絡連接配接
* @return 傳回false無可用網絡
*/
private fun isNetworkConnected(): Boolean {
val mConnectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val mNetworkInfo = mConnectivityManager.activeNetworkInfo
if (mNetworkInfo != null) {
return mNetworkInfo.isConnected
}
return false
}
private fun show(msg: String) {
Toast.makeText(application, msg, Toast.LENGTH_LONG).show()
}
private var width = 0
private var height = 0
/**
* 擷取螢幕寬高
*/
private fun screenWidth() {
val wm = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val display = wm.defaultDisplay
val size = Point()
display.getSize(size)
width = size.x
height = size.y
}
}
四、網絡工具類
一個簡單的小工具類,為懶癌患者準備
public class HttpUtil {
private static volatile HttpUtil instance;
private HttpUtil() {}
public static HttpUtil getInstance() {
if (instance == null) {
synchronized (HttpUtil.class) {
if (instance == null) {
instance = new HttpUtil();
}
}
}
return instance;
}
private OkHttpClient client;
public void init(){
client = new OkHttpClient.Builder()
//設定連接配接的連接配接逾時的時間
.connectTimeout(10,TimeUnit.SECONDS)
//設定連接配接的讀取逾時時間
.readTimeout(30, TimeUnit.SECONDS)
//設定寫入逾時時間
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public OkHttpClient getClient(){
return client;
}
}
五、bitmap工具類
https://blog.csdn.net/a896159476/article/details/92996925
完整代碼
https://github.com/a896159476/KotlinTest