原文链接:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
有效的加载大图
图像存在各种形状和大小。在很多情况下,它们往往比用户界面所需要的图像要大。例如,系
统的Gallery程序显示由Android系统的摄像头拍摄的照片,它们的分辨率往往高于设备的分辨率。
既然应用程序工作在有限的内存下,理想情况下你只想在内存加载低分辨率的图片。低分辨率
的图片大小应该匹配显示它的UI控件大小。一个高分辨率的图片不会提供任何可见的利益,而且
占据了宝贵的内存,还可能由于额外的缩放导致额外的性能开销。
读取位图的尺寸和类型
BitmapFactory类提供了很多解码的方法(decodeByteArray(),decodeFile(),decodeResource())
从不同的来源创建一个Bitmap。基于你的图片数据来源选择最合适的解码方法。这些方法试
图为构建这些图片分配内存,因此很容易就会导致OutOfMemory的exception。每种类型的解
码方法可以通过BitmapFactory.Options类指定解码选项。将inJustDecodeBounds属性设置为
true,可以再解码的时候避免内存的分配。对bitmap对象返回null,但设置了outWidth,outHeight,
和outMimeType。这个技术使得你在构建bitmap之前,读取图片的尺寸和类型。
将缩小的版本加载到内存
既然已经知道了图像的尺寸,他们可以被用来确定完整的图像是否被加载到内存,或者应该
加载一个缩小的版本。下面是一些需要考虑的因素:
1.估计完整的图像加载到内存中内存的使用情况。
2.总计你确认加载这个图片,留给应用程序中其他内存需求的内存大小。
3.图像将要显示的UI控件的大小。
4.当前设备的屏幕大小和分辨率。
例如,要把一个1024*768的图片显示到一个128*96的ImageView中是不划算的。告诉解码器
对图片进行缩放,加载一个缩小的图片到内存中,把BitmapFactory.Options的inSampleSize属
性设置为true。例如,使用inSampleSize为4,将一个2048*1536的图片进行解码,将得到一个
512*384的图片。将这个图片加载到内存中只占用0.75M的内存,而不是完整图片的12M。
不要再UI线程中处理图片
使用AsyncTask
AsyncTask类提供了一个简单的方式在后台线程中执行一些工作,并将结果返回到UI线程中。
要使用它,创建一个子类,并重写提供的方法。
对于ImageView的WeakReference确保了AsyncTask不会阻止垃圾回收机制对ImageView进行
回收。当task完成时,不能保证ImageView仍然有效,因此必须在onPostExecute()方法中
检查引用。
处理并发
普遍的View组件,例如ListView和GridView,在与AsyncTask联合使用时引入了一个新的问题。
为了提供内存的使用效率,当用户滑动的时候这些组件都会回收子视图。如果每一个子视图
都触发一个AsyncTask,不能保证哪一个完成,与其相关联的子视图并没有被回收用于另一
个子视图。此外,异步的任务开始的顺序是不能保证他们完成的顺序。
有文章引出使用多线程来处理并发问题,并提供了一个解决方案,在ImageView存储一个最近
的AsyncTask的引用时,当task完成时可以进行检查。使用相同的方法,可以扩展AsyncTask
达到该目的。
创建一个专用的Drawable类的子类存储一个工作线程的引用。
在执行BitmapWorkerTask之前,创建一个AsyncDrawable并将它绑定到目标ImageView:
cancelPotentialWork方法会检查是否有另一个task已经与ImageView关联。如果有,它将会试
图去通过调用cancel()方法来取消前一个task。在一些情况下,新task的数据与已有task的
数据相同,因此再不需要做什么。
getBitmapWorkerTask()是一个辅助方法,被用来检索与一个特定ImageView关联的task。
最后一步就是onPostExecute(),检查task是否被取消,和当前的任务是否与ImageView关
联的task相匹配。
缓存图片
将一个图片加载到用户界面很简单,如果需要将一组图片加载到UI,事件就变得复杂了。在
很多情况下,随着图像在屏幕上快速滚动,需要加载的图像是无穷无尽的。
通过将不在屏幕上的子view进行回收使内存总量保持稳定。假如你不保持对任何图片的长时
间引用,垃圾回收机制将会释放加载的图片。但为了保持一个流畅,快速加载的UI,要避免
每次来回在屏幕上处理这些图像。内存和磁盘缓存通常可以帮助处理这些问题,使得组件能
够快速重新加载处理过的图像。
使用内存缓存
内存缓存以占据宝贵的内存资源为代价提供了对图像的快速访问。LruCache类对于处理缓存
图像的任务是非常好合适的,将最近被引用的对象保存在一个强引用的LinkedHashMap中,
并在内存超过分配给它的大小之前是否最近最少使用的成员对象。
要为LruCache选择一个合适的大小,应该考虑下面一些因素:
1.
2.一次在屏幕上将要显示多少张图片?多少张需要准备好显示到屏幕上?
3.屏幕的大小和密度是怎么样的?一个高分辨率的设备需要准备更多的内存来显示在低分辨率
设备上相同数量的图片。
4.图片的尺寸和配置是怎么样的,每张图片需要占据多大内存空间?
5.图片被访问的频率有多高?是否有一些比其他的访问频率要高?如果是这样,也行你可能需
要总是在内存中保持一定的项目,甚至为不同图片组创建不同的LruCache。
6.你能在质量和数量之间取得平衡吗?有时候可以存储较大数量低质量的图片,在一个后台任
务中加载高质量的图片。
并没有对所有应用程序都适合的方案,应该由你分析自己应用的情况,并拿出一个合理的解决
方案。缓存太小导致额外的开销,缓存太大会导致内存溢出,并给程序的其他部分留下很少的
内存。
当把一个图片加载到一个ImageView中时,LruCache将会先进行检查。如果发现一个入口,它
被立即用来更新ImageView,否则一个后台线程会进行图像处理:
使用磁盘缓存
内存缓存对于加速访问最近浏览过的图片是非常有用的,然而,你不能倚靠存储在此的图像。
类似GridView的组件会很快占据整个缓存。你的应用程序可能会被另一个任务,例如来电而打
断,而在后台,它将会被杀死,高速缓存也会被摧毁。一旦用户恢复操作,你的应用程序需要
重新处理每个图像。
在这种情况下,可以使用磁盘高速缓存,当图片不再内存中时可以减少加载图像的时间。当然
从磁盘中读取图像比从内存中读取要慢,这个操作应该在一个后台线程中进行,因为磁盘的读
取时间是不可预知的。
内存缓存在UI线程中进行检查,而磁盘缓存在后台线程中进行检查。磁盘操作不应该在UI线
程上发生。当图片的处理过程结束后,最终的图片被添加到内存和磁盘缓存,以便以后使用。
处理配置变化
运行时的配置变化,例如屏幕方向的变化,会使得Android销毁并重写启动正在运行的activity。
幸运的是,你有一个很好的内存缓存的位图,缓存能够通过使用由调用setRetainInstance保存
的Fragment传递给新的activity。当activity被创建后,这个保留的Fragment被重写连接,你也
获得了访问现有缓存的机会,可以使得图像被快速取出并重写填充到ImageView对象。
有效的加载大图
图像存在各种形状和大小。在很多情况下,它们往往比用户界面所需要的图像要大。例如,系
统的Gallery程序显示由Android系统的摄像头拍摄的照片,它们的分辨率往往高于设备的分辨率。
既然应用程序工作在有限的内存下,理想情况下你只想在内存加载低分辨率的图片。低分辨率
的图片大小应该匹配显示它的UI控件大小。一个高分辨率的图片不会提供任何可见的利益,而且
占据了宝贵的内存,还可能由于额外的缩放导致额外的性能开销。
读取位图的尺寸和类型
BitmapFactory类提供了很多解码的方法(decodeByteArray(),decodeFile(),decodeResource())
从不同的来源创建一个Bitmap。基于你的图片数据来源选择最合适的解码方法。这些方法试
图为构建这些图片分配内存,因此很容易就会导致OutOfMemory的exception。每种类型的解
码方法可以通过BitmapFactory.Options类指定解码选项。将inJustDecodeBounds属性设置为
true,可以再解码的时候避免内存的分配。对bitmap对象返回null,但设置了outWidth,outHeight,
和outMimeType。这个技术使得你在构建bitmap之前,读取图片的尺寸和类型。
01
02
03
04
05
06
|
BitmapFactory.Options options =
new
BitmapFactory.Options();
options.inJustDecodeBounds =
true
;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int
imageHeight = options.outHeight;
int
imageWidth = options.outWidth;
String imageType = options.outMimeType;
|
将缩小的版本加载到内存
既然已经知道了图像的尺寸,他们可以被用来确定完整的图像是否被加载到内存,或者应该
加载一个缩小的版本。下面是一些需要考虑的因素:
1.估计完整的图像加载到内存中内存的使用情况。
2.总计你确认加载这个图片,留给应用程序中其他内存需求的内存大小。
3.图像将要显示的UI控件的大小。
4.当前设备的屏幕大小和分辨率。
例如,要把一个1024*768的图片显示到一个128*96的ImageView中是不划算的。告诉解码器
对图片进行缩放,加载一个缩小的图片到内存中,把BitmapFactory.Options的inSampleSize属
性设置为true。例如,使用inSampleSize为4,将一个2048*1536的图片进行解码,将得到一个
512*384的图片。将这个图片加载到内存中只占用0.75M的内存,而不是完整图片的12M。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public
static
int
calculateInSampleSize(
BitmapFactory.Options options,
int
reqWidth,
int
reqHeight) {
// Raw height and width of image
final
int
height = options.outHeight;
final
int
width = options.outWidth;
int
inSampleSize =
1
;
if
(height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final
int
heightRatio = Math.round((
float
) height / (
float
) reqHeight);
final
int
widthRatio = Math.round((
float
) width / (
float
) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return
inSampleSize;
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public
static
Bitmap decodeSampledBitmapFromResource(Resources res,
int
resId,
int
reqWidth,
int
reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final
BitmapFactory.Options options =
new
BitmapFactory.Options();
options.inJustDecodeBounds =
true
;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds =
false
;
return
BitmapFactory.decodeResource(res, resId, options);
}
|
01
02
|
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage,
100
,
100
));
|
不要再UI线程中处理图片
使用AsyncTask
AsyncTask类提供了一个简单的方式在后台线程中执行一些工作,并将结果返回到UI线程中。
要使用它,创建一个子类,并重写提供的方法。
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
|
class
BitmapWorkerTask
extends
AsyncTask<Integer, Void, Bitmap> {
private
final
WeakReference<ImageView> imageViewReference;
private
int
data =
0
;
public
BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference =
new
WeakReference<ImageView>(imageView);
}
// Decode image in background.
@Override
protected
Bitmap doInBackground(Integer... params) {
data = params[
0
];
return
decodeSampledBitmapFromResource(getResources(), data,
100
,
100
));
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected
void
onPostExecute(Bitmap bitmap) {
if
(imageViewReference !=
null
&& bitmap !=
null
) {
final
ImageView imageView = imageViewReference.get();
if
(imageView !=
null
) {
imageView.setImageBitmap(bitmap);
}
}
}
}
|
对于ImageView的WeakReference确保了AsyncTask不会阻止垃圾回收机制对ImageView进行
回收。当task完成时,不能保证ImageView仍然有效,因此必须在onPostExecute()方法中
检查引用。
01
02
03
04
|
public
void
loadBitmap(
int
resId, ImageView imageView) {
BitmapWorkerTask task =
new
BitmapWorkerTask(imageView);
task.execute(resId);
}
|
处理并发
普遍的View组件,例如ListView和GridView,在与AsyncTask联合使用时引入了一个新的问题。
为了提供内存的使用效率,当用户滑动的时候这些组件都会回收子视图。如果每一个子视图
都触发一个AsyncTask,不能保证哪一个完成,与其相关联的子视图并没有被回收用于另一
个子视图。此外,异步的任务开始的顺序是不能保证他们完成的顺序。
有文章引出使用多线程来处理并发问题,并提供了一个解决方案,在ImageView存储一个最近
的AsyncTask的引用时,当task完成时可以进行检查。使用相同的方法,可以扩展AsyncTask
达到该目的。
创建一个专用的Drawable类的子类存储一个工作线程的引用。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
static
class
AsyncDrawable
extends
BitmapDrawable {
private
final
WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public
AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super
(res, bitmap);
bitmapWorkerTaskReference =
new
WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public
BitmapWorkerTask getBitmapWorkerTask() {
return
bitmapWorkerTaskReference.get();
}
}
|
在执行BitmapWorkerTask之前,创建一个AsyncDrawable并将它绑定到目标ImageView:
01
02
03
04
05
06
07
08
09
|
public
void
loadBitmap(
int
resId, ImageView imageView) {
if
(cancelPotentialWork(resId, imageView)) {
final
BitmapWorkerTask task =
new
BitmapWorkerTask(imageView);
final
AsyncDrawable asyncDrawable =
new
AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
|
cancelPotentialWork方法会检查是否有另一个task已经与ImageView关联。如果有,它将会试
图去通过调用cancel()方法来取消前一个task。在一些情况下,新task的数据与已有task的
数据相同,因此再不需要做什么。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public
static
boolean
cancelPotentialWork(
int
data, ImageView imageView) {
final
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if
(bitmapWorkerTask !=
null
) {
final
int
bitmapData = bitmapWorkerTask.data;
if
(bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(
true
);
}
else
{
// The same work is already in progress
return
false
;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return
true
;
}
|
getBitmapWorkerTask()是一个辅助方法,被用来检索与一个特定ImageView关联的task。
01
02
03
04
05
06
07
08
09
10
|
private
static
BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if
(imageView !=
null
) {
final
Drawable drawable = imageView.getDrawable();
if
(drawable
instanceof
AsyncDrawable) {
final
AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return
asyncDrawable.getBitmapWorkerTask();
}
}
return
null
;
}
|
最后一步就是onPostExecute(),检查task是否被取消,和当前的任务是否与ImageView关
联的task相匹配。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
class
BitmapWorkerTask
extends
AsyncTask<Integer, Void, Bitmap> {
...
@Override
protected
void
onPostExecute(Bitmap bitmap) {
if
(isCancelled()) {
bitmap =
null
;
}
if
(imageViewReference !=
null
&& bitmap !=
null
) {
final
ImageView imageView = imageViewReference.get();
final
BitmapWorkerTask bitmapWorkerTask =
getBitmapWorkerTask(imageView);
if
(
this
== bitmapWorkerTask && imageView !=
null
) {
imageView.setImageBitmap(bitmap);
}
}
}
}
|
将一个图片加载到用户界面很简单,如果需要将一组图片加载到UI,事件就变得复杂了。在
很多情况下,随着图像在屏幕上快速滚动,需要加载的图像是无穷无尽的。
通过将不在屏幕上的子view进行回收使内存总量保持稳定。假如你不保持对任何图片的长时
间引用,垃圾回收机制将会释放加载的图片。但为了保持一个流畅,快速加载的UI,要避免
每次来回在屏幕上处理这些图像。内存和磁盘缓存通常可以帮助处理这些问题,使得组件能
够快速重新加载处理过的图像。
使用内存缓存
内存缓存以占据宝贵的内存资源为代价提供了对图像的快速访问。LruCache类对于处理缓存
图像的任务是非常好合适的,将最近被引用的对象保存在一个强引用的LinkedHashMap中,
并在内存超过分配给它的大小之前是否最近最少使用的成员对象。
要为LruCache选择一个合适的大小,应该考虑下面一些因素:
1.
2.一次在屏幕上将要显示多少张图片?多少张需要准备好显示到屏幕上?
3.屏幕的大小和密度是怎么样的?一个高分辨率的设备需要准备更多的内存来显示在低分辨率
设备上相同数量的图片。
4.图片的尺寸和配置是怎么样的,每张图片需要占据多大内存空间?
5.图片被访问的频率有多高?是否有一些比其他的访问频率要高?如果是这样,也行你可能需
要总是在内存中保持一定的项目,甚至为不同图片组创建不同的LruCache。
6.你能在质量和数量之间取得平衡吗?有时候可以存储较大数量低质量的图片,在一个后台任
务中加载高质量的图片。
并没有对所有应用程序都适合的方案,应该由你分析自己应用的情况,并拿出一个合理的解决
方案。缓存太小导致额外的开销,缓存太大会导致内存溢出,并给程序的其他部分留下很少的
内存。
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的组件会很快占据整个缓存。你的应用程序可能会被另一个任务,例如来电而打
断,而在后台,它将会被杀死,高速缓存也会被摧毁。一旦用户恢复操作,你的应用程序需要
重新处理每个图像。
在这种情况下,可以使用磁盘高速缓存,当图片不再内存中时可以减少加载图像的时间。当然
从磁盘中读取图像比从内存中读取要慢,这个操作应该在一个后台线程中进行,因为磁盘的读
取时间是不可预知的。
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对象。
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
);
}
}
|