[안드로이드/android] 비트맵 이미지 최적화 로딩하기 - Loading Large Bitmaps Efficiently

Posted by 앱해피
2015. 8. 24. 18:20 안드로이드 이론
비트맵 이미지 최적화 로딩하기

 

이미지는 다양한 크기와 모양으로 구성된다. 많은 경우, 그 이미지들은 사용자 인터페이스(UI)로 사용하기에는 크기가 큰 이미지들로 구성된다. 예를 들면, 갤러리 어플에서 볼 수 있는 사진 디바이스의 해상도에 맞게 표현되지만 실제 해당 이미지는 높은 수준의 해상도를 갖는다.

 

대부분의 스마트폰은 제한된 메모리 용량을 갖기 때문에, 이미지를 메모리에 로딩할 때 더 낮은 해상도의 이미지로 로딩해야 하는 경우가 많다. 낮은 해상도의 이미지는 그것을 출력할 UI 요소의 사이즈와 일치해야 한다. 고해상도의 이미지를 그대로 가져다 쓰는 것은 많은 단점을 유발한다. 메모리를 상당히 많이 차지하며 성능상의 오버헤드를 유발한다.

 

이번 수업에서는 메모리에 작은 크기의 서브 이미지를 로딩 시킴으로써 어플의 메모리 제한을 초과하지 않도록 하는 방법을 알아본다.

 

 

비트맵 면적(가로, 세로)와 유형 읽기

 

 

BitmapFactory 클래스는 다양한 소스로부터 비트맵을 생성하기 위해 다양한 디코딩 메소드를 제공한다. (decodeByteArray(), decodeFile(), decodeResource(), etc.) 이 방법들은 구축된 비트 맵에 메모리를 할당하려 하기 때문에 쉽게 OutOfMemory 예외를 발생시킬 수 있다. 디코드 메소드의 각각의 유형은 추가적인 기능을 갖고 있다. 그 중 하나가 BitmapFactory.Options class를 이용해서 디코딩 옵션을 명시할 수 있다는 것이다. 디코딩을 하면서 inJustDecodeBounds 속성을 true로 설정하는 것은 메모리 할당을 피하면서 bitmap 객체에 null을 반환하지만 outWidth, outHeight and outMimeType를 설정할 수 있다. 이 기술은 비트 맵의 구축 (및 메모리 할당)에 앞서, 이미지 데이터의 크기와 종류를 읽을 수 있다.

 

** 예제 소스 시작 **

 

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

 

** 예제 소스 끝 **

 

예외 java.lang.OutOfMemory을 피하기 당신은 절대적으로 편안하게 사용 가능한 메모리에 맞는 예상대로 크기의 이미지 데이터를 제공하기 위해 소스를 신뢰하지 않는 한 그것을 해독하기 전에 비트 맵의 크기를 확인한다.

 

java.lang.OutOfMemory exceptions 예외를 피하기 위해서는, 이미지를 디코딩 하기 전에 비트맵의 면적을 확인해야 한다. (물론 이용 가능한 메모리에 정확히 일치하는 이미지 데이터가 제공된다면 이미지의 면적을 따로 확인할 필요는 없다.)

 

 

메모리에 사이즈 줄인 이미지 로딩하기

 

이제 이미지의 면적을 알기 때문에, 이 정보를 이용해서 전체 이미지를 로딩할 것인지 서브 샘플 이미지를 로딩할 것인지 결정할 수 있다. 고려해야 할 것들은 아래와 같다.

• 메모리에 전체 이미지를 로딩할 때 예상되는 메모리 사용량

 

• 이미지를 제외한 어플의 다른 메모리 요구사항을 충족하면서 이미지를 로딩하기 위해 사용하고자 하는 메모리의 양

 

• 이미지가 로딩될 이미지뷰 혹은 UI 요소의 면적

 

• 현재 디바이스의 화면 크기 또는 해상도 정보

 

예를들어, 128x96 픽셀의 썸네일 이미지를 보여줄 이미지뷰에 1024x768 픽셀의 이미지를 로딩할 필요는 없다.

 

디코더로 하여금 이미지의 서브 샘플을 생성하기 위해서는, inSampleSize를 true로 설정하여 더 작은 크기의 이미지를 메모리에 로딩해야 한다. 예를 들어 2048x1536 해상도의 이미지를 4라는 값으로 inSampleSize로 설정하여 디코딩을 하면 대략 512x384 크기의 비트맵이 생성된다.

 

ARGB_888을 기준으로 전체 이미지 12MB를 사용하는 대신 단지 0.75MB만을 사용하면 된다. 타겟의 너비와 높이를 기준으로 해서 2의 지수 형태로 샘플의 사이즈를 계산하는 방법이 있다.

 

** 예제 소스 시작 **

 

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

 

** 예제 소스 끝 **

 

Note : 디코더는 2의 지수값과 일치하도록 숫자를 만들기 때문에, 가능하면 2의 지수값으로 계산되야 한다.

이 메소드를 사용하기 위해선, 먼저 inJustDecodeBounds를 true로 설정한다. 그 이후 새로운 inSampleSize 값과 inJustDecodeBounds값을 false로 설정한 뒤 디코딩을 다시 한다.

 

** 예제 소스 시작 **

 

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {

    // 면적을 확인하기 위해 inJustDecodeBounds=true로 설정한 뒤 디코딩
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // inSampleSize 계산
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // inSampleSize를 설정한 뒤 bitmap 계산
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

 

** 예제 소스 끝 **