前几天我写了一篇通过压缩Bitmap,减少OOM的文章,那篇文章的目的是按照imageview的大小来压缩bitmap,让bitmap的大小正好是imageview。但是那种算法的通用性比较差,仅仅能适合fit_xy的情况。对此我进一步分析了下这个问题,并且参考了Volley的源码,最终得出了结论:如果你要让这个压缩后的bitmap完全适合多种imageview拉伸模式,你就必须重写拉伸模式的算法,但这过于小题大做了。讨巧一点的办法就是让这个imageview不完全按照imageview的长宽进行压缩,而仅仅按照imageview的长或宽按比例缩小,得到的是一张和原图比率一样的小图,让imageview加载这个小图就行了。世上没有十全十美的事情,你这个虽然讨巧了,但问题也就来了,在某些模式下可能会有一部分图片没有显示在屏幕上,浪费了一点点内存,在cent模式下,原图的显示效果和小图的显示效果完全不一样。

总结:考虑到多种因素,我还是决定使用比较讨巧的做法,因为它通用性比较高,浪费内存的情况有,但浪费的内存很少(几kb),一般情况下我们不用center模式进行图片的显示,所以我们完全可以考虑这个方式。

工具类:

我参考了volley的代码,重新构建了工具类,下面直接贴出工具类的代码:

package com.kale.bitmaptest;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory; public class BitUtils { private static int mDesiredWidth;
private static int mDesiredHeight; /**
* @description 从Resources中加载图片
*
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
// 设置成了true,不占用内存,只获取bitmap宽高
options.inJustDecodeBounds = true;
// 初始化options对象
BitmapFactory.decodeResource(res, resId, options);
// 得到计算好的options,目标宽、目标高
options = getBestOptions(options, reqWidth, reqHeight);
Bitmap src = BitmapFactory.decodeResource(res, resId, options); // 载入一个稍大的缩略图
return createScaleBitmap(src, mDesiredWidth, mDesiredHeight); // 进一步得到目标大小的缩略图
} /**
* @description 从SD卡上加载图片
*
* @param pathName
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
options = getBestOptions(options, reqWidth, reqHeight);
Bitmap src = BitmapFactory.decodeFile(pathName, options);
return createScaleBitmap(src, mDesiredWidth, mDesiredHeight);
} /**
* @description 计算目标宽度,目标高度,inSampleSize
*
* @param options
* @param reqWidth
* @param reqHeight
* @return BitmapFactory.Options对象
*/
private static BitmapFactory.Options getBestOptions(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 读取图片长宽
int actualWidth = options.outWidth;
int actualHeight = options.outHeight;
// Then compute the dimensions we would ideally like to decode to.
mDesiredWidth = getResizedDimension(reqWidth, reqHeight, actualWidth, actualHeight);
mDesiredHeight = getResizedDimension(reqHeight, reqWidth, actualHeight, actualWidth);
// 根据现在得到计算inSampleSize
options.inSampleSize = calculateBestInSampleSize(actualWidth, actualHeight, mDesiredWidth, mDesiredHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return options;
} /**
* Scales one side of a rectangle to fit aspect ratio. 最终得到重新测量的尺寸
*
* @param maxPrimary
* Maximum size of the primary dimension (i.e. width for max
* width), or zero to maintain aspect ratio with secondary
* dimension
* @param maxSecondary
* Maximum size of the secondary dimension, or zero to maintain
* aspect ratio with primary dimension
* @param actualPrimary
* Actual size of the primary dimension
* @param actualSecondary
* Actual size of the secondary dimension
*/
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary) {
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
if (resized * ratio > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
} /**
* Returns the largest power-of-two divisor for use in downscaling a bitmap
* that will not result in the scaling past the desired dimensions.
*
* @param actualWidth
* Actual width of the bitmap
* @param actualHeight
* Actual height of the bitmap
* @param desiredWidth
* Desired width of the bitmap
* @param desiredHeight
* Desired height of the bitmap
*/
// Visible for testing.
private static int calculateBestInSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float inSampleSize = 1.0f;
while ((inSampleSize * 2) <= ratio) {
inSampleSize *= 2;
} return (int) inSampleSize;
} /**
* @description 通过传入的bitmap,进行压缩,得到符合标准的bitmap
*
* @param src
* @param dstWidth
* @param dstHeight
* @return
*/
private static Bitmap createScaleBitmap(Bitmap tempBitmap, int desiredWidth, int desiredHeight) {
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) {
// 如果是放大图片,filter决定是否平滑,如果是缩小图片,filter无影响
Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
tempBitmap.recycle(); // 释放Bitmap的native像素数组
return bitmap;
} else {
return tempBitmap; // 如果没有缩放,那么不回收
}
} }

这个工具类构造的思想和原本的构造思想完全一致,差别之处在于这里的图片是等比缩放的。

测试代码:

    public void loadBitmap(boolean exactable) {
int bmSize = 0;
Bitmap bm = null;
if (exactable) {
// 通过工具类来产生一个符合ImageView的缩略图
bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
} else {
// 直接加载原图
bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
}
iv.setImageBitmap(bm);
bmSize += bm.getByteCount(); // 得到bitmap的大小
int kb = bmSize / 1024;
int mb = kb / 1024;
kb = kb % 1024;
Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight());
Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB");
Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show();
}

通过加载原图和加载缩略图进行比较,最终在log打印出图片的宽高和图片内存占用。

测试结果:

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context="${relativePackage}.${activityClass}" > <ImageView
android:id="@+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_launcher" /> <Button
android:id="@+id/original_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:onClick="butonListener"
android:text="加载原图" /> <Button
android:id="@+id/clip_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/original_button"
android:layout_alignBottom="@+id/original_button"
android:layout_alignParentRight="true"
android:onClick="butonListener"
android:text="加载缩略图" /> </RelativeLayout>

前题:我的手机定义的imageview是100dp,实际是200pix。加载图片的实际大小:850 x 1200

① 加载原图

bitmap宽 = 567,高 = 800;

内存占用:1M 747KB

解释:最终得到的图片大小和原始图片不同,这里应该是BitmapFactory在解码时就已经做了压缩,算是自带的一个智能压缩方案吧。

② 用工具类加载缩略图

bitmap宽 = 141,高 = 200;

内存占用:110KB

解释:目标的imageview宽、高均为100dp,在我手机上换算为200pix,这里做了等比缩放处理,所以高为200.最后我们也明显的看出,用这种方式得到的图片比较小,不会轻易出现OOM

完整的activity代码:

package com.kale.bitmaptest;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.Toast; public class MainActivity extends Activity {
ImageView iv; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.imageView);
iv.setScaleType(ScaleType.CENTER_CROP);
// center 变了
getMemoryCacheSize();
} public void butonListener(View v) {
switch (v.getId()) {
case R.id.original_button:
loadBitmap(false); // 加载原图
break; case R.id.clip_button:
loadBitmap(true); // 加载缩略图
break;
}
} public void loadBitmap(boolean exactable) {
int bmSize = 0;
Bitmap bm = null;
if (exactable) {
// 通过工具类来产生一个符合ImageView的缩略图
bm = BitUtils.decodeSampledBitmapFromResource(getResources(), R.drawable.saber, iv.getWidth(), iv.getHeight());
} else {
// 直接加载原图
bm = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
}
iv.setImageBitmap(bm);
bmSize += bm.getByteCount(); // 得到bitmap的大小
int kb = bmSize / 1024;
int mb = kb / 1024;
kb = kb % 1024;
Log.d("Bitmap", "bitmap w = " + bm.getWidth() + " h = " + bm.getHeight());
Log.d("Bitmap", "bitmap size = " + mb + "MB " + kb + "KB");
Toast.makeText(this, "bitmap size = " + mb + "MB " + kb + "KB", Toast.LENGTH_LONG).show();
} public int getMemoryCacheSize() {
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
System.out.println("memory = " + memClass + "M");
return memClass;
} public int dip2px(float dpValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
} }

利弊:

利:

节约内存,降低出现OOM的几率

弊:

降低了图片的清晰度,不适用于center模式的imageview。

  

左边的是加载的缩略图,右边的是加载的原图。右边的图片明显比坐标的清晰,但锐化过于严重了,左边的虽然小,但是较为模糊。

如果你用了android studio,你可以很明显的看出内存的变化:

Android Studio下的测试代码:

package com.example.jack.loadbitmap;

import com.kale.lib.activity.KaleBaseActivity;
import com.kale.lib.utils.BitmapUtil;
import com.kale.lib.utils.EasyToast; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView; public class MainActivity extends KaleBaseActivity { ImageView mImageView; Button loadOriginPicBtn; Button loadThumbPicBtn; @Override
protected int getContentViewId() {
return R.layout.activity_main;
} @Override
protected void findViews() {
mImageView = getView(R.id.imageView);
loadOriginPicBtn = getView(R.id.loadOriginal_button);
loadThumbPicBtn = getView(R.id.loadThumb_button);
} @Override
protected void setViews() {
// 加载原始的图片
loadOriginPicBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.saber);
setBitmapToImageView(bitmap);
}
}); // 加载压缩后的图片
loadThumbPicBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bitmap = BitmapUtil.decodeSampledBitmapFromResource(getResources(), R.drawable.saber,
mImageView.getWidth(), mImageView.getHeight());
setBitmapToImageView(bitmap); }
});
} private void setBitmapToImageView(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap); int bmSize = BitmapUtil.getBitmapSize(bitmap);
int mb = bmSize / 1024 / 1024;
int kb = bmSize /1014 % 1024;
String bitmapSizeStr = mb + "MB " + kb + "KB";
Log.d("Bitmap", "bitmap w = " + bitmap.getWidth() + " h = " + bitmap.getHeight());
Log.d("Bitmap", bitmapSizeStr);
EasyToast.makeText(mContext, "bitmap size = " + bitmapSizeStr);
} }

源码下载:

http://download.csdn.net/detail/shark0017/8412329

利用AS构建的工程(推荐):http://download.csdn.net/detail/shark0017/8671957

参考自:

http://www.open-open.com/lib/view/open1329994992015.html

通用的Bitmap压缩算法,进一步节约内存(推荐)的更多相关文章

  1. 小白的CTF学习之路8——节约内存的编程方式

    今天第二更,废话不说上干货 上一章我们学习了内存和cpu间的互动方式,了解到内存的空间非常有限,所以这样就需要我们在编程的时候尽可能的节省内存空间,用最少的空间发挥最大的效果,以下是几种节约内存的方法 ...

  2. android图片的缓存--节约内存提高程序效率

    如今android应用占内存一个比一个大,android程序的质量亟待提高. 这里简单说说网络图片的缓存,我这边就简单的说说思路 1:网络图片,无疑须要去下载图片,我们不须要每次都去下载. 维护一张表 ...

  3. [转]创建节约内存的JavaBean

    转自:创建节约内存的JavaBean 如果编写节约内存的java对象 编写Java代码的时候,大多数情况下,我们很少关注一个Java对象究竟有多大(占据多少内存),更多的是关注业务与逻辑.但是殊不知, ...

  4. 节约内存:Instagram的Redis实践(转)

    一.问题:     数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案:      1.通过高速服务器Cache缓存数据库数据      2.内存数据库 三.主流解Ca ...

  5. libCURL动态分配buffer——节约内存

    libCURL是一个免费的.开源的强大客户端url传输库.支持的平台.协议甚广.平台上有Windows.Linux.FreeBSD:协议上有FTP.HTTP(S).Telnet.DICT.File等. ...

  6. Bitmap那些事之内存占用计算和加载注意事项

    前言:本来我是做电视应用的,但是因为公司要出手机,人员紧张,所以就抽调我去支援一下,谁叫俺是雷锋呢!我做的一个功能就是处理手机中的应用ICON,处理无非就是美化一下,重新与底板进行合成和裁剪,用到了很 ...

  7. 节约内存:Instagram的Redis实践(转)

    Instagram可以说是网拍App的始祖级应用,也是当前最火热的拍照App之一,Instagram的照片数量已经达到3亿,而在Instagram里,我们需要知道每一张照片的作者是谁,下面就是Inst ...

  8. 节约内存:Instagram的Redis实践

    Instagram可以说是网拍App的始祖级应用,也是当前最火热的拍照App之一,Instagram的照片数量已经达到3亿,而在Instagram里,我们需要知道每一张照片的作者是谁,下面就是Inst ...

  9. Bitmap到底占多少内存

    转至:Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存? Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟 ...

随机推荐

  1. Ocelot + IdentityServer4 构建 GateWay

    上一篇已经构建好了例子,接下来将IdentityServer4添加到Ocelot中去实现 配置一个客户端配置,可以构建一个简单的客户端信息,这里我用的混合模式,配置比较多,对于客户端模式而言实际很多都 ...

  2. 翻译 – 从心理学角度看UX设计

    本文是一篇非常不错的关于用户体验设计的文章,本文作者是一个心理学家,他从他所研究的领域去看到用户行为,用户体验,相信会给你带来不一样的观念与知识.翻译水平有限,若有不准确之处欢迎指正. ——————— ...

  3. Qwidget+opencv显示图像

    步骤 1. 设置opencv库路径 在.pro文件中添加 INCLUDEPATH += D:/opencv/OpencvMingw/opencv310/include LIBS += D:/openc ...

  4. vue-cli的工程模板与构建工具

    vue-cli的工程模板与构建工具 https://www.cnblogs.com/yinn/p/9712480.html 脚手架vue-cli系列二:vue-cli的工程模板与构建工具 上篇文章我们 ...

  5. Codeforces 466E Information Graph

    Information Graph 把询问离线之后就能随便搞了, 去check一下是不是祖先, 可以用倍增也能用dfs序. #include<bits/stdc++.h> #define ...

  6. 007.KVM虚机时间-快照管理

    一 快照管理 1.1 创建快照 [root@kvm-host ~]# virsh snapshot-create vm03-centos6.8 [root@kvm-host ~]# virsh sna ...

  7. Django中的ORM关系映射查询方面

    ORM:Object-Relation Mapping:对象-关系映射 在MVC框架中的Model模块中都包括ORM,对于开发人员主要带来了如下好处: 实现了数据模型与数据库的解耦,通过简单的配置就可 ...

  8. i春秋CTF web题(1)

    之前边看writeup,边做实验吧的web题,多多少少有些收获.但是知识点都已记不清.所以这次借助i春秋这个平台边做题,就当记笔记一样写写writeup(其实都大部分还是借鉴其他人的writeup). ...

  9. python全栈开发之匿名函数和递归函数

    python 匿名函数和递归函数 python全栈开发,匿名函数,递归函数 匿名函数 lambda函数也叫匿名函数,即函数没有具体的名称.是为了解决一些功能很简单需求而设计的一句话函数.如下: #这段 ...

  10. Android对Sqlite数据库的增删改查

    SqLite 数据库 Google 为我们提供了sqlite相关的api SqLiteOpenHelper 这是一个抽象的类 如果想要使用的话,需要其他的类去继承他 SqLiteDatabase 类 ...