http://developer.android.com/training/displaying-bitmaps/index.html

.手机内存资源有限

.Bitmap占用的内存大

.App有时需要同时加载多张bitmap到内存

一张 2592x1936 的照片,在默认 ARGB_8888 的情况下,占用的内存: 19MB (2592*1936*4 bytes)

1.图片内存占用的计算

android 3.1之前 (level 12):

int bytes = bmp.getRowBytes() * bmp.getHeight()

android 3.1 开始增加了方法,实现和上述是一样的:(android4.4开始,这个方法返回的数值可能不准)

bmp.getByteCount();

/**
* Returns the number of bytes used to store this bitmap's pixels.
*/
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}

从android 4.4开始要用新增的方法:

bmp.getAllocationByteCount();

This can be larger than the result of getByteCount() if a bitmap is reused to decode other bitmaps of smaller size, or by manual reconfiguration. See reconfigure(int, int, Config),setWidth(int), setHeight(int), setConfig(Bitmap.Config), and BitmapFactory.Options.inBitmap. If a bitmap is not modified in this way, this value will be the same as that returned bygetByteCount().

2. Loading Large Bitmaps Efficiently

如果图片的实际大小比界面上显示的要大,会消耗更多的内存和需要额外的缩放计算。

BitmapFactory 提供了一系列从各种资源创建Bitmap的方法,如果直接创建很可能会因为图片过大而OOM。 BitmapFactory.Options 中 inJustDecodeBounds = true 时,只解析出图片的大小等信息,不会创建bitmap:

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;

加载图片时,设置inSampleSize可以减小加载到内存图片的尺寸,比如inSampleSize=2,则图片的长和高都变为原来的1/2.

//计算inSampleSize:
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) { final int halfHeight = height / 2;
final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
} return inSampleSize;
} //根据需要的大小加载图片
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);
}

3.Processing Bitmaps Off the UI Thread

图片的加载通常耗时较长,不应放在UI线程中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
//WeakReference 防止因为AsyncTask保留有ImageView 的引用而导致其不能正常释放,
//所以不能保证执行onPostExecute时,imageView还是有效的,因此要检查null.
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);
}
}
}
} public void loadBitmap(int resId, ImageView imageView) {
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(resId);
}

如何在复用View的同时,正确的利用多线程加载图片的一个解决方案。

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();
}
} 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);
}
} public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == 0 || 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;
} 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;
} 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);
}
}
}
}

4.Caching Bitmaps 图片缓存

为了保证效率和界面的流畅性,需要缓存图片。一般分为mem缓存和disk缓存。

MEM:
Mem缓存时常用到 LruCache类(Added in API level 12,support-lib4中也有)。

其内部实现,是将对象用强引用保存在一个LinkedHashMap中,当达到最大的缓存数量或者缓存内存大小时,会将最近最少使用的对象移除(每次添加一个新对象或者get使用一个对象时,都将其放到链表的头部,尾部的对象就是最少使用,要移除的

以前常见缓存方式是用softReference和weakReference,但是现在不推荐使用了。 从 2.3开始,GC对软/弱引用的回收更加具有aggressive,也就是说它们很快就会被回收,起不到缓存的作用,也使其效率很低。 其次 3.0之前,bitmap 的内存数据是存储在 native 内存中的。

没有一个固定的缓存大小能适合所有的app,缓存太小影响效率,太大可能导致OOM。

设置缓存的大小需要考虑以下因素:

.最大可用内存多少。

.一次有多少图片在屏幕上,有多少即将要显示。

.屏幕的尺寸和密度。

.bitmap的参数影响其占用内存的多少 。

.图片加载的频率,根据频率高低可以考虑用多个lrucache来缓存不同类别的图片。

.平衡图片的质量和数量,有时需要大量低质量的图片,有时又需要高质量的图片。

Disk:

Mem缓存可能很快被占满。当应用切换到后台,可能被kill掉,导致mem缓存丢失,要重新加载处理图片,所以需要disk缓存。 从disk读数据比mem读慢,而且时间不确定,所以要放在工作线程中。

DiskLruCache

5.Managing Bitmap Memory

.android 2.2(API level 8)和以下的版本中,GC工作时,会阻塞app线程,导致性能降低。 从2.3开始,GC是并行工作的,bitmap没有引用指向时会很快被回收掉。

.Android 2.3.3 (API level 10)和以下的版本中,bitmap的像素数据是存储在 native内存中的,而bitmap对象是分配在虚拟机的堆上,二者是分开的。native内存中像素数据是以一种不可预测的方式释放,很可能导致OOM。

.从Android 3.0 (API level 11)开始,bitmap的像素数据和bitmap对象本身都是存储在dalvik虚拟机的堆上的。所以最好在3.0以上的机器上调试图片内存占用的问题。

所以3.0以下时,推荐使用 recycle()来显示的释放bitmap的内存。 但要注意的是,调用该方法时要确认该bitmap对象后续不会再使用到,否则会抛异常: "Canvas: trying to use a recycled bitmap". bitmapfun示例中使用了一种基于引用计数的解决方案。

Options.inBitmap

从android 3.0 开始,引入了 BitmapFactory.Options.inBitmap 字段,可以传入一个不再使用的bitmap对象,来加载一个新的bitmap,新的对象会复用旧的bitmap的内存,这样可以减少内存的分配和释放,提高了效率。

但是使用inBitmap字段有一定的限制条件:

.首先,要求旧的bitmap必须是mutable的才能复用(options.inMutable = true)。

.Android 4.4 (API level 19)之前,要求新/旧对象的尺寸必须是相等的(the dimensions must match exactly and the inSampleSize must be 1)。

.从4.4开始,新对象占用总字节数<=旧bitmap对象占用的总字节数就可以复用(the byte size of the new bitmap is smaller than the reusable bitmap candidate allocation byte count.)。

在bitmapFun 示例中,从LruCache内存缓存中移除的bitmap,放入了一个 Set<SoftReference<Bitmap>> mReusableBitmaps 中,后续decode新bitmap对象的时候,到这里面去找看是否有能符合条件的,可以复用的bitmap,有的话就复用其内存。

static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
} // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
} static int getBytesPerPixel(Config config) {
if (config == Config.ARGB_8888) {
return 4;
} else if (config == Config.RGB_565) {
return 2;
} else if (config == Config.ARGB_4444) {
return 2;
} else if (config == Config.ALPHA_8) {
return 1;
}
return 1;
}

6. Displaying Bitmaps in Your UI

Displaying Bitmaps Efficiently 显示图片相关的更多相关文章

  1. 微信小程序--图片相关问题合辑

    图片上传相关文章 微信小程序多张图片上传功能 微信小程序开发(二)图片上传 微信小程序上传一或多张图片 微信小程序实现选择图片九宫格带预览 ETL:微信小程序之图片上传 微信小程序wx.preview ...

  2. Android训练课程(Android Training) - 高效的显示图片

    高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...

  3. Loading Large Bitmaps Efficiently

    有效地加载大位图文件-Loading Large Bitmaps Efficiently 图像有各种不同的形状和大小.在许多情况下,他们往往比一个典型应用程序的用户界面(UI)所需要的资源更大.例如, ...

  4. OpenCV 2.2版本号以上显示图片到 MFC 的 Picture Control 控件中

    OpenCV 2.2 以及后面的版本号取消掉了 CvvImage.h 和CvvImage.cpp 两个文件,直接导致了苦逼的程序猿无法调用里面的显示函数来将图片显示到 MFC 的 Picture Co ...

  5. FrameBuffer系列 之 显示图片

     摘自:http://blog.csdn.net/luxiaoxun/article/details/7622988 #include <unistd.h> #include < ...

  6. Web前端学习(4):显示图片、url与文件路径

    本章主旨 介绍<img>标签及其基本属性:介绍URL和文件路径 在上一章中,我简单地介绍了HTML的一些基本标签及基本属性,例如,我们用<p>标签来标记文本段落,用<h1 ...

  7. Android开发——获得Json数据,并显示图片

    流程介绍 使用okhttp网络框架进行get请求,获得json数据 //一个封装好的工具类的静态方法 public static void sendOkHttpRequest(final String ...

  8. PS不显示图片

    最近安装Adobe AIR的时候因为安装不了,删除了相关的配置文件,安装了Adobe AIR之后,用PS打开图片的时候发现不显示图片了,如图: 最后通过万能的百度了解到了是笔记本都采用了双显卡(i3. ...

  9. [AX]AX2012 R2 EP员工自助服务中的产品不能显示图片的问题

    在员工自助服务EP站点中员工可以通过Order products自助提交采购申请,在正确设置员工采购目录后会罗列出允许员工购买的产品,每个产品都可带有图片,我们可以通过Product image来为产 ...

随机推荐

  1. [转]PYTHON-SCRAPY-WINDOWS下的安装笔记

    分类: Crawler.Net Python2014-05-15 13:36 127人阅读 评论(0) 收藏 举报 PYTHON SCRAPY 1.安装PYTHON2.7.6,下载地址:https:/ ...

  2. centos7下安装nodejs

    http://www.runoob.com/nodejs/nodejs-install-setup.html这个教程中注明 注意:Linux上安装Node.js需要安装Python 2.6 或 2.7 ...

  3. MySQL命令行导出数据库

    MySQL命令行导出数据库:1,进入MySQL目录下的bin文件夹:cd MySQL中到bin文件夹的目录如我输入的命令行:cd C:\Program Files\MySQL\MySQL Server ...

  4. HDU 1839

    http://acm.hdu.edu.cn/showproblem.php?pid=1839 题意:从1到n,要求时间小于等于T到达.每条边有一个容量,问最多能运多少货物. 分析:最多能运的货物取决于 ...

  5. Android 中onConfigurationChanged问题

    onConfigurationChanged 不生效问题解决方案: 1).首先,需要重写onConfigurationChanged函数 @Override    public void onConf ...

  6. 修改hive分区表,在分区列前增加一个字段

    本文主要为了测试,在有数据的分区表中增加新的一个非分区字段后,新数据加入表中是否正常. 原始数据 1;zhangsan 2;zhangsan 3;zhangsan 4;lisi 5;lisi 6;li ...

  7. C++ 类知识点

    1. member function definitions are processed after the compiler processes all of the declarations in ...

  8. mssql 2008 游标 临时表 作业批处理失败问题

    在项目中,写过一个作业,当订单超过1个小时未支付,则取消订单,同时返回订单使用的积分,优惠券,余额. 经过调试发现,作业存储过程中,使用了游标,而且使用了临时表,在游标第一次创建临时表时,没有问题,但 ...

  9. Lae程序员小漫画(三),仅供一乐

    Lae软件开发,快乐程序员!

  10. Android---闪频页和倒计时

    android闪频的实现非常简单,使用Handler对象的postDelayed()方法就可以实现.在这个方法里传递一个Runnable对象和一个延迟的时间.该方法实现了一个延迟执行的效果,延迟的时间 ...