Ringster's Techblog

Tech, Mobile, Internet

ImageView에 대용량 Bitmap 효과적으로 로딩하기

with 6 comments

하나의 안드로이드 앱에서 다룰 수 있는 메모리의 한계 때문에 이미지를 처리하다보면 OutofMemory exception이나 퍼포먼스 문제에 부딪치기 쉽다. 또한 이미지를 처리하는 도중 상대적으로 작은 뷰의 크기에 맞지 않는 고해상도의 이미지를 불러오는 것은 괜한 리소스를 낭비하게 만든다.
이 포스팅은 Google 개발자 튜토리얼(여기)을 참조하여 효과적으로 비트맵을 핸들링 하는 방법에 대해 정리해 보았다.

1. inJustDecodeBounds 설정하기

BitmapFactory class는 여러가지 리소스로부터 Bitmap 이미지를 만들어내기 위한 여러 decoding 메소드를 제공한다.
이중 options.inJustDecodeBounds를 true로 설정하면 이미지를 decoding할때 이미지의 크기만을 먼저 불러와 OutofMemory Exception을 일으킬만한 큰 이미지를 불러오더라도 선처리를 가능하도록 해준다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options); // inJustDecodeBounds 설정을 해주지 않으면 이부분에서 큰 이미지가 들어올 경우 outofmemory Exception이 발생한다.
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

2. Scaled Down된 버전의 이미지 메모리에 로딩하기

1번의 과정을 통해 이미지의 크기를 알게되었다면 이제 적절한 크기로 이미지를 리사이징할 차례이다.
inSampleSize 설정을 통해 이미지를 작은 사이즈로 리사이즈 할 수 있다.
inSampleSize는 픽셀수 기준으로 1/inSampleSize 크기로 이미지를 줄여준다. (가로,세로 기준)

리사이징을 위한 decoder가 inSampleSize를 2의 배수에 가까운 수로 버림하여 계산하기 때문에 2의 배수로 값을 계산하도록 하고, 선처리된 높이와 폭을 바탕으로 이미지를 표시할 뷰의 크기보다 작지 않은 크기로 가장 큰 inSampleSize값을 산출한다.

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;
}

3.이미지 전처리 메소드 생성하기

이제 1, 2번 과정을 통합하여 출력하고자 하는 이미지 뷰에 표시할 차례이다.
1번 과정과 2번 메소드를 통합한 decodeSampledBitmapFromResource 메소드를 생성한다.
해당 메소드는 어떠한 크기의 이미지가 소스로 들어오더라도 outofmemory exception 을 발생시키지 않는다.

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

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

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

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

4. ImageView에 출력하기

이제 3번에서 만든 메소드를 이용해 이미지 비트맵을 출력해주면 된다.

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

바로 setImageBitmap을 사용했을 때 outofmemory exception이 발생하는 이미지에 대해서도
decodeSampledBitmapFromResource 메소드를 통해서는 적절하게 리사이징해서 출력함을 확인할 수 있다.

Written by Ringster

2014/11/27 , 시간: 7:37 am

6개의 답글

Subscribe to comments with RSS.

  1. public Bitmap decodeSampledBitmapFromIs(InputStream is,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    Log.i(“decodeFromIs: “, “width ” + reqWidth + ” height ” + reqHeight + ” inSampleSize : ” + options.inSampleSize);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap b = BitmapFactory.decodeStream(is, null, options);
    return b;
    }

    리소스가 아니라 외부서버에서 인풋스트림으로 받아서 처리하는데 메모리를 잡아먹어서 해결방법 찾다가 오게되었습니다! 설명 되게 잘해주셔서 감사합니다 ㅋㅋ
    근데 위에 처럼 인풋스트림으로 하고있는데 Log.i를 찍어도 잘나오고 하는데 이미지가 안그려져서 질문드립니다.
    CalculateinSampleSize 메서드를 부르기 전에
    BitmapFactory.decodeStream(is, null, options);
    이 한줄때문에 안그려지는거 같습니다.. 이거 지우면 잘되더라구요. 물론 CalculateinSamepleSize 안부르구요..
    디코드스트림을 두번 부르면 안되는거같은데 왜그런지모르겟네요 ㅠㅠ

    익명

    2015/07/16 at 1:13 pm

  2. 감사합니다. 덕분에 메모리가 적게 드네요 ㅎㅎ
    그런데 액티비티를 전환할 때 메모리를 해제시키려면 어떻게 해야 되나요??
    액티비티 전환 시에도 메모리가 계속 누적이 되네요..

    HandyKim

    2015/12/23 at 3:29 am

    • 액티비티에서 사용하지 않는 이미지라면 다음과 같이 ImageView를 명시적으로 background를 null로 주고 GC를 호출하시면 될 것 같습니다.

      Ringster

      2015/12/23 at 3:02 pm

      • 답장을 이제서야 봤네요 ㅎㅎ.. 감사합니다
        제가 다이얼로그로 팝업창을 여러 개 띄우는 액티비티가 있는데 팝업창에도 이미지가 있습니다.
        그럼 onDestroy와 dismiss 할 때 팝업창에 사용된 모든 이미지들도 background를 null로 주는게 맞을까요??

        p.s : 이미지가 background로 설정된 것만 가능한거죠?? src로 되어있는건 setImageDrawable(null);가 맞나요?

        Handykim

        2016/01/02 at 1:33 am


댓글 남기기

블로그 이사갔어요! https://milooy.github.io/

Fork my brain because I'm ready to commit

jamesjungkuelee's biotech story

Biotech, entrepreneur, life

Communications as Ikor

기업 위기관리의 모든 것

Charles Pyo

시도와 실패, 성장의 기록. 2막에서도 계속되는 모험들.

techNeedle 테크니들

글로벌 테크 소식을 인사이트와 함께 전합니다

Open API, Cloud, DevOps 와 eBook

Open API, eBook, Cloud, DevOps

Economics of almost everything

Tech, Mobile, Internet

cylee

Tech, Mobile, Internet

Google for Developers Korea Blog

Tech, Mobile, Internet

Android Developers Blog

Tech, Mobile, Internet

최피디의 앱스 개발기

기술, 앱스, SNS, 창업

D2 Blog

Tech, Mobile, Internet

All of Software

Tech, Mobile, Internet

김동호의 스타트업 이야기

한국신용데이터, 오픈서베이, 그리고 기업가정신