异步获取网络图片和BitmapFactory.Options缩放图片

As we all know,when Android applications load a picture with high resolution,it’s prone to occur OOM.This is caused due to memory overflow…

在Android开发的过程中,我们在新建AVD(Android Virtual Devices)的时候知道对应的设备不同分配的固定堆内存大小是不一样的,一般低分辨率的手机堆内存大小是16M,高一点分辨率的就是32M,然后更高分辨率的有64M的,Android TV(720P)的就是默认堆内存大小64,但是Android TV(1080P)的默认堆内存大小就是128M。之所以会出现OOM,是因为Android加载图片是把位图放到内存中,位图都是由一个个小的像素点组成,位图的大小与像素点所占的字节有关系,而每个像素点在内存中所占的字节大小是和位图的一个Bitmap.Config属性有关系的,Bitmap.Config枚举类有四个常见的枚举值。我们通过查看Android源码Bitmap类,可以看到其中有如下一个内部枚举类:

1
2
3
4
5
6
7
8
9
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
public enum Config {
// these native values must match up with the enum in SkBitmap.h
/**
* Each pixel is stored as a single translucency (alpha) channel.
* This is very useful to efficiently store masks for instance.
* No color information is stored.
* With this configuration, each pixel requires 1 byte of memory.
*/
ALPHA_8 (2),
/**
* Each pixel is stored on 2 bytes and only the RGB channels are
* encoded: red is stored with 5 bits of precision (32 possible
* values), green is stored with 6 bits of precision (64 possible
* values) and blue is stored with 5 bits of precision.
*
* This configuration can produce slight visual artifacts depending
* on the configuration of the source. For instance, without
* dithering, the result might show a greenish tint. To get better
* results dithering should be applied.
*
* This configuration may be useful when using opaque bitmaps
* that do not require high color fidelity.
*/
RGB_565 (4),
/**
* Each pixel is stored on 2 bytes. The three RGB color channels
* and the alpha channel (translucency) are stored with a 4 bits
* precision (16 possible values.)
*
* This configuration is mostly useful if the application needs
* to store translucency information but also needs to save
* memory.
*
* It is recommended to use {@link #ARGB_8888} instead of this
* configuration.
*
* Note: as of {@link android.os.Build.VERSION_CODES#KITKAT},
* any bitmap created with this configuration will be created
* using {@link #ARGB_8888} instead.
*
* @deprecated Because of the poor quality of this configuration,
* it is advised to use {@link #ARGB_8888} instead.
*/
@Deprecated
ARGB_4444 (5),
/**
* Each pixel is stored on 4 bytes. Each channel (RGB and alpha
* for translucency) is stored with 8 bits of precision (256
* possible values.)
*
* This configuration is very flexible and offers the best
* quality. It should be used whenever possible.
*/
ARGB_8888 (6);

final int nativeInt;

@SuppressWarnings({"deprecation"})
private static Config sConfigs[] = {
null, null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
};

Config(int ni) {
this.nativeInt = ni;
}

static Config nativeToConfig(int ni) {
return sConfigs[ni];
}
}

上面写的很清楚了,再写明白点就是这样的

1
2
3
4
5
A:透明度,R:红色,G:绿,B:蓝
Bitmap.Config ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位=2byte
Bitmap.Config ARGB_8888:每个像素占八位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位=4byte
Bitmap.Config RGB_565:即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位=2byte
Bitmap.Config ALPHA_8:只有透明度,没有储存颜色,一个像素点占8位=1byte

这样写就很明吧了,通常都建议使用ARGB_8888这种属性值来存储位图,那么比如一张10801920的图片,所占用的存储空间的值就是`10801920*4 Byte`大约就是8M的内存,再大一些的图片,或者是反复加载就很容易就出现OOM了。

为了防止OOM的产生,我们有下面几个选择:

  1. 采用低质量Bitmap.Config像素占用小的属性加载,比如ARGB_4444,不过不推荐,这样会出现马赛克的现象。
  2. 缩小图片的尺寸,降低分辨率。
  3. 采用缓存加载大量图片。

今天我们只讨论第二种方法,降低图片大小需要使用一个类BitmapFactory.Options,会用到里面这些属性inJustDecodeBoundsinSampleSize,outWidth,outHeight

Android源码中对于属性的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* If set to true, the decoder will return null (no bitmap), but
* the out... fields will still be set, allowing the caller to query
* the bitmap without having to allocate the memory for its pixels.
*/
public boolean inJustDecodeBounds;

/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1\. Note: the
* decoder uses a final value based on powers of 2, any other value will
* be rounded down to the nearest power of 2.
*/
public int inSampleSize;

上面写的也就是说inSampleSize是缩放比例,inJustDecodeBounds设置为true时,可以允许调用者在不需要分配内存给位图的情况下获取位图的宽高

知道上面的原理之后我们就可以理一下步骤了,基本步骤是:

  1. 实例化一个BitmapFactory.Options对象options

  2. 设置options的inJustDecodeBounds值为true,以便下一步可以不分配内存来获取图片的分辨率。

  3. 根据BitmapFactory的decodeXXX方法,以第一步实例化的对象option作为参数,以不分配内存的方法获取到的实际的outWidth和outHeight,结合自己想设置的大小,计算出缩放比例inSampleSize,将值赋给options.inSampleSize

  4. inJustDecodeBounds的值设置为false。

  5. 再次使用BitmapFactory的decodeXXX方法,来获取到缩放后的图片,切记在执行改步骤之前一定要先将inJustDecodeBounds的值设置为false。

下面是我从网络上获取一张图片,然后缩放到想要的尺寸显示出来的Demo,附上源码

1
2
3
4
5
6
7
8
9
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package cn.picksomething.asyncdownloadimage;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.InputStream;
import java.lang.ref.WeakReference;

public class MyActivity extends Activity {

private Button button;
private ImageView imageView;
private String imageUrl = "http://www.konka.com/cms_file/uploads/Image/1/140627120650604/VC5A7259.JPG";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
button = (Button) findViewById(R.id.download);
imageView = (ImageView) findViewById(R.id.imageview);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
executeAsyncTask();
}
});
}

private void executeAsyncTask() {
DownloadImage downloadImage = new DownloadImage(imageView);
imageView.setImageResource(R.drawable.welcome);
downloadImage.execute(imageUrl);
}

/**
* 从网络加载图片
*/

public Bitmap getBitmapStream(String uri) {
Bitmap bitmap = null;
HttpGet httpGet = new HttpGet(uri);
try {
HttpClient httpClient = new DefaultHttpClient();
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity entity = httpResponse.getEntity();
InputStream inputStream = entity.getContent();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
int inSampleSize = calculateInSampleSize(options, 200, 100);
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
httpResponse = httpClient.execute(httpGet);
entity = httpResponse.getEntity();
inputStream = entity.getContent();
bitmap = BitmapFactory.decodeStream(inputStream, null, options);

} catch (Exception e) {
e.printStackTrace();
}
return bitmap;

}

public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
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;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}

class DownloadImage extends AsyncTask<String, Void, Bitmap> {

WeakReference<ImageView> imageViewWeakReference;

public DownloadImage(ImageView imageView1) {
imageViewWeakReference = new WeakReference&lt;ImageView&gt;(imageView1);
}

@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if(bitmap != null &amp;&amp; imageViewWeakReference != null){
ImageView imageView1 = imageViewWeakReference.get();
if(imageView1 != null){
imageView1.setImageBitmap(bitmap);
}
}
}

@Override
protected Bitmap doInBackground(String... params) {
return getBitmapStream(params[0]);
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.my, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}