Android 多种方式正确的载入图像,有效避免oom
图像载入的方式:
Android开发中消耗内存较多一般都是在图像上面。本文就主要介绍如何正确的展现图像降低对内存的开销,有效的避免oom现象。
首先我们知道我的获取图像的来源一般有三种源头:
1.从网络载入
2.从文件读取
3.从资源文件载入
针对这三种情况我们一般使用BitmapFactory的:decodeStream,
decodeFile,decodeResource,这三个函数来获取到bitmap然后再调用ImageView的setImageBitmap函数进行展现。
我们的内存去哪里了(为什么被消耗了这么多):
事实上我们的内存就是去bitmap里了,BitmapFactory的每一个decode函数都会生成一个bitmap对象,用于存放解码后的图像,然后返回该引用。假设图像数据较大就会造成bitmap对象申请的内存较多。假设图像过多就会造成内存不够用自然就会出现out of memory的现象。
如何才是正确的载入图像:
我们知道我们的手机屏幕有着一定的分辨率(如:840*480),图像也有自己的像素(如高清图片:1080*720)。假设将一张840*480的图片载入铺满840*480的屏幕上这就是最合适的了,此时显示效果最好。假设将一张1080*720的图像放到840*480的屏幕并不会得到更好的显示效果(和840*480的图像显示效果是一致的),反而会浪费很多其它的内存。
我们一般的做法是将一张网络获取的照片或拍摄的照片放到一个一定大小的控件上面进行展现。这里就以nexus 5x手机拍摄的照片为例说明,其摄像头的像素为1300万(拍摄图像的分辨率为4032×3024),而屏幕的分辨率为1920x1080。其摄像头的分辨率要比屏幕的分辨率大得多,假设不正确图像进行处理就直接显示在屏幕上。就会浪费掉非常多的内存(假设内存不够用直接就oom了),并且并没有达到更好的显示效果。
为了降低内存的开销。我们在载入图像时就应该參照控件(如:263pixel*263pixel)的宽高像素来获取合适大小的bitmap。
以下就一边看代码一边解说:
public static Bitmap getFitSampleBitmap(String file_path, int width, int height) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file_path, options);
options.inSampleSize = getFitInSampleSize(width, height, options);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file_path, options);
}
public static int getFitInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
int inSampleSize = 1;
if (options.outWidth > reqWidth || options.outHeight > reqHeight) {
int widthRatio = Math.round((float) options.outWidth / (float) reqWidth);
int heightRatio = Math.round((float) options.outHeight / (float) reqHeight);
inSampleSize = Math.min(widthRatio, heightRatio);
}
return inSampleSize;
}
BitmapFactory提供了BitmapFactory.Option,用于设置图像相关的參数。在调用decode的时候我们能够将其传入来对图像进行相关设置。
这里我们主要介绍option里的两个成员:inJustDecodeBounds(Boolean类型) 和inSampleSize(int类型)。
inJustDecodeBounds :假设设置为true则表示decode函数不会生成bitmap对象,仅是将图像相关的參数填充到option对象里,这样我们就能够在不生成bitmap而获取到图像的相关參数了。
inSampleSize:表示对图像像素的缩放比例。假设值为2。表示decode后的图像的像素为原图像的1/2。
在上面的代码里我们封装了个简单的getFitInSampleSize函数(将传入的option.outWidth和option.outHeight与控件的width和height相应相除再取当中较小的值)来获取一个适当的inSampleSize。
在设置了option的inSampleSize后我们将inJustDecodeBounds设置为false再次调用decode函数时就能生成bitmap了。
这里须要注意的是假设我们decodeFile解析的文件是外部存储里的文件,我们须要在Manifists加上文件的读写权限,不然获取的bitmap会为null.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
同理我们编写decodeResource的重载函数
public static Bitmap getFitSampleBitmap(Resources resources, int resId, int width, int height) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resId, options);
options.inSampleSize = getFitInSampleSize(width, height, options);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, resId, options);
}
对于decodeStream重载,和从file中载入和从resource中载入稍有不同,因对stream是一种有顺序的字符流,对其decode一次后,其顺序就会发生变化,再次进行第二次decode的时候就不能解码成功了。这也是为什么当我们对inputStream decode两次的时候会得到一个null值的bitmap的原因。
所以我们对stream类型的源须要进行转换,转换有两种思路:
1. 将inputStream的字节流读取后放到一个byte[]数组里。然后使用BitmapFactory.decodeByteArray两次decode进行压缩——可是发现这样的方法事实上治标不治本,不建议使用。详细原因接下来会介绍。
2. 将inputStream的字节流读取到一个文件中,然后通过处理file的方式来进行处理就可以——推荐。优点多,后面介绍。
1.通过decodeByteArray的形式:
public static Bitmap getFitSampleBitmap(InputStream inputStream, int width, int height) throws Exception {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
byte[] bytes = readStream(inputStream);
//BitmapFactory.decodeStream(inputStream, null, options);
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
options.inSampleSize = getFitInSampleSize(width, height, options);
options.inJustDecodeBounds = false;
// return BitmapFactory.decodeStream(inputStream, null, options);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
/*
* 从inputStream中获取字节流 数组大小
* */
public static byte[] readStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.close();
inStream.close();
return outStream.toByteArray();
}
我们发现这里的处理方式大致一看还能够,然后我们会发如今readStream函数中会返回一个byte[]数组,在这个数组的大小即为原始图像的大小,因此并没有起到节省内存的效果。
因此推荐使用第二中方式通过保存本地文件后再解码
public static Bitmap getFitSampleBitmap(InputStream inputStream, String catchFilePath,int width, int height) throws Exception {
return getFitSampleBitmap(catchStreamToFile(catchFilePath, inputStream), width, height);
}
/*
* 将inputStream中字节流保存至文件
* */
public static String catchStreamToFile(String catchFile,InputStream inStream) throws Exception {
File tempFile=new File(catchFile);
try {
if (tempFile.exists()) {
tempFile.delete();
}
tempFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
FileOutputStream fileOutputStream=new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
inStream.close();
fileOutputStream.close();
return catchFile;
}
这里我们能够看到,我们通过调用catchStreamToFile先将文件保存到指定文件名称里,然后再利用两次decodeFile的形式来处理stream流的。
这样做的优点是什么呢:
1.避免了超大的中间内存变量的生成,所以自然就避免了oom现象。
2.对于从file和resource中载入图片其本质都是从文件中载入图片的。
3.一般inputStream都是应用于网络中获取图片的方式。我们採用了用文件进行缓存的方式进行图片载入还有效的避免了来回切换activity页面时多次从网络中下载同一种图片。从而造成的卡顿现象,使用这样的方法,我们载入一次后,再进行第二次载入时,我们能够推断下是否是和第一次载入时的url是一致的,假设是那么直接从使用getFitSampleBitmap file的重载从第一次缓存的catchfile中载入就可以,这样大大提高了载入速度(在主程序里我们能够用一个map变量保存下url和catchFileName的相应关系)。
内存对照
这样我们载入相关代码就完毕了。最后我们通过一个demo来对照下正确载入图像和不处理的载入图像时的内存消耗吧。这里我们就写一个手机拍摄头像的程序吧。
还是一样一边看代码一边解说吧:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".Activity.MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<ImageView
android:layout_margin="32dp"
android:id="@+id/img_preview"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/res_photo"
/>
<Button
android:id="@+id/btn_take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TAKE PHOTO"/>
</LinearLayout>
界面非常easy:就是一个用拍照的Button和一个用于显示头像的ImageView,当中ImageView大小为100dp*100dp.
java代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button mTakePhoneButton;
private ImageView mPreviewImageView;
public static final int TAKE_PHOTO = 0;
private String photoPath = Environment.getExternalStorageDirectory() + "/outout_img.jpg";
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
init();
mTakePhoneButton.setOnClickListener(this);
}
private void init() {
mTakePhoneButton = (Button) findViewById(R.id.btn_take_photo);
mPreviewImageView = (ImageView) findViewById(R.id.img_preview);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_take_photo:
File file = new File(photoPath);
imageUri = Uri.fromFile(file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
Bitmap bitmap = null;
int requestWidth = mPreviewImageView.getWidth();
int requestHeight = mPreviewImageView.getHeight();
//不处理直接载入
bitmap = BitmapFactory.decodeFile(photoPath);
//缩放后载入:从file中载入
// bitmap = BitmapUtils.getFitSampleBitmap(photoPath,
// requestWidth, requestHeight);
mPreviewImageView.setImageBitmap(bitmap);
}
break;
}
}
}
这里简单的实现了一个调用相机的功能,点击button调用系统自带相机。然后再onActivityResult里载入拍摄的照片。
这里我们重点关注载入照片的部分:
Bitmap bitmap = null;
int requestWidth = mPreviewImageView.getWidth();
int requestHeight = mPreviewImageView.getHeight();
//不处理直接载入
bitmap = BitmapFactory.decodeFile(photoPath);
//缩放后载入:从file中载入
// bitmap = BitmapUtils.getFitSampleBitmap(photoPath,
// requestWidth, requestHeight);
mPreviewImageView.setImageBitmap(bitmap);
这里提供了两种载入照片的方式:
1.不做不论什么处理直接载入。
2.就是调用我们之前写的代码缩放后载入(这里的BitmapUtils就是将之前的代码封装成的一个工具类)。
最后我们看看在两种方式下分别的内存消耗对照图吧:
调用BitmapUtils载入的:
没拍摄照片前:
拍摄照片后:
直接载入的方式:
没拍摄照片前:
拍摄照片后:
我们能够大致计算下,在没有採用压缩方式处理的时候:
图片分辨率为4032×3024採用的是RGB_8888编码:即每一个像素点占用4个字节。因此载入一张高清图片所用到的内存大小=4032×3024×4/1024/1024=40+M.
而採用正确的载入方式呢(其屏幕显示效果一致):
图片所用到的内存大小=263×263×4/1024/1024=0.26M.
最后将全部代码上传至GitHub:包括了所以载入函数。还有拍摄相机的demo,当中github里的代码比文章里的要多一些,里面还分别測试了从stream里和rersouces里载入图片
ps:对于不同手机执行直接载入图像方式的时候可能会不能正在执行,直接就oom了。
地址:https://github.com/CoolThink/EfficientLoadingPicture.git(欢迎加星或fork)
最后感谢github上xumengyin对inputStream载入方式的询问。才有了我第二次对文章的修该,欢迎大家点多多关注我的博客。相应文章的提问我都会尽量及时回答和改动我不正确的地方。
Android 多种方式正确的载入图像,有效避免oom的更多相关文章
- Android 多种方式正确的加载图像,有效避免oom
图像加载的方式: Android开发中消耗内存较多一般都是在图像上面,本文就主要介绍怎样正确的展现图像减少对内存的开销,有效的避免oom现象.首先我们知道我的获取图像的来源一般有三种源头:1.从网络加 ...
- Android多种方式实现相机圆形预览
效果图如下: 一.为预览控件设置圆角 为控件设置ViewOutlineProvider public RoundTextureView(Context context, AttributeSet at ...
- android 设置字体颜色、EditText自己主动输入转换成大写字母的多种方式
在TextView上面设置某一个字的字体颜色为指定颜色时,能够通过java类SpannableString类和Html语言来实现. (一)SpannableString类方式 private void ...
- Spring学习总结(一)——Spring实现IoC的多种方式
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法.没有IoC的程序中我们使用面向对象编程对象的创 ...
- idea打包jar的多种方式
这里总结出用IDEA打包jar包的多种方式,以后的项目打包Jar包可以参考如下形式: 用IDEA自带的打包形式 用Maven插件maven-shade-plugin打包 用Maven插件maven-a ...
- PHP 载入图像 imagecreatefrom_gif_jpeg_png 系列函数
imagecreatefrom 系列函数用于从文件或 URL 载入一幅图像. 载入图像 imagecreatefrom 系列函数用于从文件或 URL 载入一幅图像,成功返回图像资源,失败则返回一个空字 ...
- PDO多种方式取得查询结果
PDO多种方式取得查询结果 01 December 2009 1:26 Tuesday by Sjolzy PDO最大的特点之一是它的灵活性,本节将介绍如何取得查询结果,包括: 数组(数值或关联数组) ...
- java 获取classpath下文件多种方式
java 获取classpath下文件多种方式 一:properties下配置 在resources下定义server.properties register.jks.path=classpath\: ...
- Jquery Validate 表单验证的多种方式
ASP.NET MVC Jquery Validate 表单验证的多种方式 在我们日常开发过程中,前端的表单验证很重要,如果这块处理不当,会出现很多bug .但是如果处理的好,不仅bug会很少,用户体 ...
随机推荐
- 据统计WIN10用户已经比WIN7多
数据统计机构Netmarketshare今天发布了2018年12月份最新的桌面操作系统份额报告.在看似无休止的等待之后,微软在2018年取得了最后的胜利,不仅成为市值最高的公司,而且最新的Window ...
- struts2怎么返回一个字符串给jsp?(使用json)
我们都知道使用servlet时可以直接用PrintWriter对象的print方法来向页面传送一些字符串(可以是html标签和内容),然后在用RequestDispatcher来转向网页 虽Strut ...
- ArcGIS api for javascript——查找任务-在地图上查找要素
描述 本例展示了如何使用查找任务搜索数据.本例在地图上用图表显示结果并用DojoX的grid格式化结果为表格样式. FindTask构造函数需要一个ArcGIS Server地图服务的URL.本例使用 ...
- 今天听说了一个压缩解压整型的方式-group-varint
group varint https://github.com/facebook/folly/blob/master/folly/docs/GroupVarint.md 这个是facebook的实现 ...
- UVa 11849 - CD
题目:给你两个有序序列(每一个序列中元素不同),求两序列中都出现的元素个数. 分析:简单题. 合并排序合并过程. 设置两个指针.指向两序列当前元素.那个元素小指针向后移动.相同大则计数加一,同一时候后 ...
- Android中App可分配内存的大小
现在真实测试结果: 1,为了搞清楚每个应用程序在Android系统中最多可分配多少内存空间,我们使用了真机进行测试,测试机型为魅族MX4 Pro,3G内存. 测试方法是直接申请一块较大的内存空间,看应 ...
- Announcing Zuul: Edge Service in the Cloud--转
原文地址:http://techblog.netflix.com/2013/06/announcing-zuul-edge-service-in-cloud.html The Netflix st ...
- java 返回json格式的数据
1 阿里巴巴的fastjson import com.alibaba.fastjson.JSON; 使用的时候 JSON.toJSON(list); 2 Gson 解析json数据 import c ...
- vue中eventbus的使用
eventbus的方法很是简单,我们需要做三步事情: 第一步,我们需要创造一个容器去充当我们的eventbus 第二步,我们需要去抛出,或者说提交我们的事件 第三步,我们去监听我们的那个事件(也许这才 ...
- 现实人脸识别性别之路----弄清楚train_test_split函数
'''train_test_split(trian_data,trian_target,test_size,random_state)各个参数表示的意义:trian_data表示被划分的样本特征集tr ...