2012년 6월 14일 목요일

recycle시 같은 이미지 재사용하면 에러 나는 이유


Drawable Mutations

이번엔 구글 개발자 사이트의 기술 문서 중, 짤막한 내용을 번역해 보았습니다. 어플리케이션 개발(특히 GUI) 에 엄청나게 많이 활용되는 Drawable, 그 중에서도 상당히 유용해 보이는(적어도 문서상으로는...) muate 함수 관련된 내용입니다. Drawable 이 실제 Bitmap Data 등과는 어떤 관계를 갖고 있는지 대략적으로 감을 잡을 수 있겠네요.
 안드로이드 Drawable 은 어플리케이션을 작성하는데 굉장히 큰 도움을 준다. Drawable 은 일반적으로 View 와 연관되어 사용되며, 쉽게 교체할 수 있는 그림을 담는 상자(Drawing Container) 이다. 예를 들어 BitmapDrawable 은 이미지를 표시하는데 사용되며, ShapeDrawable 은 도형이나 그라데이션등을 그리는데 사용된다. 또한, 개발자는 좀 더 복잡한 랜더링을 위해, Drawable 들을 결합할 수도 있다.
 Drawable 은 개발자들이 안드로이드에서 제공하는 위젯들을 상속받아 새로운 클래스를 만들지 않고도, 화면상에 그려지는 위젯의 모습을 변경할 수 있게 해준다. 사실 Drawable 은 너무나 편리해서, 대부분의 안드로이드 기본 어플리케이션과  위젯들은 Drawable 을 이용해서 만들어 졌다. 실재로 안드로이드 프레임워크 코어 단에는 약 700 개의 Drawable 이 사용된다. Drawable 들은 매우 광범위하고 빈번하게 사용되고 있음으로, 안드로이드는 효율적인 방식으로 리소스로부터 Drawable 을 로드한다. 
 예를 들어 개발자가 매번 새로운 버튼을 생성할때 마다, 새로운 Drawable 인스턴스(android.R.drawable.btn_default)가 안드로이드 프레임워크의 리소스에서 로드된다. 즉, 어플리케이션에서 사용되는 버튼들은 모두 서로 다른 Drawable 인스턴스를 갖고 있다는 뜻이다. 그러나 버튼에 대한 모든 Drawable 들은 하나의 'Constant State' 를 공유한다. Constant State 에 포함되는 값은 Drawable 의 종류에 따라 다르지만, 일반적으로 리소스 파일내에서 정의할 수 있는 모든 속성값을 포함한다. 버튼의 Constant State 는 Bitmap 이미지를 포함한다. 따라서, 어플리케이션에서 사용되는 모든 버튼은 동일한 Bitmap 이미지를 공유해서 사용하고, 해당 이미지는 메모리 상에 한번만 로드되면 됨으로 메모리를 크게 절약할 수 있다.
 다음의 다이어그램은 두 개의 서로 다른 View 에서, 서로 동일한 이미지 리소스를 배경으로 설정 할 때, 어떤 구성요소들이 생성되는지 보여 준다. 아래 그림에서와 같이 두 개의 Drawable 인스턴스가 생성되지만, 두 개의 인스턴스는 서로 동일한 Constant State - Bitmap 이미지 를 공유한다. 

 이렇게 서로 다른 Drawable 이 동일한 Constant State 를 공유하는 것은 메모리 낭비를 크게 줄여주지만, 개발자가 Drawable 의 속성값을 변경하고자 한다면 문제를 야기할 수도 있다. 예를 들어 책 목록 (혹은 음악 목록)을 보여주는 어플리케이션을 상상해 보자. 각 책이름 옆에서 반투명한 별 표시가 있고, 사용자는 자신이 좋아하는 책에 대하여, 별을 클릭해 표시할 수 있다. 이런 기능을 구현하기 위해 개발자는 아마도 ListAdapter 의 getView() 메서드에 아래와 같은 코드를 작성할 것이다.
Book book = ...;
TextView listItem = ...;

listItem.setText(book.getTitle());
Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.setAlpha(255); // opaque
} else {
  star.setAlpha(70); // translucent
}
 그러나 불행히도, 이 코드는 잘 작동하지 않는다. 사용자의 선택과는 관계없이 모든 별은 똑같이 반투명하거나, 똑같이  불투명하게 표시된다. 

  이 현상의 원인은 비로 하나의 어플리케이션에서 서로 다른 Drawable 인스턴스를 사용하고 있다 하더라도, 각각의 아이템에 대하여 동일한 Constant State 를 사용하고, BitmapDrawable 의 경우 투명도 속성값은 Constant State 에 속하는 값 중에 하나이기 때문이다. 따라서 하나의 Drawable 인스턴스의 투명도 값을 변경하면, 다른 모든 인스턴스의 투명도 값도 변경 된다. 이러한 문제를 잘 처리하는 것은 안드로이드 1.0 혹은 1.1 에서는 쉽지 않았다. 

 하지만, 다행이도 안드로이드 1.5 이 후 버전 에서는 Constant State 가 모든 인스턴스간에 공유되기 때문에 발생하는 문제를 해결하기 위한 간편한 방법을 제공한다. 개발자가 Drawable 의 mutate() 메서드를 호출 하면, 해당 Drawable 의 Constant State 가 복제되고, 개발자는 다른 인스턴스들에게 영향을 주지 않고 마음대로 원하는 속성 값을 변경할 수 있다. 또한가지 중요한 사실은 Constant State 는 복제되더라도, Bitmap 리소스는 여전히 공유된다는 점이다.  아래의 다이어그램은 개발자가 mutate() 메서드를 호출 한 후 어떤 일이 일어나는지 보여준다. 

 이제, 이전 코드를 mutate 메서드를 이용해서 수정해 보자.
Drawable star = context.getResources().getDrawable(R.drawable.star);
if (book.isFavorite()) {
  star.mutate().setAlpha(255); // opaque
} else {
  star. mutate().setAlpha(70); // translucent
}
  mutate() 메서드는 Drawable 자체를 반환한다. 이때, 반환된 Drawable 은 자기 자신이며, 새롭게 생성된 것은 아니다. 이는, 개발자가 추가적인 메서드를 바로 뒤 이어서 호출(Chain method call) 할 수 있도록 해주기 위해서 이다. 새롭게 수정한 코드는 아래와 같이 정상적으로 동작한다.

댓글 없음:

댓글 쓰기