編輯:關於Android編程
DiskLruCache是一套硬盤緩存的解決方案,算法同LruCache基於LRU算法。
DiskLruCache不是由Google官方編寫的,這個類沒有被包含在Android API當中。這個類需要從網上下載
可以到Github這裡下載
//創建一個DiskLruCache的實例,需要調用它的open()靜態方法
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
File directory:指定的是數據的緩存地址,一般為應用的內部/外部緩存文件夾,這樣當應用卸載時,裡面的數據也會被清除。
int appVersion:指定當前應用程序的版本號。
int valueCount :指定同一個key可以對應多少個緩存文件,一般都是傳1,這樣key和value一一對應,方便查找。
long maxsize:指定最多可以緩存數據的總字節數。
a. 獲取DiskLruCache的緩存目錄,可以根據緩存的類型來傳入不同的參數:
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
cacheDir = new File(cachePath + File.separator + uniqueName);
// 判斷緩存文件夾是否存在,不存在則創建
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
return cacheDir;
}
當SD卡存在或者SD卡不可被移除的時候,就調用getExternalCacheDir()方法來獲取手機內部緩存路徑,否則就調用getCacheDir()方法來獲取手機外部緩存路徑。前者獲取到的就是 /sdcard/Android/data//cache 這個路徑,後者獲取到的是 /data/data//cache 這個路徑。
b. 獲取應用版本號:
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
每當版本號改變,緩存路徑下存儲的所有數據都會被清除掉。
c. 所以創建DiskLruCache對象方式如下:
private DiskLruCache mDiskCache;
//指定磁盤緩存大小
private long DISK_CACHE_SIZE = 1024 * 1024 * 10;//10MB
//得到緩存文件
File cacheDir = getDiskCacheDir(mContext, "Bitmap");
mDiskCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1,DISK_CACHE_SIZE);
2. 將文件寫入DiskLruCache緩存
寫入的操作是借助DiskLruCache.Editor這個類完成,需要調用DiskLruCache的edit()方法來獲取實例。
public Editor edit(String key) throws IOException
edit()方法接收一個參數key,該key將會成為緩存文件的文件名,並且需要和文件的URL是一 一對應。
直接使用URL來作為key不太合適,因為文件的URL中可能包含一些特殊字符,這些字符有可能在命名文件時是不合法的。
最簡單的做法就是將圖片的URL進行MD5編碼,編碼後的字符串肯定是唯一的,並且只會包含0-F這樣的字符,完全符合文件的命名規則。
將文件的URL字符串進行MD5編碼:
public String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
獲取DiskLruCache.Editor的實例:
String imageUrl = "http://.....jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
獲取editor的輸出流並寫入數據
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(url, outputStream)) {
editor.commit(); // 提交編輯
} else {
editor.abort(); // 放棄編輯
}
mDiskCache.flush(); // 刷新緩存
}
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
注意newOutputStream()方法接收一個index參數,由於前面在設置valueCount的時候指定的是1,這裡index傳0就可以了。
在寫入操作執行完之後,我們還需要調用一下commit()方法進行提交才能使寫入生效,調用abort()方法的話則表示放棄此次寫入。
3. 獲取緩存文件:
a. 通過DiskLruCache的get方法來得到一個Snapshot對象
public synchronized Snapshot get(String key) throws IOException
get()方法要求傳入一個key來獲取到相應的緩存數據,而這個key就是將文件URL進行MD5編碼後的值
String imageUrl = "http://.....jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
b. 通過Snapshot得到一個文件輸入流
InputStream is = snapShot.getInputStream(0);
getInputStream()方法也需要傳一個index參數,這裡傳入0。這是因為我們在open方法中設置了一個key對應一個文件
c. 通過文件輸入流得到文件對象(以獲取bitmap為例)
Bitmap bitmap = BitmapFactory.decodeStream(is);
4. 移除某個key的緩存
借助DiskLruCache的remove()方法移除緩存
public synchronized boolean remove(String key) throws IOException
這個方法不經常被調用。因為DiskLruCache會根據我們在調用open()方法時設定的緩存最大值來自動刪除多余的緩存。
5. 獲取緩存的總字節數和刪除全部緩存
借助DiskLruCache的size()方法獲取緩存的總字節數
public synchronized long size()
這個方法會返回當前緩存路徑下所有緩存數據的總字節數,以byte為單位。如果應用程序中需要在界面上顯示當前緩存數據的總大小,就可以通過調用這個方法計算出。
public void delete() throws IOException
這個方法用於將所有的緩存數據全部刪除,可以實現手動清理緩存功能。
6. 刷新緩存狀態
public synchronized void flush() throws IOException {
checkNotClosed();
trimToSize();
journalWriter.flush();
}
這個方法用於將內存中的操作記錄同步到日志文件(journal文件)當中。這個方法非常重要,因為DiskLruCache能夠正常工作的前提就是要依賴於journal文件中的內容。可在Activity的onPause()方法中去調用一次flush()方法。
二、使用DiskLruCache對圖片進行內存緩存
根據DiskLruCache,我們可以將它封裝在DiskCacheImageLoader來對圖片進行緩存。設計模式為使用單例模式來設計:
可以查看Logcat中顯示的異步任務個數:
package com.cxmscb.cxm.cacheproject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Set;
import libcore.io.DiskLruCache;
/**
* 利用DiskLruCache來緩存圖片
*/
public class DiskCacheImageLoader {
private Context mContext;
private Set mTaskSet;
//DiskLruCache
private DiskLruCache mDiskCache;
private static DiskCacheImageLoader mDiskCacheImageLoader;
public static DiskCacheImageLoader getInstance(Context context){
if(mDiskCacheImageLoader==null){
synchronized (DiskCacheImageLoader.class){
if(mDiskCacheImageLoader==null){
mDiskCacheImageLoader = new DiskCacheImageLoader(context);
}
}
}
return mDiskCacheImageLoader;
}
private DiskCacheImageLoader(Context context) {
mTaskSet = new HashSet<>();
mContext = context.getApplicationContext();
//得到緩存文件
File diskCacheDir = getDiskCacheDir(mContext, "Bitmap");
//如果文件不存在 直接創建
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
try {
mDiskCache = DiskLruCache.open(diskCacheDir, 1, 1,
1024*1024*20);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 將一個URL轉換成bitmap對象
*
*/
public Bitmap getBitmapFromURL(String urlStr) {
Bitmap bitmap;
InputStream is = null;
try {
URL url = new URL(urlStr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(connection.getInputStream(), 1024*8);
bitmap = BitmapFactory.decodeStream(is);
connection.disconnect();
return bitmap;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 將URL中的圖片保存到輸出流中
*
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 1024*8);
out = new BufferedOutputStream(outputStream, 1024*8);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 普通地加載url
*
*/
public void loadImage(ImageView imageView,String url){
//從緩存中取出圖片
Bitmap bitmap = null;
try {
bitmap = getBitmapFromDiskCache(url);
} catch (IOException e) {
e.printStackTrace();
}
//如果緩存中沒有,則需要從網絡中下載
if (bitmap == null) {
DiskCacheAsyncTask task = new DiskCacheAsyncTask(imageView);
task.execute(url);
mTaskSet.add(task);
} else {
//如果緩存中有 直接設置
imageView.setImageBitmap(bitmap);
}
}
/**
* 為listview加載從start到end的所有的Image
*
*/
public void loadTagedImagesInListView(int start, int end,String[] urls,ListView mListView) {
for (int i = start; i < end; i++) {
String url = urls[i];
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
loadImage(imageView,url);
}
Log.i("num of task"," "+mTaskSet.size());
}
/**
* 創建緩存文件
*
*/
public File getDiskCacheDir(Context context, String filePath) {
boolean externalStorageAvailable = Environment
.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
final String cachePath;
if (externalStorageAvailable) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + filePath);
}
/**
* 將URL轉換成key
*
*/
private String hashKeyFormUrl(String url) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(url.hashCode());
}
return cacheKey;
}
/**
* 將Url的字節數組轉換成哈希字符串
*
*/
private String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* 將Bitmap寫入緩存
*
*/
private Bitmap addBitmapToDiskCache(String url) throws IOException {
if (mDiskCache == null) {
return null;
}
//設置key,並根據URL保存輸出流的返回值決定是否提交至緩存
String key = hashKeyFormUrl(url);
//得到Editor對象
DiskLruCache.Editor editor = mDiskCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(url, outputStream)) {
//提交寫入操作
editor.commit();
} else {
//撤銷寫入操作
editor.abort();
}
mDiskCache.flush();
}
return getBitmapFromDiskCache(url);
}
/**
* 從緩存中取出Bitmap
*
*/
private Bitmap getBitmapFromDiskCache(String url) throws IOException {
//如果緩存中為空 直接返回為空
if (mDiskCache == null) {
return null;
}
//通過key值在緩存中找到對應的Bitmap
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
//通過key得到Snapshot對象
DiskLruCache.Snapshot snapShot = mDiskCache.get(key);
if (snapShot != null) {
//得到文件輸入流
InputStream ins = snapShot.getInputStream(0);
bitmap = BitmapFactory.decodeStream(ins);
}
return bitmap;
}
/**
* 異步任務類
*/
private class DiskCacheAsyncTask extends AsyncTask {
private ImageView imageView;
public DiskCacheAsyncTask(ImageView imageView){
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bitmap = getBitmapFromURL(params[0]);
//保存到緩存中
if (bitmap != null) {
try {
//寫入緩存
addBitmapToDiskCache(params[0]);
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
mTaskSet.remove(this);
Log.i("num of task"," "+mTaskSet.size());
}
}
/**
* 停止所有當前正在運行的任務
*/
public void cancelAllTask() {
if (mTaskSet != null) {
for (DiskCacheAsyncTask task : mTaskSet) {
task.cancel(false);
}
mTaskSet.clear();
Log.i("num of task"," "+mTaskSet.size());
}
}
}
三、 使用LruCacheImageLoader來加載網絡圖片:
一、項目效果圖:
開始ListView從網絡上加載圖片,關閉網絡退出應用後,再打開應用,從本地DiskLruCache中加載圖片
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPjxjb2RlPrb+oaK21Expc3RWaWV3vNPU2M28xqy1xNPFu6+jujwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHByZSBjbGFzcz0="brush:java;">
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int scrollState) {
if(scrollState==SCROLL_STATE_IDLE){
mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView);
}else {
mDiskImageLoader.cancelAllTask();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mStart = firstVisibleItem;
mEnd = firstVisbleItem + visibleItemCount;
if(mFirstIn && visibleItemCount > 0){
mDiskImageLoader.loadTagedImagesInListView(mStart,mEnd,urls,listView);
mFirstIn = false;
}
}
});
完整項目地址:
Github地址
ListFragment繼承於Fragment。因此它具有Fragment的特性,能夠作為activity中的一部分,目的也是為了使頁面設計更加靈活。相比Fragment
百度google大家多說的是任務管理器 kill掉adb 或者重啟adb server,但我任務管理器就沒有adb ,猜測是某個程序占用了adb端口。於是按此思路查找。5
1. UIAutomatorViewer自動化測試是Android測試的趨勢, 穩定\復用, 最常用的工具就是Espresso.使用UIAutomatorViewer獲取
前言:對於ListView,大家絕對都不會陌生,只要是做過Android開發的人,哪有不用ListView的呢?只要是用過ListView的人,哪有不關心對它性能優化的呢