Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發中解析、創建Bitmap對象時OOM的有效解決方法並附上一些干貨

Android開發中解析、創建Bitmap對象時OOM的有效解決方法並附上一些干貨

編輯:關於Android編程

先來點雞湯:
Stay hungry,stay foolish
這句話的的解讀:我們必須了解自己的渺小。如果我們不學習,科技發展的速度會讓我們五年後被清空。所以,我們必須用初學者謙虛的自覺,饑餓者渴望的求知態度,來擁抱未來的知識。

這幾天做的項目中需要從圖庫選擇圖片或者拍照生成圖片,然後展現在IamgeView控件上。當然,從圖庫選擇圖片和拍照選擇圖片的功能實現起來很簡單。直接寫上代碼:
CharSequence[] items = { "拍照", "圖庫" };
new AlertDialog.Builder(context).setTitle("請選擇:")
            .setIcon(R.drawable.ic_choose_picture)
            .setItems(items, new OnClickListener() {
    public void onClick(DialogInterface dialog,int which) {
            switch (which) {
                case 0:
                // 調用拍照功能
                // 創建File對象,用於存儲拍照後的對象
                takePhotoImage = new File(Environment.getExternalStorageDirectory(),"take_photo_image.jpg");
                try {
                    if (takePhotoImage.exists()) {
                    takePhotoImage.delete();
                                                  }
                takePhotoImage.createNewFile();
                    } catch (Exception e) { 
                    e.printStackTrace();
                                    }
                    // 將File對象轉換成Uri對象
                    imageUri =Uri.fromFile(takePhotoImage);
                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    // 指定圖片的輸出地址
                intent.putExtra(MediaStore.EXTRA_OUTPUT,
                imageUri);
                startActivityForResult(intent, TAKE_PHOTO);
                break;
                case 1:
                // 調用系統圖庫
                // 創建File對象,用於存儲選擇圖庫後的圖片
                File choosePhoto = new File(Environment.getExternalStorageDirectory(),"choose_photo.jpg");
                try {
                    if (choosePhoto.exists()) {
                    choosePhoto.delete();
                        }
                    choosePhoto.createNewFile();
                    } catch (Exception e) {
                    e.printStackTrace();
                    }
                    // 將File對象轉換成Uri對象
                    imageUri = Uri.fromFile(choosePhoto);
                    Intent intent2 = new Intent("android.intent.action.GET_CONTENT");
                    intent2.setType("image/*");
                    intent2.putExtra("crop", "true");
                    intent2.putExtra("scale", true);
        intent2.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
            startActivityForResult(intent2, CROP_PHOTO);
                break;
                }
                    }
                        }).show();

以上代碼很簡單,就是創建一個對話框,有兩個item選項。一個是拍照、一個是圖庫。如下所示:

對話框顯示

兩個item選項的功能都是為了生成一張圖片並設置到ImageView控件上。

拍照和從圖庫選擇圖片都先創建了一個File對象,用於存儲攝像頭拍下的圖片或者存儲從圖庫中選擇的圖片。

然後將它放在手機的根目錄下,我的手機是在內存設備的根目錄下,這個無所謂。然後調用Uri的fromFile()方法將File對象轉換成Uri對象。這個Uri對象標識著圖片的唯一地址。

如果點擊的是拍照,接著會構建出一個Intent對象,並將這個Intent的action指定為:MediaStZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmUuQUNUSU9OX0lNQUdFX0NBUFRVUkWjrNTZtffTw0ludGVudLXEcHV0RXh0cmGjqKOpt723qNa4tqjNvMastcTK5LP2tdjWt6Os1eLA777NzO7I67jVuNW1w7W9tcRVcmm21M/zo6zX7rrztffTw3N0YXJ0QWN0aXZpdHlGb3JSZXN1bHSjqKOpwLTG9Lavu+62r6GjPC9wPg0K0vLOqsnPw+a1xLT6wuvKx8q508NzdGFydEFjdGl2aXR5Rm9yUmVzdWx0o6ijqcC0xvS2r7vutq+1xKOsy/nS1MXE1dWzybmmuvO74bvYtfdvbkFjdGl2aXR5UmVzdWx0o6ijqbe9t6ijrNKyvLTFxM3q1dW687vh09C94bn7t7W72LW9b25BY3Rpdml0eVJlc3VsdKOoo6m3vbeo1tCho8v50tTW2NC0uMO3vbeovs3E3LbUxcTV1brztcTNvMas1/a9+NK7sr21xLSmwO3By6Gjt723qMjnz8LL+cq+o7oNCjxwcmUgY2xhc3M9"brush:java;"> /** * 在調用startActivityForResult的時候會回調該方法 */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { // 拍照 case TAKE_PHOTO: if (resultCode == Activity.RESULT_OK) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(imageUri, "image/*"); intent.putExtra("scale", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, CROP_PHOTO); } break; // 裁剪圖片 case CROP_PHOTO: if (resultCode == Activity.RESULT_OK) { try { // 壓縮Bitmap對象 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 8; options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable = true; options.inInputShareable = true; bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri),null, options); setImageData(bitmap); if (bitmapEntities.size() == 1) { adapter = new BitmapAdapter(context, bitmapEntities); gv_Picture.setAdapter(adapter); } else { adapter.notifyDataSetChanged(); } linear_Picture.addView(view_Picture, 1); } catch (Exception e) { e.printStackTrace(); } } break; } } 上面說到如果用攝像頭拍照成功後會回調onActivityResult()方法,這時候會繼續構建Intent對象,把它的action指定為:
“com.android.camera.action.CROP”
這個Intent是用於拍出的照片進行裁剪的。因為攝像頭比較大,而我們可能只希望截取其中的一小部分。然後給這個Intent設置上一些必要的屬性,並再調用startActivityForResult()來啟動裁剪程序。裁剪後的圖片同樣會輸出到手機根目錄下的圖片文件中。

===========================================

如果點擊的item選項是圖庫。那麼會調用系統的圖庫選擇圖片。在這裡,跟拍照時基本的操作沒有什麼太多的差別。
都是先創建一個File對象,保存從圖庫選擇的文件。然後構建出一個Intent對象,並將它的action指定為:“android.intent.action.GET_CONTENT”。接著給這個Intent對象設置一些必要的參數,包括是否允許縮放和裁剪、圖片的輸出位置等。最後調用startActivityForResult()方法,就可以打開相冊程序選擇照片了。

這裡用到了一個小技巧,就是我們在調用startActivityForResult()方法的時候,給第二個參數傳入的值仍然是:CROP_PHOTO常量,這樣就可以復用之前調用攝像頭顯示圖片的邏輯了,不用再編寫第二遍顯示圖片的邏輯。

好了,以上的兩個item選項的操作都講完了,接下來就是關鍵的時刻了。
裁剪操作完成後,程序又會回調到onActivityResult方法中,這個時候就可以利用BItmapFactory的decodeStream()方法將存儲在手機根目錄下的圖片文件解析成Bitmap對象,然後把它設置到ImageView控件上顯示出來。

我在這裡用的是gridView控件顯示一些圖片。不過都是一樣的,在gridView設置adapter的時候,需要在自定義的Adapter中把Bitmap對象設置到ImageView控件上。一開始設置一兩張圖片的時候如下所示:

兩張圖片時候

但是放的圖的一旦多一點,程序就直接崩潰了,看了一下logcat的錯誤日志。
一個醒目的Java.lang.OutOfMemoryError(內存溢出錯誤),最不想遇見的錯誤。說心裡話,蛋疼。困惑了有段時間,試了很多的方法,終於很好的做了一些有效的圖片壓縮方法優化內存溢出的問題,但是並不能徹底的解決內存溢出問題。所以有時候我們自己要對圖片的張數做一些限制。像釘釘的出差管理的圖片選擇最多允許選擇9張、微信發朋友圈發圖片最多允許9張。有時候需要一些限制才能解決一些無法避免的問題。
上面的發生內存溢出錯誤的代碼定位了一下,直接就定位到下面這一行,如下圖所示:

定位錯誤代碼行

該行代碼如下:
bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri));
為什麼這行代碼會導致OOM呢?給大家講講:該行代碼的作用是用於從指定輸入流中解析、創建Bitmap對象。但是由於手機系統的內存比較小,如果不停的去解析、創建Bitmap對象,可能由於前面所創建的Bitmap所占用的內存還沒有回收,而導致程序運行時引發OutOfMemory錯誤。所以我們需要將Bitmap對象先進行壓縮,使用的時候可以降低對內存的占用,這樣就可以有效的解決這個問題。我的解決方法是將以上的代碼替換為下圖所示的代碼:

關鍵代碼

上圖所示的各行代碼解釋如下:

======

BitmapFactory.Options options = new BitmapFactory.Options();

該行代碼用來創建一個BitmapFactory.Options對象,且Options 只保存圖片尺寸大小,不保存圖片到內存。

======

options.inSampleSize = 8;

該行代碼是最關鍵的代碼。給大家點進去看一下源碼,源碼如下所示:

源碼圖片

源碼的注釋給大家講講。
注釋說:如果該值設置為>1,就會請求解析器對原圖做二次抽樣,即二次解析,返回一個較小的圖片用來節省內存。二次抽樣樣本大小的像素尺寸,對應於一個解碼位圖的像素。舉個例子,如果inSampleSize == 4,會返回一個是原圖1/4高度和1/4寬度的圖像,和1/16像素的數量。對任何值< = 1的值都用=1來賦值。
以上源碼的注釋很好的說明了該行代碼的關鍵性。大家要有效避免OOM錯誤的話一定要記得加上哦!只有這樣,才能有效的節省Bitmap對象占用的內存。別忘記了!

======

options.inPreferredConfig = Bitmap.Config.RGB_565;

該行代碼是附加上圖片的Config參數,解析器或根據當前的參數配置進行對應的解析,這也可以有效減少加載的內存。

======

options.inPurgeable = true;

該行代碼作用:由此產生的位圖將分配它的像素,這樣他們可以被淨化系統需要回收的內存。

======

options.inInputShareable = true;

該行代碼的作用:inInputShareable屬性和inPurgeable 有關,當inPurgeable 屬性設置為false的時候,inInputShareable屬性就可以忽略。但是如果這兩個屬性都設置為true的時候,源碼的注釋這樣說:確定位圖可以共享一個參考輸入數據如果它必須深拷貝的話。好了這都不是什麼關鍵的,不設置也是可以的。

======
最後調用方法:

bitmap=BitmapFactory.decodeStream(getActivity().getContentResolver().openInputStream(imageUri),null, options);

該行代碼就是根據options的一些選項設置解析輸入流返回一個壓縮後的Bitmap對象。這時候使用bitmap的時候就可以有效的避免OOM了。下面附上我的項目的測試圖片給大家看,眼見為實嘛!如下圖:

這裡寫圖片描述

好了,沒有報OOM的錯誤,9張圖片都成功的呈現在界面上。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved