天天看點

安卓圖檔緩存問題(轉)

一、問題描述

Android應用中經常涉及從網絡中加載大量圖檔,為提升加載速度和效率,減少網絡流量都會采用二級緩存和異步加載機制,所謂二級緩存就是通過先從記憶體中擷取、再從檔案中擷取,最後才會通路網絡。記憶體緩存(一級)本質上是Map集合以key-value對的方式存儲圖檔的url和Bitmap資訊,由于記憶體緩存會造成堆記憶體洩露, 管理相對複雜一些,可采用第三方元件,對于有經驗的可自己編寫元件,而檔案緩存比較簡單通常自己封裝一下即可。下面就通過案例看如何實作網絡圖檔加載的優化。

二、案例介紹

案例新聞的清單圖檔

三、主要核心元件

下面先看看實作一級緩存(記憶體)、二級緩存(磁盤檔案)所編寫的元件

1、MemoryCache

在記憶體中存儲圖檔(一級緩存), 采用了1個map來緩存圖檔代碼如下:

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

public

class

MemoryCache {

// 最大的緩存數

private

static

final

int

MAX_CACHE_CAPACITY =

30

;

//用Map軟引用的Bitmap對象, 保證記憶體空間足夠情況下不會被垃圾回收

private

HashMap<String, SoftReference<Bitmap>> mCacheMap =

new

LinkedHashMap<String, SoftReference<Bitmap>>() {

private

static

final

long

serialVersionUID = 1L;

//當緩存數量超過規定大小(傳回true)會清除最早放入緩存的 

protected

boolean

removeEldestEntry(

Map.Entry<String,SoftReference<Bitmap>> eldest){

return

size() > MAX_CACHE_CAPACITY;};

};

public

Bitmap get(String id){

if

(!mCacheMap.containsKey(id))

return

null

;

SoftReference<Bitmap> ref = mCacheMap.get(id);

return

ref.get();

}

public

void

put(String id, Bitmap bitmap){

mCacheMap.put(id,

new

SoftReference<Bitmap>(bitmap));

}

public

void

clear() {

try

{

for

(Map.Entry<String,SoftReference<Bitmap>>entry :mCacheMap.entrySet())

{  SoftReference<Bitmap> sr = entry.getValue();

if

(

null

!= sr) {

Bitmap bmp = sr.get();

if

(

null

!= bmp) bmp.recycle();

}

}

mCacheMap.clear();

}

catch

(Exception e) {

e.printStackTrace();}

}

}

2、FileCache

在磁盤中緩存圖檔(二級緩存),代碼如下

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

public

class

FileCache {

//緩存檔案目錄

private

File mCacheDir;

public

FileCache(Context context, File cacheDir, String dir){

if

(android.os.Environment.getExternalStorageState().equals、(android.os.Environment.MEDIA_MOUNTED))

mCacheDir =

new

File(cacheDir, dir);

else

mCacheDir = context.getCacheDir();

// 如何擷取系統内置的緩存存儲路徑

if

(!mCacheDir.exists()) mCacheDir.mkdirs();

}

public

File getFile(String url){

File f=

null

;

try

{

//對url進行編輯,解決中文路徑問題

String filename = URLEncoder.encode(url,

"utf-8"

);

f =

new

File(mCacheDir, filename);

}

catch

(UnsupportedEncodingException e) {

e.printStackTrace();

}

return

f;

}

public

void

clear(){

//清除緩存檔案

File[] files = mCacheDir.listFiles();

for

(File f:files)f.delete();

}

}

3、編寫異步加載元件AsyncImageLoader

android中采用單線程模型即應用運作在UI主線程中,且Android又是實時作業系統要求及時響應否則出現ANR錯誤,是以對于耗時操作要求不能阻塞UI主線程,需要開啟一個線程處理(如本應用中的圖檔加載)并将線程放入隊列中,當運作完成後再通知UI主線程進行更改,同時移除任務——這就是異步任務,在android中實作異步可通過本系列一中所用到的AsyncTask或者使用thread+handler機制,在這裡是完全是通過代碼編寫實作的,這樣我們可以更清晰的看到異步通信的實作的本質,代碼如下

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

public

class

AsyncImageLoader{

private

MemoryCache mMemoryCache;

//記憶體緩存

private

FileCache mFileCache;

//檔案緩存

private

ExecutorService mExecutorService;

//線程池

//記錄已經加載圖檔的ImageView

private

Map<ImageView, String> mImageViews = Collections

.synchronizedMap(

new

WeakHashMap<ImageView, String>());

//儲存正在加載圖檔的url

private

List<LoadPhotoTask> mTaskQueue =

new

ArrayList<LoadPhotoTask>();

public

AsyncImageLoader(Context context, MemoryCache memoryCache, FileCache fileCache) {

mMemoryCache = memoryCache;

mFileCache = fileCache;

mExecutorService = Executors.newFixedThreadPool(

5

);

//建立一個容量為5的固定尺寸的線程池(最大正在運作的線程數量)

}

public

Bitmap loadBitmap(ImageView imageView, String url) {

//先将ImageView記錄到Map中,表示該ui已經執行過圖檔加載了

mImageViews.put(imageView, url);

Bitmap bitmap = mMemoryCache.get(url);

//先從一級緩存中擷取圖檔

if

(bitmap ==

null

) {

enquequeLoadPhoto(url, imageView);

//再從二級緩存和網絡中擷取

}

return

bitmap;

}

private

void

enquequeLoadPhoto(String url, ImageView imageView) {

//如果任務已經存在,則不重新添加

if

(isTaskExisted(url))

return

;

LoadPhotoTask task =

new

LoadPhotoTask(url, imageView);

synchronized

(mTaskQueue) {

mTaskQueue.add(task);

//将任務添加到隊列中     

}

mExecutorService.execute(task);

//向線程池中送出任務,如果沒有達到上限(5),則運作否則被阻塞

}

private

boolean

isTaskExisted(String url) {

if

(url ==

null

)

return

false

;

synchronized

(mTaskQueue) {

int

size = mTaskQueue.size();

for

(

int

i=

; i<size; i++) {

LoadPhotoTask task = mTaskQueue.get(i);

if

(task !=

null

&& task.getUrl().equals(url))

return

true

;

}

}

return

false

;

}

private

Bitmap getBitmapByUrl(String url) {

File f = mFileCache.getFile(url);

//獲得緩存圖檔路徑

Bitmap b = ImageUtil.decodeFile(f);

//獲得檔案的Bitmap資訊

if

(b !=

null

)

//不為空表示獲得了緩存的檔案

return

b;

return

ImageUtil.loadBitmapFromWeb(url, f);

//同網絡獲得圖檔

}

private

boolean

imageViewReused(ImageView imageView, String url) {

String tag = mImageViews.get(imageView);

if

(tag ==

null

|| !tag.equals(url))

return

true

;

return

false

;

}

private

void

removeTask(LoadPhotoTask task) {

synchronized

(mTaskQueue) {

mTaskQueue.remove(task);

}

}

class

LoadPhotoTask

implements

Runnable {

private

String url;

private

ImageView imageView; 

LoadPhotoTask(String url, ImageView imageView) {

this

.url = url;

this

.imageView = imageView;

}

@Override

public

void

run() {

if

(imageViewReused(imageView, url)) {

//判斷ImageView是否已經被複用

removeTask(

this

);

//如果已經被複用則删除任務

return

;

}

Bitmap bmp = getBitmapByUrl(url);

//從緩存檔案或者網絡端擷取圖檔

mMemoryCache.put(url, bmp);

// 将圖檔放入到一級緩存中

if

(!imageViewReused(imageView, url)) {

//若ImageView未加圖檔則在ui線程中顯示圖檔

BitmapDisplayer bd =

new

BitmapDisplayer(bmp, imageView, url);            Activity a = (Activity) imageView.getContext();

a.runOnUiThread(bd);

//在UI線程調用bd元件的run方法,實作為ImageView控件加載圖檔

}

removeTask(

this

);

//從隊列中移除任務

}

public

String getUrl() {

return

url;

}

}

class

BitmapDisplayer

implements

Runnable {

private

Bitmap bitmap;

private

ImageView imageView;

private

String url;

public

BitmapDisplayer(Bitmap b, ImageView imageView, String url) {

bitmap = b;

this

.imageView = imageView;

this

.url = url;

}

public

void

run() {

if

(imageViewReused(imageView, url))

return

;

if

(bitmap !=

null

)

imageView.setImageBitmap(bitmap);

}

}

public

void

destroy() {

mMemoryCache.clear();

mMemoryCache =

null

;

mImageViews.clear();

mImageViews =

null

;

mTaskQueue.clear();

mTaskQueue =

null

;

mExecutorService.shutdown();

mExecutorService =

null

;

}

}

編寫完成之後,對于異步任務的執行隻需調用AsyncImageLoader中的loadBitmap()方法即可非常友善,對于AsyncImageLoader元件的代碼最好結合注釋好好了解一下,這樣對于Android中線程之間的異步通信就會有深刻的認識。

4、工具類ImageUtil

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

public

class

ImageUtil {

public

static

Bitmap loadBitmapFromWeb(String url, File file) {

HttpURLConnection conn =

null

;

InputStream is =

null

;

OutputStream os =

null

;

try

{

Bitmap bitmap =

null

;

URL imageUrl =

new

URL(url);

conn = (HttpURLConnection) imageUrl.openConnection();

conn.setConnectTimeout(

30000

);

conn.setReadTimeout(

30000

);

conn.setInstanceFollowRedirects(

true

);

is = conn.getInputStream();

os =

new

FileOutputStream(file);

copyStream(is, os);

//将圖檔緩存到磁盤中

bitmap = decodeFile(file);

return

bitmap;

}

catch

(Exception ex) {

ex.printStackTrace();

return

null

;

}

finally

{

try

{

if

(os !=

null

) os.close();

if

(is !=

null

) is.close();

if

(conn !=

null

) conn.disconnect();

}

catch

(IOException e) {  }

}

}

public

static

Bitmap decodeFile(File f) {

try

{

return

BitmapFactory.decodeStream(

new

FileInputStream(f),

null

,

null

);

}

catch

(Exception e) { }

return

null

;

}

private

static

void

copyStream(InputStream is, OutputStream os) {

final

int

buffer_size =

1024

;

try

{

byte

[] bytes =

new

byte

[buffer_size];

for

(;;) {

int

count = is.read(bytes,

, buffer_size);

if

(count == -

1

)

break

;

os.write(bytes,

, count);

}

}

catch

(Exception ex) {

ex.printStackTrace();

}

}

}

四、測試應用

 元件之間的時序圖:

安卓圖檔緩存問題(轉)

1、編寫MainActivity

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

public

class

MainActivity

extends

Activity {

ListView list;

ListViewAdapter adapter;

@Override

public

void

onCreate(Bundle savedInstanceState) {

super

.onCreate(savedInstanceState);

setContentView(R.layout.main);

list=(ListView)findViewById(R.id.list);

adapter=

new

ListViewAdapter(

this

, mStrings);

list.setAdapter(adapter);

}

public

void

onDestroy(){

list.setAdapter(

null

);

super

.onDestroy();

adapter.destroy();

}

private

String[] mStrings={

"http://news.jb51.net/UserFiles/x_Image/x_20150606083511_0.jpg"

,

"http://news.jb51.net/UserFiles/x_Image/x_20150606082847_0.jpg"

,

…..};

2、編寫擴充卡

?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

public

class

ListViewAdapter

extends

BaseAdapter {

private

Activity mActivity;

private

String[] data;

private

static

LayoutInflater inflater=

null

;

private

AsyncImageLoader imageLoader;

//異步元件

public

ListViewAdapter(Activity mActivity, String[] d) {

this

.mActivity=mActivity;

data=d;

inflater = (LayoutInflater)mActivity.getSystemService(

Context.LAYOUT_INFLATER_SERVICE);

MemoryCache mcache=

new

MemoryCache();

//記憶體緩存

File sdCard = android.os.Environment.getExternalStorageDirectory();

//獲得SD卡

File cacheDir =

new

File(sdCard,

"jereh_cache"

);

//緩存根目錄

FileCache fcache=

new

FileCache(mActivity, cacheDir,

"news_img"

);

//檔案緩存

imageLoader =

new

AsyncImageLoader(mActivity, mcache,fcache);

}

public

int

getCount() {

return

data.length;

}

public

Object getItem(

int

position) {

return

position;

}

public

long

getItemId(

int

position) {

return

position;

}

public

View getView(

int

position, View convertView, ViewGroup parent) {

ViewHolder vh=

null

;

if

(convertView==

null

){

convertView = inflater.inflate(R.layout.item,

null

);

vh=

new

ViewHolder();

vh.tvTitle=(TextView)convertView.findViewById(R.id.text);

vh.ivImg=(ImageView)convertView.findViewById(R.id.image);

convertView.setTag(vh);   

}

else

{

vh=(ViewHolder)convertView.getTag();

}

vh.tvTitle.setText(

"标題資訊測試———— "

+position);

vh.ivImg.setTag(data[position]);

//異步加載圖檔,先從一級緩存、再二級緩存、最後網絡擷取圖檔

Bitmap bmp = imageLoader.loadBitmap(vh.ivImg, data[position]);

if

(bmp ==

null

) {

vh.ivImg.setImageResource(R.drawable.default_big);

}

else

{

vh.ivImg.setImageBitmap(bmp);

}

return

convertView;

}

private

class

ViewHolder{

TextView tvTitle;

ImageView ivImg;

}

public

void

destroy() {

imageLoader.destroy();

}

}

繼續閱讀