學(xué)習(xí)資料:
Android 開發(fā)藝術(shù)探索
其實(shí)就是完完全全摘抄,讀書筆記 : )
LruCache
和DiskLruCache
是采用了LRU(Least Recently Used)
近期最少使用算法的兩種緩存纬黎。LruCache
內(nèi)存緩存幅骄,DiskLruCache
存儲(chǔ)設(shè)備緩存
1.LruCache 內(nèi)存緩存
LruCache
是一個(gè)泛型類,內(nèi)部是一個(gè)LinkedHashMap
以強(qiáng)引用的方式存儲(chǔ)緩存對(duì)象本今,提供了get
和put
方法進(jìn)行對(duì)緩存對(duì)象的操作拆座。當(dāng)緩存滿時(shí),移除近期最少使用的緩存對(duì)象诈泼,添加新的緩存對(duì)象
- 強(qiáng)引用:直接的對(duì)象引用
- 軟引用:當(dāng)一個(gè)對(duì)象只有軟引用存在時(shí)懂拾,系統(tǒng)內(nèi)存不足時(shí),會(huì)被gc回收
- 弱引用:當(dāng)一個(gè)對(duì)象只有弱引用存在時(shí)铐达,該對(duì)象會(huì)隨時(shí)被gc回收
LruCache
是線程安全的
LruCache
典型的初始化岖赋,重寫sizeOf()
方法
int MaxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);// kB
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key,Bitmap bitmap){
//bitmap.getByteCount() = bitmap.getRowBytes() * bitmap.getHeight();
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;// KB
}
}
計(jì)算緩存對(duì)象大小的單位和總?cè)萘康膯挝灰3忠恢?/p>
一些特殊時(shí)候,還需要重寫entryRemoved()
方法瓮孙。LruCache
移除舊緩存對(duì)象時(shí)會(huì)調(diào)用這個(gè)方法唐断,根據(jù)需求可以在這個(gè)方法中完成一些資源回收工作
獲取一個(gè)緩存對(duì)象,mMemoryCache.get(key)
添加一個(gè)緩存對(duì)象杭抠,mMemoryCache.put(key,bitmap)
2.DiskLruCache 磁盤緩存
DiskLruCache
并不是Android SDK
中的類脸甘。不明白為啥,官方只進(jìn)行推薦偏灿,為何不加入SDK
中
2.1創(chuàng)建
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB
File diskCacheDir = new File(mContext,"bitmap");
if(!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
-
diskCacheDir
緩存文件夾丹诀,具體指sdcard/Android/data/package_name/cache
-
1
應(yīng)用版本號(hào),一般寫為1 -
1
單個(gè)節(jié)點(diǎn)所對(duì)應(yīng)的數(shù)據(jù)的個(gè)數(shù)翁垂,一般寫1 -
DISK_CACHE_SIZE
緩存大小
2.2添加
DishLruCache
緩存添加的操作通過(guò)Eidtor
完成铆遭,Editor
為一個(gè)緩存對(duì)象的編輯對(duì)象。
首先需要獲取圖片的url
所對(duì)應(yīng)的key
沿猜,根據(jù)key
利用edit()
來(lái)獲取Editor
對(duì)象枚荣。若此時(shí),這個(gè)緩存正在被編輯啼肩,edit()
會(huì)返回null
橄妆。DiskLruCache
不允許同時(shí)編輯同一個(gè)緩存對(duì)象。之所以把url
轉(zhuǎn)換成key
祈坠,因?yàn)閳D片的url
中可能存在特殊字符害碾,會(huì)影響使用,一般將url
的md5
值作為key
private String hashKeyFromUrl(String url){
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = byteToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
private String byteToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i ++){
String hex = Integer.toHexString(0xFF & bytes[i]);//得到十六進(jìn)制字符串
if (hex.length() == 1){
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
將url
轉(zhuǎn)成key
赦拘,利用這key
值獲取Editor
對(duì)象慌随。若這個(gè)key
的Editor
對(duì)象不存在,edit()
方法就創(chuàng)建一個(gè)新的出來(lái)另绩。通過(guò)Editor
對(duì)象可以獲取一個(gè)輸出流對(duì)象儒陨。DiskLruCache
的open()
方法中,一個(gè)節(jié)點(diǎn)只能有一個(gè)數(shù)據(jù)笋籽,edit.newOutputStream(DISK_CACHE_INDEX)
參數(shù)設(shè)置為0
String key = hashKeyFromUrl(url);
DiskLruCache.Editor editor =mDiskLruCache.edit(key);
if (editor != null){
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
}
有了這個(gè)文件輸出流蹦漠,從網(wǎng)絡(luò)加載一個(gè)圖片后,通過(guò)這個(gè)OutputStream outputStream
寫入文件系統(tǒng)
private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bis = new BufferedInputStream(urlConnection.getInputStream(),8 * 1024);
bos = new BufferedOutputStream(outputStream,8 * 1024);
int b ;
while((b = bis.read())!= -1){
bos.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (urlConnection != null){
urlConnection.disconnect();
}
closeIn(bis) ;
closeOut(bos);
}
return false;
}
上面的代碼并沒(méi)有將圖片寫入文件系統(tǒng)车海,還需要通過(guò)Editor.commit()
提交寫入操作笛园,若寫入失敗,調(diào)用abort()
方法侍芝,進(jìn)行回退整個(gè)操作
if (downLoadUrlToStream(url,outputStream)){
editor.commit();//提交
}else {
editor.abort();//重復(fù)操作
}
這時(shí)研铆,圖片已經(jīng)正確寫入文件系統(tǒng),接下來(lái)的圖片獲取就不需要請(qǐng)求網(wǎng)絡(luò)
2.3 緩存查找
查找過(guò)程州叠,也需要將url
轉(zhuǎn)換為key
棵红,然后通過(guò)DiskLruCache
的get
方法得到一個(gè)Snapshot
對(duì)象,再通過(guò)Snapshot
對(duì)象可得到緩存的文件輸入流咧栗,有了輸入流就可以得到Bitmap
對(duì)象
為了避免oom
逆甜,會(huì)使用ImageResizer
進(jìn)行縮放。若直接對(duì)FileInputStream
進(jìn)行操作致板,縮放會(huì)出現(xiàn)問(wèn)題交煞。FileInputStream
是有序的文件流,兩次decodeStream
調(diào)用會(huì)影響文件流的位置屬性斟或∷卣鳎可以通過(guò)文件流得到其所對(duì)應(yīng)的文件描述符,利用BitmapFactory.decodeFileDescriptor()
方法進(jìn)行縮放
Bitmap bitmap = null;
String key = hashKeyFromUrl(url);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null){
FileInputStream fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fis.getFD();
bitmap = imageResizer.decodeBitmapFromFileDescriptor(fileDescriptor,targetWidth,targetHeight);
if (bitmap != null){
addBitmapToMemoryCache(key,bitmap);
}
}
在查找得到Bitmap
后萝挤,把key,bitmap
添加到內(nèi)存緩存中
3.ImageLoader的實(shí)現(xiàn)
主要思路:
- 拿到圖片請(qǐng)求地址
url
后御毅,先把url
變作對(duì)應(yīng)的key
; - 利用
key
在內(nèi)存緩存中查找平斩,查找到了就進(jìn)行加載顯示圖片亚享;若沒(méi)有查到就進(jìn)行3
- 在磁盤緩存中查找,在若查到了绘面,就加載到內(nèi)存緩存欺税,后加載顯示圖片;若沒(méi)有查找到揭璃,進(jìn)行
4
- 進(jìn)行網(wǎng)絡(luò)求晚凿,拿到數(shù)據(jù)圖片,把圖片寫進(jìn)磁盤緩存成功后瘦馍,再加入到內(nèi)存緩存中歼秽,并根據(jù)實(shí)際所需顯示大小進(jìn)行合理縮放顯示
類比較長(zhǎng),查看順序:構(gòu)造方法->bindBitmap(),之后順著方法內(nèi)的方法情组,結(jié)合4個(gè)步驟燥筷,并不難理解
public class ImageLoader {
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;// 50MB
private Context mContext;
private LruCache<String, Bitmap> mMemoryCache;
private DiskLruCache mDiskLruCache;
private boolean mIsDiskLruCacheCreated = false;//用來(lái)標(biāo)記mDiskLruCache是否創(chuàng)建成功
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT+ 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final long KEEP_ALIVE = 10;
private final int DISK_CACHE_INDEX = 0;
private static final int MESSAGE_POST_RESULT = 101;
private ImageResizer imageResizer = new ImageResizer();
private static final ThreadFactory mThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"ImageLoader#"+mCount.getAndIncrement());
}
};
/**
* 創(chuàng)建線程池
*/
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),mThreadFactory
);
/**
* 創(chuàng)建Handler
*/
private Handler mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MESSAGE_POST_RESULT){
LoaderResult loadResult = (LoaderResult) msg.obj;
ImageView iv = loadResult.iv;
String url = (String) iv.getTag();
if (url.equals(loadResult.uri)){//防止加載列表形式時(shí)箩祥,滑動(dòng)復(fù)用的錯(cuò)位
iv.setImageBitmap(loadResult.bitmap);
}
}
}
};
private ImageLoader(Context mContext) {
this.mContext = mContext.getApplicationContext();
init();
}
/**
* 創(chuàng)建一個(gè)ImageLoader
*/
public static ImageLoader build(Context context) {
return new ImageLoader(context);
}
/**
* 初始化
* LruCache<String,Bitmap> mMemoryCache
* DiskLruCache mDiskLruCache
*/
private void init() {
// LruCache<String,Bitmap> mMemoryCache
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
//return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
return bitmap.getByteCount() / 1024;
}
};
// DiskLruCache mDiskLruCache
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
try {
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
mIsDiskLruCacheCreated = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 加載原始大小的圖
*/
public void bindBitmap(String uri,ImageView iv){
bindBitmap(uri,iv,0,0);
}
/**
* 異步加載網(wǎng)絡(luò)圖片 指定大小
*/
public void bindBitmap(final String uri, final ImageView iv, final int targetWidth, final int targetHeight){
iv.setTag(uri);
Bitmap bitmap = loadBitmapFormMemCache(uri);
if (bitmap != null){
iv.setImageBitmap(bitmap);
return;
}
Runnable loadBitmapTask = new Runnable() {
@Override
public void run() {
Bitmap bitmap = loadBitmap(uri,targetWidth,targetHeight);
if (bitmap != null){
LoaderResult result = new LoaderResult(iv,uri,bitmap);
Message message = mHandler.obtainMessage();
message.obj = result;
message.what = MESSAGE_POST_RESULT;
mHandler.sendMessage(message);
}
}
};
THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
}
/**
* 同步加載網(wǎng)絡(luò)圖片
*/
private Bitmap loadBitmap(String url, int targetWidth, int targetHeight) {
Bitmap bitmap = loadBitmapFormMemCache(url);
if (bitmap != null) {
return bitmap;
}
try {
bitmap = loadBitmapFromDiskCache(url, targetWidth, targetHeight);
if (bitmap != null) {
return bitmap;
}
bitmap = loadBitmapFromHttp(url, targetWidth, targetHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap == null && !mIsDiskLruCacheCreated) {//緩存文件夾創(chuàng)建失敗
bitmap = downLoadFromUrl(url);
}
return bitmap;
}
/**
* 向緩存中添加Bitmap
*/
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
/**
* 通過(guò)key拿到bitmap
*/
private Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
private Bitmap loadBitmapFormMemCache(String url) {
final String key = hashKeyFromUrl(url);
return getBitmapFromMemoryCache(key);
}
/**
* 從網(wǎng)絡(luò)進(jìn)行請(qǐng)求
*/
private Bitmap loadBitmapFromHttp(String url, int targetWidth, int targetHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("UI 線程不能進(jìn)行網(wǎng)絡(luò)訪問(wèn)");
}
if (mDiskLruCache == null) {
return null;
}
String key = hashKeyFromUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if (downLoadUrlToStream(url, outputStream)) {
editor.commit();
} else {
editor.abort();//重復(fù)操作
}
mDiskLruCache.flush();//刷新
}
return loadBitmapFromDiskCache(url, targetWidth, targetHeight);
}
/**
* 從硬盤緩存中讀取Bitmap
*/
private Bitmap loadBitmapFromDiskCache(String url, int targetWidth, int targetHeight) throws IOException {
if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException("硬盤讀取Bitmap在UI線程,UI 線程不進(jìn)行耗時(shí)操作");
}
if (mDiskLruCache == null) {
return null;
}
Bitmap bitmap = null;
String key = hashKeyFromUrl(url);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
FileDescriptor fileDescriptor = fis.getFD();
bitmap = imageResizer.decodeBitmapFromFileDescriptor(fileDescriptor, targetWidth, targetHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
}
return bitmap;
}
/**
* 將數(shù)據(jù)請(qǐng)求到流之中
*/
private boolean downLoadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bis = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
bos = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
closeIn(bis);
closeOut(bos);
}
return false;
}
/**
* 直接通過(guò)網(wǎng)絡(luò)請(qǐng)求圖片 也不做任何的縮放處理
*/
private Bitmap downLoadFromUrl(String urlString) {
Bitmap bitmap = null;
HttpURLConnection urlConnection = null;
BufferedInputStream bis = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bis = new BufferedInputStream(urlConnection.getInputStream());
bitmap = BitmapFactory.decodeStream(bis);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
closeIn(bis);
}
return bitmap;
}
/**
* 得到MD5值key
*/
private String hashKeyFromUrl(String url) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = byteToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
/**
* 將byte轉(zhuǎn)換成16進(jìn)制字符串
*/
private String byteToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);//得到十六進(jìn)制字符串
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* 得到緩存文件夾
*/
private File getDiskCacheDir(Context mContext, String uniqueName) {
//判斷儲(chǔ)存卡是否可以用
boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if (externalStorageAvailable) {
cachePath = mContext.getExternalCacheDir().getPath();//儲(chǔ)存卡
} else {
cachePath = mContext.getCacheDir().getPath();//手機(jī)自身內(nèi)存
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* 得到可用空間大小
*/
private long getUsableSpace(File file) {
return file.getUsableSpace();
}
/**
* 關(guān)閉輸入流
*/
private void closeIn(BufferedInputStream in) {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
in = null;
}
}
}
/**
* 關(guān)閉輸輸出流
*/
private void closeOut(BufferedOutputStream out) {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
out = null;
}
}
}
private static class LoaderResult {
public ImageView iv ;
public String uri;
public Bitmap bitmap;
public LoaderResult(ImageView iv, String uri, Bitmap bitmap) {
this.iv = iv;
this.uri = uri;
this.bitmap = bitmap;
}
}
}
經(jīng)過(guò)測(cè)試肆氓,是可以正常加載出圖片的,緩存文件也正常袍祖,主要是學(xué)習(xí)過(guò)程思路
3.1補(bǔ)充 Closeable接口
在ImageLoader
中,代碼最后寫了closeIn(),closeOut()
方法谢揪,完全沒(méi)必要這樣寫蕉陋。在jdk1.6
之后,所有的流都實(shí)現(xiàn)了Closeable
接口拨扶,可以用這個(gè)接口來(lái)關(guān)閉所有的流
/**
* 關(guān)閉流
*/
private void closeStream(Closeable closeable){
if (closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在downLoadFromUrl()
和downLoadUrlToStream()
方法中需要關(guān)閉流凳鬓,就可以直接closeStream(bis),closeStream(bos)
來(lái)進(jìn)行關(guān)閉流操作
4.最后
這個(gè)ImgageLoader
還非常簡(jiǎn)陋,和Glide
和Picasso
根本無(wú)法相比患民。并不會(huì)在實(shí)際工作開發(fā)中使用缩举,還是使用Glide
或者Picasso
。主要是學(xué)習(xí)基礎(chǔ)實(shí)現(xiàn)原理酒奶,學(xué)習(xí)了下Android
中緩存的部分知識(shí)蚁孔。之前面試時(shí),被問(wèn)到過(guò)ImageLoader
原理惋嚎。
最近的學(xué)習(xí)杠氢,感覺(jué)不會(huì)的東西太多了,得理一理學(xué)習(xí)的順序另伍。
注意多鍛煉身體鼻百。 共勉:)