Tistory View

여기서의 재사용은 recycle을 말하는 것이 아니고,이미 존재하는 Bitmap변수에 이미지를 가지고 오는 방법을 설명한다.

BitmapFactory.decode* 함수들을 이용할 때, 이미 메모리상에 존재하는 Bitmap에 이미지를 로드할 수 있다. 이렇게 하면 다음과 같은 장점이 존재하게 된다.

장점

  1. 비트맵메모리를 필요한 만큼 할당이 되어 있다면, 할당/해제 과정이 사라지게되고, 이로인해 GC에 의해 순간순간 멈추는 현상이 줄어들게 된다.
  2. 이미 존재하는 Bitmap을 이용하기 때문에 OOM이 현저히 줄어들게 된다.

단점

  1. 사용되지 않는 비트맵을 recycle하지 않고, 계속할당되어있기 때문에, 필요없는 메모리 사용량이 존재하기 된다.
  2. 재사용할 비트맵을 유지관리하는 코드가 추가 되어야 한다.

지금부터 설명하는 내용은 안드로이드 버전 KITKAT(4.4, SDK19)을 기준으로 한다.

비트맵의 크기

당연한 말이지만, Bitmap를 로드할 때, 불러오는 이미지의 크기가 현재 메모리에 존재하는 이미지의 크기보다 같거나 작아야 한다. 여기서 크기는 byte단위로 처리된다.

Bitmap 인스턴스에는 getByteCount() 함수와 getAllocationByteCount() 두개의 함수가 있는 데, getByteCount()는 현재이미지 크기에 따른 byte를 리턴하며, getAllocationByteCount()를 비트맵 내부에 할당된 메모리크기를 리턴한다.

말이 좀 헤깔리니, 좀 더 이해를 위해 다음의 코드를 보자

예제 : getByteCount vs getAllocationByteCount

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT )
{
  Bitmap bmp = Bitmap.createBitmap( 320, 240, Bitmap.Config.ARGB_8888 );
  Log.d( TAG, "byteCount=" + bmp.getByteCount() + " getAllocationByteCount=" + bmp.getAllocationByteCount() );

  bmp.reconfigure( 160, 120, Bitmap.Config.ARGB_8888 );
  Log.d( TAG, "byteCount=" + bmp.getByteCount() + " getAllocationByteCount=" + bmp.getAllocationByteCount() );

  bmp.reconfigure( 160, 120, Bitmap.Config.RGB_565 );
  Log.d( TAG, "byteCount=" + bmp.getByteCount() + " getAllocationByteCount=" + bmp.getAllocationByteCount() );

  bmp.reconfigure( 1, 1, Bitmap.Config.RGB_565 );
  Log.d( TAG, "byteCount=" + bmp.getByteCount() + " getAllocationByteCount=" + bmp.getAllocationByteCount() );
}

출력

byteCount=307200 getAllocationByteCount=307200    // 320, 240, Bitmap.Config.ARGB_8888
byteCount=76800 getAllocationByteCount=307200     // 160, 120, Bitmap.Config.ARGB_8888
byteCount=38400 getAllocationByteCount=307200     // 160, 120, Bitmap.Config.RGB_565
byteCount=2 getAllocationByteCount=307200         //   1,   1, Bitmap.Config.RGB_565

예제를 잘 보면, getAllocationByteCount는 처음 Bitmap이 만들어 질 때의 바이트크기를 늘 리턴하고, getByteCount는 현재 이미지를 기준으로 바이트크기를 리턴해주고 있다.

KITKAT부터 Bitmap의 내부의 구조(크기나 pixel config)를 바꿀 수 있는 능력(?)이 생기면서, 비트맵을 재사용하는 것이 수월해졌다.

다시 원래 이야기로 돌아가 우리가 로드하려는 이미지의 크기가 getAllocationByteCount보다 같거나 작은 경우만 Bitmap을 로드할 수가 있다.

 

이미지 크기 알아내기

그러면 불러올 이미지의 크기를 먼저 알아야 하기 때문에, 불러올 이미지의 크기를 가지고 오는 함수는 다음과 같다.

decodeFile부분을 필요에 따라 파라미터와 함께, decodeByteArray, decodeStream 등으로 바꿔서 사용하면 된다.

// static BitmapFactory.Options getBitmapSizeFile( String filename )
    // 이미지 파일의 크기를 구하는 함수
    // param.
    //     filename : "로드할 비트맵파일이름"

    // bitmap의 크기는 return 값의 outWidth, outHeight, opts.outConfig로 계산해야 한다.
    public static BitmapFactory.Options getBitmapSizeFile( String filename ) {
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile( filename, opts );

        if( opts.outWidth > 0 && opts.outHeight > 0 ) {
            return opts;
        }
        
        return null;
    }

 

이미지의 바이트크기 구하기

우리는 이미지의 바이트크기가 필요하기 때문에 다음의 함수로 byte크기를 구해보자

public static int getImageByteSize(int w, int h , Bitmap.Config config )
{
    int byteSize = w * h;
    switch( config ) {
        case ARGB_8888:
            byteSize *= 4;
            break;
        case RGB_565:
        case ARGB_4444:
            byteSize *= 2;
        case ALPHA_8:
            break;
        default:
            return -1;
    }
    return byteSize;
}

Bitmap.Config 에는 다른 것들도 있지만 잘 사용하지 않으니 빼두었다.

비트맵 재사용

BitmapFactory.Options에 inBitmap변수가 있다. 이 곳에 사용할 비트맵을 할당한 이후에 Decode를 하면 된다.


/**
    이미 존재하는 비트맵에 이미지 로드하는 함수
    Parameters
        filename : 불러올 파일이름
        bmp      : 재사용할 비트맵 : 이미 할당이 된 녀석

    Remark
        bmp값이 올바른지 체크하는 코드는 알아서 추가하셈
*/
public static Bitmap decodeFile( String filename, Bitmap bmp )
{

    if( bmp == null ) {
        throw new IllegalArgumentException( "할당된 걸 넘기라구" );
    }
    if( !bmp.isMutable() ) {
        throw new IllegalArgumentException( "이 비트맵은 재사용 못해" );
    }


    Bitmap loadedBitmap = null;
    BitmapFactory.Options opts = new BitmapFactory.Options();

    opts.inMutable = true; // 출력되는 녀석도 mutable로 한다.(이 코드 중요하다.)
    opts.inBitmap  = bmp;

    loadedBitmap = BitmapFactory.decodeFile( filename, opts );

    // 로드 실패 했나?
    if( loadedBitmap == null ) {
        return null;
    }

    // 이거 검사를 해야 되는 데, 이유가 기억이 안남.. 근데 생각하기 싫음
    if( loadedBitmap != bmp ) {
        return null;
    }

    return bmp; // or return loadedBitmap;
}

핵심은 inBitmap에 사용할 bmp를 넣는 것이다. 하지만 몇가지 알아야 할 것이 있는 데,

bmp는 mutable(수정할 수있는)녀석이어야 한다. Bitmap내부에 할당된 메모리가 GPU에 있다던가하면 당연히 쓸 수가 없다.(GPU메모리에 바로 디코딩하라는 말이니..)

이제 이미지를 로드할 수 있는지 검사하고 가능하다면 로드하는 코드를 작성해 보자.

//Bitmap bmp2Reuse = <<= 재사용할 비트맵; 

Bitmap bmp = null;

String filename = "불러올 파일이름";
BitmapFactory.Options opt;

try {
	opt = getBitmapSizeFile(filename);
	if( opt == null ) {
		throw new Exception( "그림 맞냐?" );
	}
 
	if( getImageByteSize( opt.outWidth, opt.outHeight, Bitmap.Config.ARGB_8888 ) < bmp2Reuse.getAllocationByteCount() ) {
     
		bmp = decodeFile( filename, bmp2Reuse );
		if( bmp == null ) {
			throw new Exception( "왜 못 불러 오냐? 로그캣이나 봐야 겠다." );
		}
	}
	
	// bmp에 로드됨 
}
catch( Exception e ) {
	e.printStackTrace();
}

위의 코드는 그림은 ARGB_8888만 사용한다는 기준에서 만들었다.

 

KITKAT(4.4, SDK19) 미만에서의 제약점

일단 이 비트맵 재사용기능은 (HoneyComb3.0, SDK??)부터 지원되는 기능이다.

위의 예제에서 Bitmap.reconfigure함수는 kikat부터 지원을 한다. getAllocationByte 또한 그러하다. 그래서 재사용을 위해서는 다음의 요건을 충족해야 한다.

사용할 Bitmap과 출력될 Bitmap의 폭과 높이가 동일해야 하고 byte이 크기도 같아야 한다.

jpg와 png만 지원한다. (이 부분이 애매한 것이 kikat이상은 다 된다는 것인지는 파악하지 못했다.)

 

 

Replies
Reply Write