編輯:Android開發實例
前言
之前有兩篇文章講解了如何從系統內已有的Camera和Gallery應用中獲取圖片的例子,看到評論裡有朋友說有時候會報錯,導致程序崩潰的問題。本文主要就這個問題分析講解一下,最後將以一個簡單的Demo演示。關於從系統內已有的Camera和Gallery應用中獲取圖片還不了解的朋友,可以先看看另外兩篇:http://www.fengfly.com/plus/view-214101-1.html、http://www.fengfly.com/plus/view-214216-1.html。
分析出錯原因
之前講到的從系統現有的Camera和Gallery應用中獲取圖片的Demo中,均直接使用系統應用返回的Uri,通過ImageView.setImageURI(Uri)方法顯示在界面上。而對於Android設備來說,向內存中加載一張圖片,消耗的內存並不受圖片的大小而影響,影響它的是圖片的分辨率,圖片的分辨率越大加載到內存所占用的內存將越多。使用ImageView.setImageURI(Uri)方法將導致了一個嚴重的錯誤,雖然ImageView直接引用圖片的Uri,它會對圖片進行一部分優化,使得它可以正常顯示,但是這種辦法不利於Bitmap資源的回收。所以在重復操作之後,經歷過多次的GC,也沒有辦法回收出足夠加載圖片的內存,導致應用崩潰。
解決方案
既然已經知道導致程序崩潰的原因是內存溢出導致的,那麼只需要維護好Uri所代表的圖片內存即可。具體優化流程如下:
1、系統中現有的Camera和Gallery應用獲取圖片返回的都是一個Uri類型的數據,它是一個內容提供者的路徑,可以使用ContentResolver獲取它,這個以前有講過,不了解的朋友可以看看另外一篇:http://www.fengfly.com/plus/view-213512-1.html。而在Context中,可以使用getContentResolver()方法獲取到當前的內容解析者,並通過它的openInputStream()方法獲取到圖片的輸入流,通過輸入流可以獲取到一個Bitmap對象。
2、上面提到,Android中加載圖片到內存中所占內存的大小取決於圖片的分辨率,所有得到Bitmap還不能直接使用它,必須對其進行優化,以最大適應當前設備的屏幕分辯率又不會導致加載過多像素而導致內存不足的情況。關於加載大分辨率到內存還不了解的朋友可以參見另外一篇:http://www.fengfly.com/plus/view-214093-1.html。
3、得到了優化過後的圖片還需要在使用過後進行回收,Bitmap提供了兩個方法用於判斷是否已經回收它以及強制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中。 Android提供了訪問這些控件AudioManager類。
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
可以顯示在的Android任務,通過加載進度條的進展。進度條有兩種形狀。加載欄和加載微調(spinner)。在本章中,我們將討論微調(spinner)。Spinner 用
首先看程序界面如下! 1、布局文件:代碼如下:<AbsoluteLayout xmlns:android=http://schemas.android.c