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。
下面就一边看代码一边讲解:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
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了。
同理我们编写decodeResource的重载函数
1
2
3
4
5
6
7
8
|
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); } |
这里需要注意的是如果我们decodeFile解析的文件是外部存储里的文件,我们需要在Manifists加上文件的读写权限,不然获取的bitmap会为null.
1
2
|
<uses-permission android:name= "android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" /> |
然后是decodeStream的相关重载:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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(); } |
我们发现在处理stream的时候我们并不是同之前一样通过调用两次decodeStream函数来进行设置的,而是将stream转化成byte[],然后在两次调用decodeByteArray。其原因是:如果我们两次调用按照两次调用decodeStream的方式,会发现我们得到到bitmap为null
内存对比
这样我们加载相关代码就完成了,最后我们通过一个demo来对比下正确加载图像和不处理的加载图像时的内存消耗吧,这里我们就写一个手机拍摄头像的程序吧。
还是一样一边看代码一边讲解吧:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<?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> |
界面很简单:就是一个用拍照的Button和一个用于显示头像的ImageView,其中ImageView大小为100dp*100dp.
java代码:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
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); try { if (file.exists()) { file.delete(); } file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } 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) { if (ContextCompat.checkSelfPermission( this , Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //申请WRITE_EXTERNAL_STORAGE权限 ActivityCompat.requestPermissions( this , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0 ); } 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里加载拍摄的照片。
这里我们重点关注加载照片的部分:
01
02
03
04
05
06
07
08
09
10
|
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加载的:
没拍摄照片前:
拍摄照片后:
直接加载的方式:
没拍摄照片前:
拍摄照片后:
相信看到内存对比图后也不用我再多说什么了吧,最后将所有代码上传至GitHub:包含了所有加载函数,还有拍摄相机的demo,其中github里的代码比文章里的要多一些,里面还分别测试了从stream里和rersouces里加载图片ps:对于不同手机运行直接加载图像方式的时候可能会不能正在运行,直接就oom了。
转自:ThinkCoolys
Android 多种方式正确的加载图像,有效避免oom的更多相关文章
- Android 多种方式正确的载入图像,有效避免oom
图像载入的方式: Android开发中消耗内存较多一般都是在图像上面.本文就主要介绍如何正确的展现图像降低对内存的开销,有效的避免oom现象. 首先我们知道我的获取图像的来源一般有三种源 ...
- Android异步加载图像(含线程池,缓存方法)
研究了android从网络上异步加载图像: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Han ...
- 转: Android异步加载图像小结
转:http://blog.csdn.net/sgl870927/article/details/6285535 研究了android从网络上异步加载图像,现总结如下: (1)由于android UI ...
- Android三种基本的加载网络图片方式(转)
Android三种基本的加载网络图片方式,包括普通加载网络方式.用ImageLoader加载图片.用Volley加载图片. 1. [代码]普通加载网络方式 ? 1 2 3 4 5 6 7 8 9 10 ...
- Xamarin Android Fragment的两种加载方式
android Fragment的重点: 3.0版本后引入,即minSdk要大于11 Fragment需要嵌套在Activity中使用,当然也可以嵌套到另外一个Fragment中,但这个被嵌套的Fra ...
- Android开发中如何解决加载大图片时内存溢出的问题
Android开发中如何解决加载大图片时内存溢出的问题 在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...
- Android动画之仿美团加载数据等待时,小人奔跑进度动画对话框(附顺丰快递员奔跑效果)
Android动画之仿美团加载数据等待时,小人奔跑进度动画对话框(附顺丰快递员奔跑效果) 首句依然是那句老话,你懂得! finddreams :(http://blog.csdn.net/finddr ...
- Android引入高速缓存的异步加载全分辨率
Android引进高速缓存的异步加载全分辨率 为什么要缓存 通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图.图.动不动就是图文混排.以图代文,假设这些图片都载入到 ...
- Android 开发 图片网络缓存加载框架Fresco
简介 Fresco是一个在Android应用程序中显示图像的强大系统. Fresco负责图像的加载和显示.它将从网络.本地存储或本地资源加载图像,图像加载完成前会显示一个占位图片.它有两个级别的缓存: ...
随机推荐
- div中加入iframe,可以实现Ajax的功能
div中加入iframe,可以实现Ajax的功能,如果查询的时候,比如说城市的选择,用Ajax去实现, 在.net 可以考虑用UpdatePanel,但是点击了查询刷新表格(表格需要重新刷新加载,非纯 ...
- WPF 自定义窗口标题栏
1.建一个WPF资源词典,在其中定义窗口样式,并在App.xaml中指定其为程序资源 2.写一个继续自windows的类,并指定这个类的Style为第一步资源里的样式 3.新建窗口时,分别把xaml文 ...
- UI开发核心问题-UI随屏幕自适应
屏幕分辨率对UI适配的影响 一般来说,UIRoot都会选择FixSize的缩放模式,这样可以让UI随着分辨率而自动缩放,保持和屏幕相对的大小比例不变,让UI整体看上去不会有变大变小的奇怪现象.但是,还 ...
- 【贪心】 BZOJ 3252:攻略
3252: 攻略 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 261 Solved: 90[Submit][Status][Discuss] De ...
- 可编辑的select框的实现(实用版)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEA ...
- Zookeeper + Hadoop + Hbase部署备忘
网上类似的文章很多,本文只是记录下来备忘.本文分四大步骤: 准备工作.安装zookeeper.安装hadoop.安装hbase,下面分别详细介绍: 一 准备工作 1. 下载 zookeeper.had ...
- struts2总结五: Strut2 访问Servlet API
一.间接访问 public String execute() throws Exception { this.message="hello,this is put into applicat ...
- POJ2240——Arbitrage(Floyd算法变形)
Arbitrage DescriptionArbitrage is the use of discrepancies in currency exchange rates to transform o ...
- WCF的行为与异常-------配置文件说明
ServiceBehavior and OperationBehavior(这些都是应用在实现类上) http://msdn.microsoft.com/zh-cn/library/system.se ...
- php Late Static Bindings延迟静态绑定
官网说道: As of PHP 5.3.0, PHP implements a feature called late static bindings which can be used to ref ...