프로그래밍/안드로이드

[안드로이드] ImageView에 Bitmap 최적화 로딩하기 - OutOfMemoryError 처리

오치리일상 2017. 8. 23.

안드로이드 이미지뷰에 비트맵 이미지를 로딩하다 보면 java.lang.OutOfMemoryError 가  발생하는 경우가 있다.

 

이미지 용량이 너무 커서 발생하는 에러이다.

 

이미지 용량이 몇 메가 이상일 때(?) 라는 정해진 용량은 없는것 같다. 스마트폰에 따라 또는 안드로이드 OS에 따라 에러가 발생하는 이미지의 용량은 다를 수 있다.

 

 

그럼 이 현상을 어떻게 처리하면 될 것인가?

 

로드할 비트맵 이미지의 사이즈를 줄여서 이미지의 용량을 줄이는 방법을 사용한다.

 

하지만 이미지 사이즈를 너무 줄이면 해상도가 떨어져 알아볼 수 없을 정도가 되기 때문에 적당한 크기로 줄이는 것이 좋을 것이다.

 

아래에 OutOfMemoryError가 발생할 때 처리하는 소스 코드를 살펴본다.    

 

 

 

코딩에 앞서 res/drawable/ 폴더에 약 6MB의 sample_image.jpg 이미지를 저장한다.

 

 

 

* activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button01"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="원본 이미지 출력"
        android:textColor="#000000"
        android:textSize="18dp"/>
    <Button
        android:id="@+id/button02"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="적당히 사이즈 줄인 이미지 출력"
        android:textColor="#000000"
        android:textSize="18dp"/>

    <Button
        android:id="@+id/button03"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="너무 많이 축소 했을때 이미지 출력"
        android:textColor="#000000"
        android:textSize="18dp"/>
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

레이아웃에 버튼 3개를 준비한다. - (원본 사이즈의 이미지를 출력할 버튼, 이미지 사이즈를 적당히 줄여 출력할 버튼, 이미지 사이즈를 너무 많이 줄여 출력할 버튼)

 

그리고 이미지를 출력할 이미지뷰를 준비한다.

 

 

* MainActivity.java

package com.studio572.samplebitmapfactory;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private final int REQUEST_WIDTH = 512;
    private final int REQUEST_HEIGHT = 512;
    private Button button01, button02, button03;
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = (ImageView) findViewById(R.id.imageView);
        button01 = (Button) findViewById(R.id.button01);
        button02 = (Button) findViewById(R.id.button02);
        button03 = (Button) findViewById(R.id.button03);

        button01.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 리소스의 대용량의 이미지를 다른 작업없이 화면에 노출한다. - 이 때 OutOfMemoryError가 발생할 것이다.-
                imageView.setImageResource(R.drawable.sample_image);
            }
        });
        button02.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                // inJustDecodeBounds = true일때 BitmapFactory.decodeResource는 리턴하지 않는다. 
                // 즉 bitmap은 반환하지않고, options 변수에만 값이 대입된다.
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeResource(getResources(), R.drawable.sample_image, options);

                // 이미지 사이즈를 필요한 사이즈로 적당히 줄이기위해 계산한 값을
                // options.inSampleSize 에 2의 배수의 값으로 넣어준다.
                options.inSampleSize = setSimpleSize(options, REQUEST_WIDTH, REQUEST_HEIGHT);

                // options.inJustDecodeBounds 에 false 로 다시 설정해서 BitmapFactory.decodeResource의 Bitmap을 리턴받을 수 있게한다.
                options.inJustDecodeBounds = false;
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sample_image, options);

                // 이미지 size가 재설정된 이미지를 출력한다.
                imageView.setImageBitmap(bitmap);
            }
        });
        button03.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                BitmapFactory.Options options = new BitmapFactory.Options();

                // 이미지 size를 너무 많이 줄일 때 이미지 해상도가 떨어질 수 있으므로 해상도에 알맞게 resize 하는 것이 좋다.
                options.inSampleSize = 200;
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sample_image, options);

                imageView.setImageBitmap(bitmap);
            }
        });
    }

    // 이미지 Resize 함수
    private int setSimpleSize(BitmapFactory.Options options, int requestWidth, int requestHeight){
        // 이미지 사이즈를 체크할 원본 이미지 가로/세로 사이즈를 임시 변수에 대입.
        int originalWidth = options.outWidth;
        int originalHeight = options.outHeight;

        // 원본 이미지 비율인 1로 초기화
        int size = 1;

        // 해상도가 깨지지 않을만한 요구되는 사이즈까지 2의 배수의 값으로 원본 이미지를 나눈다.
        while(requestWidth < originalWidth || requestHeight < originalHeight){
            originalWidth = originalWidth / 2;
            originalHeight = originalHeight / 2;

            size = size * 2;
        }
        return size;
    }
}

 

위 소스 코드에 주석에 설명을 달아 놓았다. 

 

 

 

 

button01 : 클릭시 이미지 용량이 크다면 OutOfMemoryError가 발생할 것이다. 만약 발생하지 않는다면 용량이 좀 더 큰 이미지를 넣어 출력해본다.

 

button02 : 클릭시 화면에 출력전에 이미지 사이즈를 구하고 사이즈가 가로/세로(512 * 512)보다 클경우 512 * 512 사이즈보다 작을때까지 줄여서 출력한다. (여기서는 512 * 512로 작업했지만 여러분이 필요로 하는 이미지 크기로 작업하시면 될 것이다.)

 

button03 : 클릭시 임의로 이미지의 1/200로 이미지 사이즈를 줄였다. 이 때 이미지 해상도가 너무 떨어져 알아보기 어려울 정도로 깨졌다. 그렇기 때문에 이미지 사이즈를 적당히 줄이는 것이 중요하다. 

 

 

 

아래 이미지는 위 소스의 출력물

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

아래 동영상은 위 소스의 출력 동영상이다.

 

 

 

 

 

 

 

   

 

 

댓글

💲 추천 글