DEV Community

Sewon Ann
Sewon Ann

Posted on

Picasso, 직접 이미지를 설정할 땐 비동기 작업 처리 취소를 잊지말자

앱에서 이미지로더로 picasso 를 사용하고 있다. 얼마 전 새로운 요구사항이 추가되었다. 모델에서 이미지의 source를 로컬 리소스를 사용할 건지, 웹 url을 사용할 건지 분기를 하여 처리해야 한다. 이 부분은 간단히 만들 수 있다. 대강 이렇게 구현하면 된다.

sealed class ImageSource {
  class RemoteImage(val url:String) : ImageSource()
  class LocalResourceImage(@DrawableRes val resId: Int) : ImageSource()
}

when( source) {
    is RemoteImage -> picasso.load(source.url)
    is LocalResourceImage -> picasso.load(source.resId)
}
.into( imageView)

다 된 줄 알았는데 VectorDrawble이 제대로 보이지 않는다는 제보가 들어왔다. 하지만 Jake Wharton 형님은 안해주신다고 하였다.

Comment for #1109

No plans. Vectors, by definition, don't really need an image processing pipeline to change their size. Why are you wanting to use Picasso for this?

궁금해서 왜 vector drawable일 경우 실패하나 찾아보니 ResourceRequestHandler 에서 BitmapFactory.decodeResource 를 통해 bitmap 을 가져오는데, vector drawable일 경우 여기서 null이 떨어져버린다.

https://github.com/square/picasso/blob/2.71828/picasso/src/main/java/com/squareup/picasso/ResourceRequestHandler.java#L48

  private static Bitmap decodeResource(Resources resources, int id, Request data) {
    final BitmapFactory.Options options = createBitmapOptions(data);
    if (requiresInSampleSize(options)) {
      BitmapFactory.decodeResource(resources, id, options);
      calculateInSampleSize(data.targetWidth, data.targetHeight, options, data);
    }
    return BitmapFactory.decodeResource(resources, id, options);
  }

여기까지 확인해서 picasso 의 load() 로는 vector drawable을 읽어들일 수 없다고 결론을 내렸다.

이 경우 취할 수 있는 판단은 아래 2가지 일 것이다.

  1. 어떻게든 picasso 맥락 안에서 해결한다.
  2. picasso 에서 해결할 수 없는 건 picasso 맥락을 벗어나 해결한다.

이 경우 1번으로 할 여지가 거의 없어 보인다. 막 억지로 transform 이나 callback을 집어넣으면 유지보수 측면에선 오히려 더 해롭다. 그래서 picasso 맥락 안에서 해결하는 건 포기하고 아래와 같이 수정했다. 내 경우 local resource일 경우 transform 등의 후처리가 전혀 필요하지 않았기 때문에 간단했다.

when( source) {
    is RemoteImage -> {  //원격 이미지는 picasso로 처리
      picasso.load(source.url)
        .into( imageView)
    }
    is LocalResourceImage -> { //로컬 이미지는 그냥 setImageDrawble로 처리
      imageView.setDrawable( context.resource.getDrawble( source.resId) )
    }
}

잘 동작하는 것 같다. 하지만 RecyclerView 와 같이 뷰를 재사용하는 경우 문제가 된다. 왜 문제가 되는지 3초 동안 생각해보시오.

...

RecyclerView 를 빠르게 스크롤 한 경우, 원격 이미지를 picasso 가 비동기로 불러오는 와중에 로컬 리소스가 setDrawble() 된 경우가 발생할 수 있다. 이러면 로컬 리소스 이미지가 보여야 할 자리에 뜬금없는 원격 이미지가 뜰 수 있다. picasso 맥락 안에서 해결했으면 이런 일이 없었을 것이다. 이 경우 다행히 picasso 는 cancelRequest() 라는 메서드를 제공하기 때문에, ImageView 를 대상으로 동작중인 비동기 작업을 취소하면 된다.

when( source) {
    is RemoteImage -> {  //원격 이미지는 picasso로 처리
      picasso.load(source.url)
        .into( imageView)
    }
    is LocalResourceImage -> { //로컬 이미지는 그냥 setImageDrawble로 처리
      picasso.cancelRequest(imageView)
      imageView.setDrawable( context.resource.getDrawble( source.resId) )
    }
}

이런 이슈는 비단 picasso 뿐 아니라 다른 프레임워크를 사용할 때도 쉽게 접할 수 있는 문제이다. 따라서 프레임워크 맥락에서 해결할 수 있는 문제는 어지간하면 프레임워크 안에서 해결하는게 대게는 좋은 선택지라고 생각한다.

Top comments (0)