前言 html
以前有两篇博客讲解了如何从系统内已有的Camera和Gallery应用中获取图片的例子,看到评论里有朋友说有时候会报错,致使程序崩溃的 问题。本篇博客主要就这个问题分析讲解一下,最后将以一个简单的Demo演示。关于从系统内已有的Camera和Gallery应用中获取图片还不了解的 朋友,能够先看看另外两篇博客:Android--调用系统照相机拍照与摄像、Android--从系统Gallery获取图片。 java
分析出错缘由 android
以前讲到的从系统现有的Camera和Gallery应用中获取图片的Demo中,均直接使用系统应用返回的Uri,经过 ImageView.setImageURI(Uri)方法显示在界面上。而对于Android设备来讲,向内存中加载一张图片,消耗的内存并不受图片的 大小而影响,影响它的是图片的分辨率,图片的分辨率越大加载到内存所占用的内存将越多。使用ImageView.setImageURI(Uri)方法将 致使了一个严重的错误,虽然ImageView直接引用图片的Uri,它会对图片进行一部分优化,使得它能够正常显示,可是这种办法不利于Bitmap资 源的回收。因此在重复操做以后,经历过屡次的GC,也没有办法回收出足够加载图片的内存,致使应用崩溃。 canvas
解决方案 app
既然已经知道致使程序崩溃的缘由是内存溢出致使的,那么只须要维护好Uri所表明的图片内存便可。具体优化流程以下: ide
一、系统中现有的Camera和Gallery应用获取图片返回的都是一个Uri类型的数据,它是一个内容提供者的路径,能够使用ContentResolver获取它,这个之前有讲过,不了解的朋友能够看看另一篇博客:Android--ContentProvider。而在Context中,能够使用getContentResolver()方法获取到当前的内容解析者,并经过它的openInputStream()方法获取到图片的输入流,经过输入流能够获取到一个Bitmap对象。 优化
二、上面提到,Android中加载图片到内存中所占内存的大小取决于图片的分辨率,全部获得Bitmap还不能直接使用它,必须对其进行优 化,以最大适应当前设备的屏幕分辩率又不会致使加载过多像素而致使内存不足的状况。关于加载大分辨率到内存还不了解的朋友能够参见另一篇博客:Android--加载大分辨率图片到内存。 this
三、获得了优化事后的图片还须要在使用事后进行回收,Bitmap提供了两个方法用于判断是否已经回收它以及强制Bitmap回收本身。如下是它们的完整签名: .net
优化后的Demo code
上面讲到的两个demo,从Gallery中获取图片比较简单,代码量小,那么就在这个基础之上进行代码的优化。从Gallery中获取图片的Uri并不直接使用,而是把它转化为一个Bitmap,而且优化它以达到适应屏幕分辨率的效果。
package cn.bgxt.sysgallerydemo;
import java.io.InputStream;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.BitmapFactory.Options;
import android.graphics.Matrix;
import android.graphics.Paint;
public class MainActivity extends Activity {
private Button btn_getImage;
private ImageView iv_image;
private final static String TAG = "main";
private WindowManager wm;
private Bitmap bitmap;
private Bitmap blankBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获得应用窗口管理器
wm = getWindowManager();
btn_getImage = (Button) findViewById(R.id.btn_getImage);
iv_image = (ImageView) findViewById(R.id.iv_image);
btn_getImage.setOnClickListener(getImage);
}
private View.OnClickListener getImage = new OnClickListener() {
@Override
public void onClick(View v) {
// 设定action和miniType
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType("image/*");
// 以须要返回值的模式开启一个Activity
startActivityForResult(intent, 0);
}
};
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 若是获取成功,resultCode为-1
Log.i(TAG, "resultCode:" + resultCode);
if (requestCode == 0 && resultCode == -1) {
// 获取原图的Uri,它是一个内容提供者的地址
Uri uri = data.getData();
Log.i(TAG, "uri:" + data.getData().toString());
try {
// 从ContentResolver中获取到Uri的输入流
InputStream is = getContentResolver().openInputStream(uri);
// 获得屏幕的宽和高
int windowWidth = wm.getDefaultDisplay().getWidth();
int windowHeight = wm.getDefaultDisplay().getHeight();
// 实例化一个Options对象
BitmapFactory.Options opts = new BitmapFactory.Options();
// 指定它只读取图片的信息而不加载整个图片
opts.inJustDecodeBounds = true;
// 经过这个Options对象,从输入流中读取图片的信息
BitmapFactory.decodeStream(is, null, opts);
// 获得Uri地址的图片的宽和高
int bitmapWidth = opts.outWidth;
int bitmapHeight = opts.outHeight;
// 分析图片的宽高比,用于进行优化
if (bitmapHeight > windowHeight || bitmapWidth > windowWidth) {
int scaleX = bitmapWidth / windowWidth;
int scaleY = bitmapHeight / windowHeight;
if (scaleX > scaleY) {
opts.inSampleSize = scaleX;
} else {
opts.inSampleSize = scaleY;
}
} else {
opts.inSampleSize = 1;
}
// 设定读取完整的图片信息
opts.inJustDecodeBounds = false;
is = getContentResolver().openInputStream(uri);
// 若是没有被系统回收,就强制回收它
if (blankBitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = BitmapFactory.decodeStream(is, null, opts);
// 若是没有被系统回收,就强制回收它
if (blankBitmap != null && !blankBitmap.isRecycled()) {
blankBitmap.recycle();
}
// 在内存中建立一个能够操做的Bitmap对象
blankBitmap = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
// 为图片添加一个画板
Canvas canvas = new Canvas(blankBitmap);
// 把读取的图片画到新建立的Bitmap对象中
canvas.drawBitmap(bitmap, new Matrix(), new Paint());
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(30);
// 经过建立的画笔,在Bitmap上写入水印
canvas.drawText("我是水印", 10, 50, paint);
iv_image.setImageBitmap(blankBitmap);
} catch (Exception e) {
Toast.makeText(MainActivity.this, "获取图片失败", 0).show();
}
}
super.onActivityResult(requestCode, resultCode, data);
}
}
效果展现:
总结
其实对于这两个简单的Demo而言,只须要针对分辨率进行优化便可,通常而言由于功能简单,系统配置只要还过的去,都是能够被正常GC的,可是 对于一些常常操做图片的应用来讲,仍是显式的经过代码的方式来管理Bitmap的内存。最后加入Canvas进行渲染水印,不是必须的,只是加了个功能而 已,直接使用bitmap对象也能够。