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,如需转载请自行联系原作者


相关文章
|
8天前
|
Java 数据库 Android开发
【专栏】构建高效 Android 应用:探究 Kotlin 多线程优化策略
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
12天前
|
缓存 监控 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第23天】 在竞争激烈的移动市场中,一个高效的Android应用是吸引并保留用户的关键。本文将探讨如何通过一系列技术手段和最佳实践来优化Android应用的用户体验和性能表现。我们将深入分析响应式UI设计、内存管理、多线程处理以及最新的Android框架特性,揭示它们如何共同作用以减少应用延迟,提高响应速度,并最终提升整体用户满意度。
|
2天前
|
存储 安全 Android开发
安卓应用开发:构建一个高效的用户登录系统
【5月更文挑战第3天】在移动应用开发中,用户登录系统的设计与实现是至关重要的一环。对于安卓平台而言,一个高效、安全且用户体验友好的登录系统能够显著提升应用的用户留存率和市场竞争力。本文将探讨在安卓平台上实现用户登录系统的最佳实践,包括对最新身份验证技术的应用、安全性考量以及性能优化策略。
|
5天前
|
缓存 监控 API
Android应用性能优化实践
【4月更文挑战第30天】 随着智能手机的普及,用户对移动应用的性能要求越来越高。对于Android开发者而言,提升应用的性能是吸引和保留用户的关键因素之一。本文将深入探讨影响Android应用性能的主要因素,并提供一系列的优化策略,旨在帮助开发者构建更加流畅和高效的应用体验。
|
5天前
|
移动开发 调度 Android开发
构建高效Android应用:Kotlin协程的实践与优化
【4月更文挑战第30天】在移动开发领域,性能和响应性是衡量应用质量的关键指标。对于Android平台而言,Kotlin协程作为一种新兴的异步编程解决方案,提供了更为简洁和高效的处理并发任务的能力。本文将深入探讨Kotlin协程的核心原理,以及如何通过它们来提升Android应用的性能。我们将从基础概念出发,逐步介绍协程的创建、管理以及与Android UI线程的交互,并最终展示如何优化现有代码以利用协程的优势。
|
6天前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin协程的优化实践
【4月更文挑战第29天】在移动开发领域,尤其是Android平台上,性能优化一直是开发者关注的重点。近年来,Kotlin语言凭借其简洁性和功能性成为Android开发的热门选择。其中,Kotlin协程作为一种轻量级的并发处理机制,为编写异步代码、网络请求和数据库操作提供了极大的便利。本文将深入探讨Kotlin协程在Android应用中的性能优化技巧,帮助开发者构建更加高效的应用程序。
|
7天前
|
移动开发 API Android开发
Android应用性能优化实战
【4月更文挑战第28天】在移动开发领域,一个流畅的用户体验是至关重要的。对于Android开发者而言,应用的性能优化是一项既挑战性也极其重要的工作。本文将深入探讨Android应用性能优化的多个方面,包括内存管理、UI渲染、多线程处理以及电池效率等,旨在为开发者提供实用的性能提升策略和具体的实施步骤。通过分析常见的性能瓶颈,并结合最新的Android系统特性和工具,我们的目标是帮助读者打造更加高效、响应迅速的Android应用。
|
8天前
|
缓存 监控 Android开发
Android 应用性能优化实战
【4月更文挑战第27天】 在竞争激烈的移动应用市场中,性能优越的应用更能吸引和保留用户。针对Android平台,本文将深入探讨影响应用性能的关键因素,并提供一系列实用的优化策略。我们将从内存管理、UI渲染、多线程处理以及电池使用效率等方面入手,通过具体案例分析如何诊断常见问题,并给出相应的解决方案。文中所提技巧旨在帮助开发者构建更加流畅、高效的Android应用。
20 2
|
11天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。
|
11天前
|
监控 物联网 数据处理
构建未来:基于Android的智能物联网家居系统
【4月更文挑战第24天】 随着物联网技术的飞速发展,智能家居系统作为其重要分支之一,正逐渐渗透到千家万户。本文将详细探讨如何利用Android平台的灵活性和开放性,设计并实现一个高效、可靠的智能物联网家居系统。我们将从系统的架构设计出发,深入分析关键技术点,包括设备通信协议、用户界面设计以及后台数据处理等方面,并展示通过Android设备进行实时控制和监控的实用性。文章不仅聚焦于技术细节,还将对未来发展趋势及潜在挑战进行前瞻性讨论。
11 0