您的位置:首页 > 其它

Displaying Bitmaps Efficiently

2012-06-06 11:09 155 查看
http://wiki.eoeandroid.com/index.php?title=Displaying_Bitmaps_Efficiently&diff=10485&oldid=0
http://phenom.iteye.com/blog/1541336
处理Bitmap对象的,可以帮助您的UI快速响应,避免内存溢出。如果您不小心,可能很快地消耗了内存,导致程序的崩溃。抛出异常:java.lang.OutofMemoryError: bitmap size exceeds VM budget. Bitmap会消耗很多的内存,尤其像照片,摄像头在GalaxyNexus拍一张照片有2592*1936像素,如果bitmap使用ARGB_8888配置(2.3默认的),加载这张照片到内存需要消耗约19mb内存,(2592*1936*4bytes)。就超过了应用的内存限制了。

Read Bitmap Dimensions and Type
BitmapFactory类提供了一些解码的方法decodeByteArray(),decodeFile(),decodeResource()等,来创建一个位图资源。需要选择正确的方法来解析图片资源。这些方法会构造一个位图,然后申请内存,容易造成oome,每一个方法都有一个额外的信息为您 分配解码选项BitmapFactory.Options类,设置inJustDecodeBounds属性为true时,解码避免了内存占用,它不会返回位图信息,而设置了outWidth,outHeight,outMimeTYpe。这些就可以供读取解析度与类型了。
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;

Load a Scaled Down Version into Memory
加载一个缩放后的版本到内存中,位图的dimensions从上面得到后,可用于决定是否位图可以加载到内存,或加载一个缩放后的版本。
这里给出一些因素供参考:
估计内存的消耗。
计算应用的其它部分占用的内存与当前图片应该占用的内存。
贴图目标需要多大的dimensions,如ImageView,或其它ui。
屏幕的大小与解析度。

如,显示在128x96像素的ImageView中不值得加载一个1024*768像素的图片。
在BitmapFactory.Options 设置inSampleSize的值就可以改变了。
如2048*1536像素的图片解码时inSampleSize=4,就会得到一个512*384大小的位图了,使用0.75mb内存,而不是12mb。
下面提供一个方法来计算 :
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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
注意:使用2的倍数会对解码更高效的(我估计是二进制的原因),
使用这个方法,首先需要options.inJustDecodeBounds = true;先解码一次,然后再使用新的inSampleSize值,options.inJustDecodeBounds = false来解码需要的图片。
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);
}

BitmapFactory.decode*方法,在上一篇讨论过的,不应该在ui线程上处理的情况:从硬盘加载或从网络加载。因为加载时间未知,如果时间过久,会导致程序失去响应。

a variety of factors (speed of reading from disk or network,size of image, power of CPU, etc.).

AsyncTask类提供了一个简易的方法处理后台事务,并通知ui线程。使用它需要创建一个子类,覆盖一些方法这里举一个加载图片到ImageView的例子:
class BitmapWorkerTask extends AsyncTask {
private final WeakReference imageViewReference;
private int data = 0;

public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference(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);
}
}
}
}
WeakReference是为了避免ImageView被回收时由于引用造成无法回收。所以多次判断是否为null值。这种为空的情况如Activity已经到了其它Activity,或配置变化了。

The
WeakReference
to the
ImageView
ensures that the
AsyncTask

does not prevent the
ImageView
and anything itreferences from being garbage collected. There’s no guarantee the
ImageView
is
still around when the task finishes, so you must also check the reference in
onPostExecute()
. The
ImageView
may
no longer exist, if for example, the user navigates away from the activity or if aconfiguration change happens before the task finishes.

Handle Concurrency:
ListView,GridView是另一个麻烦的地方,为了有效地使用内存,这些组件会在用户滚动时回收一些子View,如果每一个View都触发一个AsyncTask,不能保证在操作完成时,相关的View还存在。它可能被回收了 http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html 更详细地说明了并发的问题,提供了一个解决办法。存储最近的AsyncTask。

提供专用的Drawable子类来存储task,
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference bitmapWorkerTaskReference;

public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference(bitmapWorkerTask);
}

public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
在执行BitmapWorkerTask时,先创建一个AsyncDrawable,绑定到相关的ImageView中,
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已经在运行了。它先调用cancel()结束先前的方法,
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
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()确认是否任务结束了和当前的关联ImageView匹配:
class BitmapWorkerTask extends AsyncTask {
...

@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);
}
}
}
}

这个实现可以用于listview,gridview这样回收他们的子元素的组件中,只要简单地调用loadBitmap方法就可以了。

LruCache
去缓存图片

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage
collection.

Use a Disk Cache 磁盘缓存。
内存缓存更快速,当然不能仅靠内存来维持,listview,gridview会很快地占用了内存中的图片,而且你的Activity可能被销毁,然后再加载,这时就需要另一处缓存了。

磁盘缓存主要在下载图片时用到,缓存后不用再次下载,从磁盘中加载当然比从网络中要快得多了
DiskLruCache已经是一种健壮的实现 了,在4.0中提供了源码libcore/luni/src/main/java/libcore/io/DiskLruCache.java,

Handle Configuration Changes
运行时配置改变了,如屏幕的方向改变了,导致Android会销毁,重启。这就需要避免处理所有的图片了,南昌需要一个更缓和,更高效的办法。

前面已经讨论过内存缓存了,这个缓存可以通过Fragment的setRetainInstance(true)得到,Activity重建以后,Fragment会重新加载,reattached附着到Activity中下面是一个使用Fragment与LruCache在配置改变时的例子。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: