jasonbla

[Android] 피카소(Picasso) 라이브러리 소개 및 주의할 점 본문

Programming

[Android] 피카소(Picasso) 라이브러리 소개 및 주의할 점

jason jason hwang 2017.09.23 14:30

피카소(Picasso) 라이브러란?

피카소(Picasso)는 외부로부터 이미지를 불러와야 할 경우 유용하게 사용할 수 있는 라이브러리입니다.

매우 간단한 코드 몇 줄로 이미지 로딩, 메모리 & 디스크 캐싱, 변형(Transforming)을 가능케 합니다.

피카소 라이브러리의 장점

피카소 공식 사이트에서 강조(?)하고 있는 장점은 다음과 같습니다.


- Handling ImageView recycling and download cancelation in an adapter.

- Complex image transformations with minimal memory use.

- Automatic memory and disk caching.


이미지를 화면에 보여주는 일에 수반되는 부가적인 작업이 정말 많음을 짐작할 수 있습니다. (캐싱, 이미지 가공, 메모리 관리 등)


피카소(Picasso)와 비슷한 글라이드(Glide)라는 라이브러리도 있으며, 사용법 또한 거의 같습니다.

다음 아티클에 어떤 차이점이 있는지 상세하게 설명되어 있으니 사용하고자 하는 시스템에 맞게 사용하면 됩니다.


https://medium.com/@multidots/glide-vs-picasso-930eed42b81d

공식 사이트

Picasso Library : https://github.com/square/picasso

Glide Library : https://github.com/bumptech/glide

둘 다 워낙 유명한 라이브러리이고, 구글링을 통해 거의 모든 정보를 얻을 수 있기 때문에 여기서는 상세한 라이브러리 사용법을 소개하지 않고, 개발하면서 겪었던 메모리 문제에 대해 글을 써보려 합니다.

피카소 라이브러리 사용법

다음과 같이 매우 간단하게 imageView에 이미지를 할당할 수 있습니다.


Picasso
    .with(context)
    .load("http://i.imgur.com/DvpvklR.png")
    .into(imageView);


하지만, ImageView에 대한 어떠한 정보도 현재로서 알 수 없기 때문에, 피카소는 원본 이미지를 그대로 디스크에 저장하고 메모리에 할당합니다.


그리고 imageView가 inflate될때마다 실시간으로 메모리에서 가져와 imageView에 맞게 처리하게 되는데, 이 경우에 예를 들어 imageView가 200x200 밖에 되지 않더라도 Picasso는 원본 이미지를 계속해서 메모리에 유지하고, 실시간으로 처리하려고 시도합니다.


그래서 다음과 같이 resize 처리를 해주거나, fit 함수를 사용해주면 이러한 문제를 해결할 수 있습니다. 사실 fit 함수는 imageView의 크기를 계산해서 내부적으로 resize를 해주기 때문에 거의 같은 성능을 보이구요. 물론 imageView의 크기가 고정적이라면, 명시적인 width & height를 주는 게 가장 좋은 방법입니다 : )

참고할 아티클

더 상세한 내용은 다음 아티클에 정말 잘 소개되어 있습니다.


캐시 정책 : https://futurestud.io/tutorials/picasso-influencing-image-caching

리사이징 & 스케일링 : https://futurestud.io/tutorials/picasso-image-resizing-scaling-and-fit

문제 1 : Fit 또는 Resize를 사용하지 못하는 경우

피카소는 해야 할 일을 모두 마친 후 (캐시, 가공 등) into 메서드를 통해 imageView에 이미지를 할당합니다.


그러면 Bitmap을 사용해야 하는 경우는? 다음과 같이 Target Callback을 활용해야 합니다.


Target target = new Target() {
    @Override
    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
        doSomething(bitmap)
    }

    @Override
    public void onBitmapFailed(Drawable errorDrawable) {

    }

    @Override
    public void onPrepareLoad(Drawable placeHolderDrawable) {

    }
};

Picasso
        .with(this)
        .load("http://i.imgur.com/DvpvklR.png")
        .into(target);

그런데, Target Callback을 통해 bitmap을 전달받는 경우는 resize, centerCrop, fit 등 몇몇 함수를 사용할 수 없습니다. 만약 함께 사용하려 시도한다면 다음 에러가 발생합니다.


Caused by: java.lang.IllegalStateException: Fit cannot be used with a Target.


개발할 때, Bitmap을 사용해야 하는 경우는 다음과 같았는데


- Notification의 thumbnail

- 락 스크린의 미디어 thumbnail


피카소 라이브러리의 Fit 함수 구현체를 보면 다음과 같이 기술되어 있습니다.


/**
 * Attempt to resize the image to fit exactly into the target {@link ImageView}'s bounds. This
 * will result in delayed execution of the request until the {@link ImageView} has been laid out.
 * Note: This method works only when your target is an {@link ImageView}.
 */
public RequestCreator fit() {
  deferred = true;
  return this;
}


왜 Fit 함수와 Target이 Bitmap인 경우 함께 사용할 수 없는지 정확한 이유를 알기는 어렵지만… 어쨌든 

Resize, centerCrop 등의 함수를 사용할 수 없으니, 정사각형 1:1 비율이 아닌 이미지의 경우는 어쩔 수 없이 아래처럼 Bitmap을 후가공하는 커스텀 함수를 구현하여 아래와 같이 처리하였습니다.

Transformation transformation = new Transformation() {
    @Override
    public Bitmap transform(Bitmap source) {
        return myCenterCrop(source);
    }
 
    @Override
    public String key() {
        return null;
    }
}
 
Picasso
        .with(this)
        .load(url)
        .transform(transformation)
        .into(target);
 

문제 2 : 메모리 증가 문제

onBitmapLoaded 콜백을 통해 bitmap을 centerCrop한 후 필요한 곳에 적용하니 잘 작동되는것 처럼 보였습니다.

고해상도 이미지로 테스트해보기 전까지는…..


테스트를 하다 보니, 2M 이상의 고해상도 이미지를 불러올 때마다 메모리가 20mb 가까이 치솟는 현상을 발견하였습니다….!

5번만 호출해도 100mb는 훌쩍 넘어버리고 결국 앱이 죽는 현상까지 발생했습니다.


onBitmapLoaded를 통해 넘어온 bitmap을 가공해서 사용할 경우, 가공된 bitmap을 캐시할 수 없기 때문에 계속해서 메모리 누수가 발생하는 것입니다. (가공된 bitmap을 캐시하도록 따로 추가한다면 모르지만)

해결방법

이를 해결하기 위해서는 다음과 같이 onBitmapLoaded로 넘어오기 이전에 이미지 처리를 해야 가공된 이미지를 메모리에 올릴 수 있습니다.


Transformation transformation = new Transformation() {
    @Override
    public Bitmap transform(Bitmap source) {
        return myCenterCrop(source);
    }

    @Override
    public String key() {
        return null;
    }
}

Picasso
        .with(this)
        .load(url)
        .transform(transformation)
        .into(target);


이후, transform 함수 내에서 로그를 찍어보면 최초 한 번만 호출되는 것을 확인할 수 있고, 더불어 메모리 누수도 막을 수 있습니다.

정리

피카소라는 막강한 라이브러리를 사용하면 빠르게 결과물을 만들어 낼 수 있지만, 라이브러리의 특성 및 원리를 제대로 이해하지 않고 사용했다가 큰코다칠 수 있다는 경험을 깨닫게 된 소중한 경험이었네요.


결론 : 잘 알고 쓰자. 

0 Comments
댓글쓰기 폼