將一個圖片加載到用戶界面很簡單,若是須要將一組圖片加載到UI,事件就變得複雜了。在
不少狀況下,隨着圖像在屏幕上快速滾動,須要加載的圖像是無窮無盡的。
經過將不在屏幕上的子view進行回收使內存總量保持穩定。假如你不保持對任何圖片的長時
間引用,垃圾回收機制將會釋放加載的圖片。但爲了保持一個流暢,快速加載的UI,要避免
每次來回在屏幕上處理這些圖像。內存和磁盤緩存一般能夠幫助處理這些問題,使得組件能
夠快速從新加載處理過的圖像。
使用內存緩存
內存緩存以佔據寶貴的內存資源爲代價提供了對圖像的快速訪問。LruCache類對於處理緩存
圖像的任務是很是好合適的,將最近被引用的對象保存在一個強引用的LinkedHashMap中,
並在內存超過度配給它的大小以前是否最近最少使用的成員對象。
要爲LruCache選擇一個合適的大小,應該考慮下面一些因素:
1.
2.一次在屏幕上將要顯示多少張圖片?多少張須要準備好顯示到屏幕上?
3.屏幕的大小和密度是怎麼樣的?一個高分辨率的設備須要準備更多的內存來顯示在低分辨率
設備上相同數量的圖片。
4.圖片的尺寸和配置是怎麼樣的,每張圖片須要佔據多大內存空間?
5.圖片被訪問的頻率有多高?是否有一些比其餘的訪問頻率要高?若是是這樣,也行你可能需
要老是在內存中保持必定的項目,甚至爲不一樣圖片組建立不一樣的LruCache。
6.你能在質量和數量之間取得平衡嗎?有時候能夠存儲較大數量低質量的圖片,在一個後臺任
務中加載高質量的圖片。
並無對全部應用程序都適合的方案,應該由你分析本身應用的狀況,並拿出一個合理的解決
方案。緩存過小致使額外的開銷,緩存太大會致使內存溢出,並給程序的其餘部分留下不多的
內存。
web
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
private
LruCache<String, Bitmap> mMemoryCache;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final
int
maxMemory = (
int
) (Runtime.getRuntime().maxMemory() /
1024
);
// Use 1/8th of the available memory for this memory cache.
final
int
cacheSize = maxMemory /
8
;
mMemoryCache =
new
LruCache<String, Bitmap>(cacheSize) {
@Override
protected
int
sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return
bitmap.getByteCount() /
1024
;
}
};
...
}
public
void
addBitmapToMemoryCache(String key, Bitmap bitmap) {
if
(getBitmapFromMemCache(key) ==
null
) {
mMemoryCache.put(key, bitmap);
}
}
public
Bitmap getBitmapFromMemCache(String key) {
return
mMemoryCache.get(key);
}
|
當把一個圖片加載到一個ImageView中時,LruCache將會先進行檢查。若是發現一個入口,它
被當即用來更新ImageView,不然一個後臺線程會進行圖像處理:
緩存
01
02
03
04
05
06
07
08
09
10
11
12
|
public
void
loadBitmap(
int
resId, ImageView imageView) {
final
String imageKey = String.valueOf(resId);
final
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if
(bitmap !=
null
) {
mImageView.setImageBitmap(bitmap);
}
else
{
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task =
new
BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
|
class
BitmapWorkerTask
extends
AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected
Bitmap doInBackground(Integer... params) {
final
Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[
0
],
100
,
100
));
addBitmapToMemoryCache(String.valueOf(params[
0
]), bitmap);
return
bitmap;
}
...
}
|
使用磁盤緩存
內存緩存對於加速訪問最近瀏覽過的圖片是很是有用的,然而,你不能倚靠存儲在此的圖像。
相似GridView的組件會很快佔據整個緩存。你的應用程序可能會被另外一個任務,例如來電而打
斷,而在後臺,它將會被殺死,高速緩存也會被摧毀。一旦用戶恢復操做,你的應用程序須要
從新處理每一個圖像。
在這種狀況下,可使用磁盤高速緩存,當圖片再也不內存中時能夠減小加載圖像的時間。固然
從磁盤中讀取圖像比從內存中讀取要慢,這個操做應該在一個後臺線程中進行,由於磁盤的讀
取時間是不可預知的。
app
01
02
03
04
05
06
07
08
09
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
|
private
DiskLruCache mDiskLruCache;
private
final
Object mDiskCacheLock =
new
Object();
private
boolean
mDiskCacheStarting =
true
;
private
static
final
int
DISK_CACHE_SIZE =
1024
*
1024
*
10
;
// 10MB
private
static
final
String DISK_CACHE_SUBDIR =
"thumbnails"
;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
...
// Initialize memory cache
...
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(
this
, DISK_CACHE_SUBDIR);
new
InitDiskCacheTask().execute(cacheDir);
...
}
class
InitDiskCacheTask
extends
AsyncTask<File, Void, Void> {
@Override
protected
Void doInBackground(File... params) {
synchronized
(mDiskCacheLock) {
File cacheDir = params[
0
];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting =
false
;
// Finished initialization
mDiskCacheLock.notifyAll();
// Wake any waiting threads
}
return
null
;
}
}
class
BitmapWorkerTask
extends
AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected
Bitmap doInBackground(Integer... params) {
final
String imageKey = String.valueOf(params[
0
]);
// Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(imageKey);
if
(bitmap ==
null
) {
// Not found in disk cache
// Process as normal
final
Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[
0
],
100
,
100
));
}
// Add final bitmap to caches
addBitmapToCache(imageKey, bitmap);
return
bitmap;
}
...
}
public
void
addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if
(getBitmapFromMemCache(key) ==
null
) {
mMemoryCache.put(key, bitmap);
}
// Also add to disk cache
synchronized
(mDiskCacheLock) {
if
(mDiskLruCache !=
null
&& mDiskLruCache.get(key) ==
null
) {
mDiskLruCache.put(key, bitmap);
}
}
}
public
Bitmap getBitmapFromDiskCache(String key) {
synchronized
(mDiskCacheLock) {
// Wait while disk cache is started from background thread
while
(mDiskCacheStarting) {
try
{
mDiskCacheLock.wait();
}
catch
(InterruptedException e) {}
}
if
(mDiskLruCache !=
null
) {
return
mDiskLruCache.get(key);
}
}
return
null
;
}
// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public
static
File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final
String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return
new
File(cachePath + File.separator + uniqueName);
}
|
內存緩存在UI線程中進行檢查,而磁盤緩存在後臺線程中進行檢查。磁盤操做不該該在UI線
程上發生。當圖片的處理過程結束後,最終的圖片被添加到內存和磁盤緩存,以便之後使用。
處理配置變化
運行時的配置變化,例如屏幕方向的變化,會使得Android銷燬並重寫啓動正在運行的activity。
幸運的是,你有一個很好的內存緩存的位圖,緩存可以經過使用由調用setRetainInstance保存
的Fragment傳遞給新的activity。當activity被建立後,這個保留的Fragment被重寫鏈接,你也
得到了訪問現有緩存的機會,可使得圖像被快速取出並重寫填充到ImageView對象。
ide
01
02
03
04
05
06
07
08
09
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
|
private
LruCache<String, Bitmap> mMemoryCache;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
...
RetainFragment mRetainFragment =
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
mMemoryCache = RetainFragment.mRetainedCache;
if
(mMemoryCache ==
null
) {
mMemoryCache =
new
LruCache<String, Bitmap>(cacheSize) {
...
// Initialize cache here as usual
}
mRetainFragment.mRetainedCache = mMemoryCache;
}
...
}
class
RetainFragment
extends
Fragment {
private
static
final
String TAG =
"RetainFragment"
;
public
LruCache<String, Bitmap> mRetainedCache;
public
RetainFragment() {}
public
static
RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if
(fragment ==
null
) {
fragment =
new
RetainFragment();
}
return
fragment;
}
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setRetainInstance(
true
);
}
}
|