Android--从系统Camera和Gallery获取图片优化

简介:

前言

  之前有两篇博客讲解了如何从系统内已有的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,并且优化它以达到适应屏幕分辨率的效果。

复制代码
  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 {
 25     private Button btn_getImage;
 26     private ImageView iv_image;
 27     private final static String TAG = "main";
 28     private WindowManager wm;
 29     private Bitmap bitmap;
 30     private Bitmap blankBitmap;
 31 
 32     @Override
 33     protected void onCreate(Bundle savedInstanceState) {
 34         super.onCreate(savedInstanceState);
 35         setContentView(R.layout.activity_main);
 36 
 37         // 得到应用窗口管理器
 38         wm = getWindowManager();
 39         btn_getImage = (Button) findViewById(R.id.btn_getImage);
 40         iv_image = (ImageView) findViewById(R.id.iv_image);
 41 
 42         btn_getImage.setOnClickListener(getImage);
 43 
 44     }
 45 
 46     private View.OnClickListener getImage = new OnClickListener() {
 47 
 48         @Override
 49         public void onClick(View v) {
 50             // 设定action和miniType
 51             Intent intent = new Intent();
 52             intent.setAction(Intent.ACTION_PICK);
 53             intent.setType("image/*");
 54             // 以需要返回值的模式开启一个Activity
 55             startActivityForResult(intent, 0);
 56         }
 57     };
 58 
 59     @Override
 60     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 61         // 如果获取成功,resultCode为-1
 62         Log.i(TAG, "resultCode:" + resultCode);
 63         if (requestCode == 0 && resultCode == -1) {
 64             // 获取原图的Uri,它是一个内容提供者的地址
 65             Uri uri = data.getData();
 66             Log.i(TAG, "uri:" + data.getData().toString());
 67             try {
 68                 // 从ContentResolver中获取到Uri的输入流
 69                 InputStream is = getContentResolver().openInputStream(uri);
 70 
 71                 // 得到屏幕的宽和高
 72                 int windowWidth = wm.getDefaultDisplay().getWidth();
 73                 int windowHeight = wm.getDefaultDisplay().getHeight();
 74 
 75                 // 实例化一个Options对象
 76                 BitmapFactory.Options opts = new BitmapFactory.Options();
 77                 // 指定它只读取图片的信息而不加载整个图片
 78                 opts.inJustDecodeBounds = true;
 79                 // 通过这个Options对象,从输入流中读取图片的信息
 80                 BitmapFactory.decodeStream(is, null, opts);
 81 
 82                 // 得到Uri地址的图片的宽和高
 83                 int bitmapWidth = opts.outWidth;
 84                 int bitmapHeight = opts.outHeight;
 85                 // 分析图片的宽高比,用于进行优化
 86                 if (bitmapHeight > windowHeight || bitmapWidth > windowWidth) {
 87                     int scaleX = bitmapWidth / windowWidth;
 88                     int scaleY = bitmapHeight / windowHeight;
 89                     if (scaleX > scaleY) {
 90                         opts.inSampleSize = scaleX;
 91                     } else {
 92                         opts.inSampleSize = scaleY;
 93                     }
 94                 } else {
 95                     opts.inSampleSize = 1;
 96                 }
 97 
 98                 // 设定读取完整的图片信息
 99                 opts.inJustDecodeBounds = false;
100                 is = getContentResolver().openInputStream(uri);
101 
102                 // 如果没有被系统回收,就强制回收它
103                 if (blankBitmap != null && !bitmap.isRecycled()) {
104                     bitmap.recycle();
105                 }
106                 bitmap = BitmapFactory.decodeStream(is, null, opts);
107 
108                 // 如果没有被系统回收,就强制回收它
109                 if (blankBitmap != null && !blankBitmap.isRecycled()) {
110                     blankBitmap.recycle();
111                 }
112                 // 在内存中创建一个可以操作的Bitmap对象
113                 blankBitmap = Bitmap.createBitmap(bitmap.getWidth(),
114                         bitmap.getHeight(), Bitmap.Config.ARGB_8888);
115                 // 为图片添加一个画板
116                 Canvas canvas = new Canvas(blankBitmap);
117                 // 把读取的图片画到新创建的Bitmap对象中
118                 canvas.drawBitmap(bitmap, new Matrix(), new Paint());
119                 Paint paint = new Paint();
120                 paint.setColor(Color.RED);
121                 paint.setTextSize(30);
122                 // 通过创建的画笔,在Bitmap上写入水印
123                 canvas.drawText("我是水印", 10, 50, paint);
124 
125                 iv_image.setImageBitmap(blankBitmap);
126             } catch (Exception e) {
127                 Toast.makeText(MainActivity.this, "获取图片失败", 0).show();
128             }
129         }
130         super.onActivityResult(requestCode, resultCode, data);
131     }
132 }
复制代码

  效果展示:

 

  源码下载

 

总结

  其实对于这两个简单的Demo而言,只需要针对分辨率进行优化即可,一般而言因为功能简单,系统配置只要还过的去,都是可以被正常GC的,但是对于一些经常操作图片的应用来说,还是显式的通过代码的方式来管理Bitmap的内存。最后加入Canvas进行渲染水印,不是必须的,只是加了个功能而已,直接使用bitmap对象也可以。



本文转自承香墨影博客园博客,原文链接:http://www.cnblogs.com/plokmju/p/android_BitmapRecycle.html,如需转载请自行联系原作者


相关文章
|
1月前
|
人工智能 搜索推荐 物联网
Android系统版本演进与未来展望####
本文深入探讨了Android操作系统从诞生至今的发展历程,详细阐述了其关键版本迭代带来的创新特性、用户体验提升及对全球移动生态系统的影响。通过对Android历史版本的回顾与分析,本文旨在揭示其成功背后的驱动力,并展望未来Android可能的发展趋势与面临的挑战,为读者呈现一个既全面又具深度的技术视角。 ####
|
1月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
25天前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
26天前
|
存储 安全 Android开发
探索Android系统的最新安全特性
在数字时代,智能手机已成为我们生活中不可或缺的一部分。随着技术的不断进步,手机操作系统的安全性也越来越受到重视。本文将深入探讨Android系统最新的安全特性,包括其设计理念、实施方式以及对用户的影响。通过分析这些安全措施如何保护用户免受恶意软件和网络攻击的威胁,我们希望为读者提供对Android安全性的全面了解。
|
1月前
|
安全 Android开发 iOS开发
深入探讨Android与iOS系统的差异及未来发展趋势
本文旨在深入分析Android和iOS两大移动操作系统的核心技术差异、用户体验以及各自的市场表现,进一步探讨它们在未来技术革新中可能的发展方向。通过对比两者的开放性、安全性、生态系统等方面,本文揭示了两大系统在移动设备市场中的竞争态势和潜在变革。
|
缓存 Android开发 数据格式
Android ListView性能优化,异步加载图片
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/48184383 ListView性能优...
1172 0
|
缓存 算法 Android开发
Android 性能优化——之图片的优化
Android 性能优化——之图片的优化  在Android性能优化中,我们会发现占内存最大的和对性能影响最大的往往是图片资源,其次是控件资源。相对来说,其他的资源的影响会小一点。这里我就先对图片资源的优化进行一下讲解,如果有什么说的不对的,希望大神指正一下。
1085 0
|
24天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
48 19
|
24天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
52 14