Displaying Bitmaps Efficiently 显示图片相关
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 显示图片相关的更多相关文章
- 微信小程序--图片相关问题合辑
图片上传相关文章 微信小程序多张图片上传功能 微信小程序开发(二)图片上传 微信小程序上传一或多张图片 微信小程序实现选择图片九宫格带预览 ETL:微信小程序之图片上传 微信小程序wx.preview ...
- Android训练课程(Android Training) - 高效的显示图片
高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...
- Loading Large Bitmaps Efficiently
有效地加载大位图文件-Loading Large Bitmaps Efficiently 图像有各种不同的形状和大小.在许多情况下,他们往往比一个典型应用程序的用户界面(UI)所需要的资源更大.例如, ...
- OpenCV 2.2版本号以上显示图片到 MFC 的 Picture Control 控件中
OpenCV 2.2 以及后面的版本号取消掉了 CvvImage.h 和CvvImage.cpp 两个文件,直接导致了苦逼的程序猿无法调用里面的显示函数来将图片显示到 MFC 的 Picture Co ...
- FrameBuffer系列 之 显示图片
摘自:http://blog.csdn.net/luxiaoxun/article/details/7622988 #include <unistd.h> #include < ...
- Web前端学习(4):显示图片、url与文件路径
本章主旨 介绍<img>标签及其基本属性:介绍URL和文件路径 在上一章中,我简单地介绍了HTML的一些基本标签及基本属性,例如,我们用<p>标签来标记文本段落,用<h1 ...
- Android开发——获得Json数据,并显示图片
流程介绍 使用okhttp网络框架进行get请求,获得json数据 //一个封装好的工具类的静态方法 public static void sendOkHttpRequest(final String ...
- PS不显示图片
最近安装Adobe AIR的时候因为安装不了,删除了相关的配置文件,安装了Adobe AIR之后,用PS打开图片的时候发现不显示图片了,如图: 最后通过万能的百度了解到了是笔记本都采用了双显卡(i3. ...
- [AX]AX2012 R2 EP员工自助服务中的产品不能显示图片的问题
在员工自助服务EP站点中员工可以通过Order products自助提交采购申请,在正确设置员工采购目录后会罗列出允许员工购买的产品,每个产品都可带有图片,我们可以通过Product image来为产 ...
随机推荐
- Excel 改变列表头显示方式, Excel显示列数字
'显示数字列号 Sub showCellNumber() Application.ReferenceStyle = xlR1C1 End Sub '显示字母列号 Sub showCellZimu() ...
- docker基本操作
centos 7 安装docker 目前,CentOS 仅发行版本中的内核支持 Docker. Docker 运行在 CentOS 7 上,要求系统为64位.系统内核版本为 3.10 以上. Dock ...
- CSS3阴影 box-shadow的使用和技巧总结
text-shadow是给文本添加阴影效果,box-shadow是给元素块添加周边阴影效果.随着html5和CSS3的普及,这一特殊效果使用越来越普遍. 基本语法是{box-shadow:[inset ...
- LPTHW 笨办法学python 33章
32-33章节 将for-loop和while-loop循环的. 俩种句法就不说了.简单说下个人对于for和while的理解. 我觉得他learn python the hard way这里的写法是, ...
- 调用DiscuzNT webApi 注册 登录 发帖
注册.登录Discuz论坛比较简单,网上很多教程. 3.发帖出现的问题 1.iis8.0版本 asp.net 4.0 不能发帖 将discuz 的web.config文件里的 此代码 <htt ...
- ArcGIS for Android_离在线一体化核心技术基本流程
核心思想: a.数据首先存储于ArcSDE中,要素添加GlobleID,图层数据启用数据归档或开启版本化.b.然后将ArcSDE数据库托管于ArcGIS for Server作为数据存储.c.在Arc ...
- download github files
想要下载github的文件,但是却发现无从下手.发现一个网站DownGit,非常方便,只要贴一下链接即可: https://minhaskamal.github.io/DownGit/#/home
- Halcon pick_and_place_scara_stationary_cam.hdev程序学习
此示例显示如何基于由SCARA手眼校准确定的校准信息,使用SCARA机器人执行拾取和放置应用程序. 在第一步骤中,根据模型图像定义形状模型. 然后,基于该形状模型,在每个图像中搜索对象. 对于一个选定 ...
- 【转载】RMAN备份保留策略设置相关命令
转自 http://blog.sina.com.cn/s/blog_7c5a82970101g4s7.html 策略有两种,一种是基于恢复窗口策略的,一种是基于冗余策略的. 基于恢复窗口策略: 定义的 ...
- WebAPI的Get和Post访问
/// <summary> /// MVC 调用webapi接口返回框架快捷按钮和一级菜单(Get) /// </summary> /// <param name=&qu ...