将一个图片加载到用户界面很简单,若是须要将一组图片加载到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
);
}
}
|