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. 全国大学列表文件(较新)+ nodejs导入mongodb数据库

    直接上代码 'use strict' var fs=require('fs'), mongodb=require('mongodb').MongoClient, assert=require('ass ...

  2. DotNetBar中collapsibleSplitContainer的问题

    如果有两个collapsibleSplitContainer,并且将splitwidth都设置为1,则只有第一个起作用,如,窗体设计中的图是: 而执行的图是: 解决办法:在窗体load里手动将第二个c ...

  3. JAVA并发框架之Semaphore实现生产者与消费者模型

    分类: Java技术      锁和信号量(Semaphore)是实现多线程同步的两种常用的手段.信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0.      如果大于0,表示 ...

  4. iredmail安装脚本分析(一)---iRedmail.sh

    iredmail是一套以postfix为核心的整合邮件系统的安装脚本,可以达到快速部署邮件服务器的目的.为了让自己不遗忘shell的语法,所以闲来无事,学习一下他的代码. 我从官网下载他的最新版,解压 ...

  5. (转)笔记320 SQLSERVER中的加密函数 2013-7-11

    1 --SQLSERVER中的加密函数 2013-7-11 2 ENCRYPTBYASYMKEY() --非对称密钥 3 ENCRYPTBYCERT() --证书加密 4 ENCRYPTBYKEY() ...

  6. DBN 入门学习资料整理

    建议按序阅读 1.Deep Learning 概述 Deep Learning(深度学习)学习笔记整理系列 : http://blog.csdn.net/zouxy09/article/details ...

  7. Android之AnimationDrawable初识

    Drawable animation可以加载Drawable资源实现帧动画.AnimationDrawable是实现Drawable animations的基本类. 这里用AnimationDrawa ...

  8. Lua string库整理

    string库提供了字符串处理的通用函数. 例如字符串查找.子串.模式匹配等. 当在 Lua 中对字符串做索引时,第一个字符从 1 开始计算(而不是 C 里的 0 ). 索引可以是负数,它指从字符串末 ...

  9. iOS9 URL Schema 白名单 微信

    近期苹果公司iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装. 受此影响,当你的应用在 ...

  10. 转载《android:scaleType属性》

    在网上查了好多资料,大致都雷同,大家都是互相抄袭的,看着很费劲,不好理解,自己总结一下,留着需要看的话来查找. 代码中的例子如下: <ImageView android:id="@+i ...