前言

  之前有两篇博客讲解了如何从系统内已有的Camera和Gallery应用中获取图片的例子,看到评论里有朋友说有时候会报错,导致程序崩溃的问题。本篇博客主要就这个问题分析讲解一下,最后将以一个简单的Demo演示。关于从系统内已有的Camera和Gallery应用中获取图片还不了解的朋友,可以先看看另外两篇博客:Android--调用系统照相机拍照与摄像Android--从系统Gallery获取图片

分析出错原因

  之前讲到的从系统现有的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,并且优化它以达到适应屏幕分辨率的效果。

 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对象也可以。

Android--从系统Camera和Gallery获取图片优化的更多相关文章

  1. Android 从Gallery获取图片

    本文主要介绍Android中从Gallery获取图片 设计项目布局 <LinearLayout xmlns:android="http://schemas.android.com/ap ...

  2. Android--从系统Gallery获取图片

    前言 在Android应用中,经常有场景会需要使用到设备上存储的图片,而直接从路径中获取无疑是非常不便利的.所以一般推荐调用系统的Gallery应用,选择图片,然后使用它.本篇博客将讲解如何在Andr ...

  3. Android -- 加载大图片到内存,从gallery获取图片,获取图片exif信息

    1. 加载大图片到内存,从gallery获取图片 android默认的最大堆栈只有16M, 图片像素太高会导致内存不足的异常, 需要将图片等比例缩小到适合手机屏幕分辨率, 再加载. 从gallery ...

  4. Android 从相机或相册或获取图片(转)

    参考: https://github.com/ASDbobo/GetPhotoDemo Android 8.0 调取系统摄像头和相册选择图片 9.3 使用Camera拍照

  5. Android总结之打开手机相册获取图片

    上一篇,总结了如何打开照相机获取图片,详情请看>>>> 这篇将总结如何打开手机存储(相册)来获取手机上的图片. 打开相册 在需要这个功能的类中,我们可以自定义一个方法openA ...

  6. 从系统的gallery获取图片

    1 ) 效果演示: 2代码演示 布局代码:

  7. android 中 系统日期时间的获取

    import    java.text.SimpleDateFormat; SimpleDateFormat    formatter    =   new    SimpleDateFormat   ...

  8. Android开发之多媒体编程之获取图片的副本

    使用BitmapFactory的decodeFile()方法获取的Bitmap对象是只读的,无法进行编辑操作 需要进行编辑的话,需要获取到该对象的一个副本 代码如下: import android.a ...

  9. Android打开系统的Document文档图片选择

    打开Document UI 过滤图片 private void startAcitivty() { Intent intent = new Intent(); intent.setAction(&qu ...

随机推荐

  1. Jmeter中主要管理器功用

    不管是在使用jmeter进行性能测试还是接口自动化测试时经常用到各种管理器进行对参数的处理,但是有时候分不清几种管理器的具体用法,所以做个笔记,列一下吧(所列内容基于版本为3.2) 主要内容包含以下: ...

  2. 解决Spring boot中读取属性配置文件出现中文乱码的问题

    问题描述: 在配置文件application.properties中写了 server.port=8081 server.servlet.context-path=/boy name=张三 age=2 ...

  3. C语言中数组使用负数值的标记

    ·引 对数组的认知 在c语言中,我们经常使用的一个结构便是数组,在最开始学习数组的时候,它被描述成这样(以一维二维数组为例):一维数组是若干个数连续排列在一起的集合,我们可以通过0-N的标记(N为数组 ...

  4. go web开发(gin&gorm) 之DB配置及DAO的基本使用

    转载请注明出处: https://www.cnblogs.com/funnyzpc/p/9501376.html ```   我先闲扯下,前天(也就是2018年11月16号)的某个时候,忽然有人在QQ ...

  5. Hashlib加密,内置函数,安装操作数据库

    hashlib模块的md5加密: md5同样的内容加密后是一样的 md5加密后是不可逆的.即能加密,没办法解密. 撞库: 只是针对简单的,因为同样的内容加密后是一样的,难一点就不行了. 登录密码:注册 ...

  6. vscode设置中文语言

    https://jingyan.baidu.com/article/7e44095377c9d12fc1e2ef5b.html

  7. 让数字变化炫酷起来,数字滚动Text组件[Unity]

    让数字滚动起来 上周我的策划又提了样需求,当玩家评分发生变动时,屏幕出现人物评分浮层UI,播放评分数字滚动动画.这类数字滚动需求非常常见,我就按一般思路,将startvalue与endvalue每隔一 ...

  8. Flutter 读写本地文件

    文档 注意 安装 path_provider 插件后重启f5, 而不是等待热更新 demo import 'dart:io'; import 'dart:async'; import 'package ...

  9. python 用正则处理日志实例

    前提:     了解正则基本语法   import re with open('top10_xiaozhuang_net.log','r') as f1: #读取日志文件 subject=f1.rea ...

  10. 04.封装ajax

    <script> //封装ajax // 函数名 ajax // 函数的参数 // url: 请求的地址 // type: 请求的方式 get /post // data: 要上传的数据 ...