分析出错原因
之前讲到的从系统现有的Camera和Gallery应用中获取图片的Demo中,均直接使用系统应用返回的Uri,通过ImageView.setImageURI(Uri)方法显示在界面上。而对于Android设备来说,向内存中加载一张图片,消耗的内存并不受图片的大小而影响,影响它的是图片的分辨率,图片的分辨率越大加载到内存所占用的内存将越多。使用ImageView.setImageURI(Uri)方法将导致了一个严重的错误,虽然ImageView直接引用图片的Uri,它会对图片进行一部分优化,使得它可以正常显示,但是这种办法不利于Bitmap资源的回收。所以在重复操作之后,经历过多次的GC,也没有办法回收出足够加载图片的内存,导致应用崩溃。
解决方案
既然已经知道导致程序崩溃的原因是内存溢出导致的,那么只需要维护好Uri所代表的图片内存即可。具体优化流程如下:
1、系统中现有的Camera和Gallery应用获取图片返回的都是一个Uri类型的数据,它是一个内容提供者的路径,可以使用ContentResolver获取它,这个以前有讲过,不了解的朋友可以看看另外一篇博客:Android--ContentProvider。而在Context中,可以使用getContentResolver()方法获取到当前的内容解析者,并通过它的openInputStream()方法获取到图片的输入流,通过输入流可以获取到一个Bitmap对象。
2、上面提到,Android中加载图片到内存中所占内存的大小取决于图片的分辨率,所有得到Bitmap还不能直接使用它,必须对其进行优化,以最大适应当前设备的屏幕分辩率又不会导致加载过多像素而导致内存不足的情况。关于加载大分辨率到内存还不了解的朋友可以参见另外一篇博客:Android--加载大分辨率图片到内存。
3、得到了优化过后的图片还需要在使用过后进行回收,Bitmap提供了两个方法用于判断是否已经回收它以及强制Bitmap回收自己。以下是它们的完整签名:
boolean isRecycled():返回Bitmap对象是否已经被回收。
void recycle():强制一个Bitmap对象回收自己。
优化后的Demo
上面讲到的两个demo,从Gallery中获取图片比较简单,代码量小,那么就在这个基础之上进行代码的优化。从Gallery中获取图片的Uri并不直接使用,而是把它转化为一个Bitmap,并且优化它以达到适应屏幕分辨率的效果。
复制代码
1 package cn.bgxt.sysgallerydemo;
2
3 import java.io.InputStream;
4
5 import android.net.Uri;
6 import android.os.Bundle;
7 import android.util.Log;
8 import android.view.View;
9 import android.view.WindowManager;
10 import android.view.View.OnClickListener;
11 import android.widget.Button;
12 import android.widget.ImageView;
13 import android.widget.Toast;
14 import android.app.Activity;
15 import android.content.Intent;
16 import android.graphics.Bitmap;
17 import android.graphics.BitmapFactory;
18 import android.graphics.Canvas;
19 import android.graphics.Color;
20 import android.graphics.BitmapFactory.Options;
21 import android.graphics.Matrix;
22 import android.graphics.Paint;
23
24 public class MainActivity extends Activity {
25private Button btn_getImage;
26private ImageView iv_image;
27private final static String TAG = "main";
28private WindowManager wm;
29private Bitmap bitmap;
30private Bitmap blankBitmap;
31
32@Override
33protected void onCreate(Bundle savedInstanceState) {
34super.onCreate(savedInstanceState);
35setContentView(R.layout.activity_main);
36
37// 得到应用窗口管理器
38wm = getWindowManager();
39btn_getImage = (Button) findViewById(R.id.btn_getImage);
40iv_image = (ImageView) findViewById(R.id.iv_image);
41
42btn_getImage.setOnClickListener(getImage);
43
44}
45
46private View.OnClickListener getImage = new OnClickListener() {
47
48@Override
49public void onClick(View v) {
50// 设定action和miniType
51Intent intent = new Intent();
52intent.setAction(Intent.ACTION_PICK);
53intent.setType("image/*");
54// 以需要返回值的模式开启一个Activity
55startActivityForResult(intent, 0);
56}
57};
58
59@Override
60protected void onActivityResult(int requestCode, int resultCode, Intent data) {
61// 如果获取成功,resultCode为-1
62Log.i(TAG, "resultCode:" + resultCode);
63if (requestCode == 0 && resultCode == -1) {
64// 获取原图的Uri,它是一个内容提供者的地址
65Uri uri = data.getData();
66Log.i(TAG, "uri:" + data.getData().toString());
67try {
68// 从ContentResolver中获取到Uri的输入流
69InputStream is = getContentResolver().openInputStream(uri);
70
71// 得到屏幕的宽和高
72int windowWidth = wm.getDefaultDisplay().getWidth();
73int windowHeight = wm.getDefaultDisplay().getHeight();
74
75// 实例化一个Options对象
76BitmapFactory.Options opts = new BitmapFactory.Options();
77// 指定它只读取图片的信息而不加载整个图片
78opts.inJustDecodeBounds = true;
79// 通过这个Options对象,从输入流中读取图片的信息
80BitmapFactory.decodeStream(is, null, opts);
81
82// 得到Uri地址的图片的宽和高
83int bitmapWidth = opts.outWidth;
84int bitmapHeight = opts.outHeight;
85// 分析图片的宽高比,用于进行优化
86if (bitmapHeight > windowHeight || bitmapWidth > windowWidth) {
87int scaleX = bitmapWidth / windowWidth;
88int scaleY = bitmapHeight / windowHeight;
89if (scaleX > scaleY) {
90opts.inSampleSize = scaleX;
91} else {
92opts.inSampleSize = scaleY;
93}
94} else {
95opts.inSampleSize = 1;
96}
97
98// 设定读取完整的图片信息
99opts.inJustDecodeBounds = false;
100is = getContentResolver().openInputStream(uri);
101
102// 如果没有被系统回收,就强制回收它
103if (blankBitmap != null && !bitmap.isRecycled()) {
104bitmap.recycle();
105}
106bitmap = BitmapFactory.decodeStream(is, null, opts);
107
108// 如果没有被系统回收,就强制回收它
109if (blankBitmap != null && !blankBitmap.isRecycled()) {
110blankBitmap.recycle();
111}
112// 在内存中创建一个可以操作的Bitmap对象
113blankBitmap = Bitmap.createBitmap(bitmap.getWidth(),
114bitmap.getHeight(), Bitmap.Config.ARGB_8888);
115// 为图片添加一个画板
116Canvas canvas = new Canvas(blankBitmap);
117// 把读取的图片画到新创建的Bitmap对象中
118canvas.drawBitmap(bitmap, new Matrix(), new Paint());
119Paint paint = new Paint();
120paint.setColor(Color.RED);
121paint.setTextSize(30);
122// 通过创建的画笔,在Bitmap上写入水印
123canvas.drawText("我是水印", 10, 50, paint);
124
125iv_image.setImageBitmap(blankBitmap);
126} catch (Exception e) {
127Toast.makeText(MainActivity.this, "获取图片失败", 0).show();
128}
129}
130super.onActivityResult(requestCode, resultCode, data);
131}
132 }
复制代码
效果展示: