서론
안드로이드 앱에서 이미지 로딩은 사용자 경험(UX)의 핵심 요소입니다. 네트워크 지연이나 서버 오류로 인해 이미지가 제대로 표시되지 않으면 사용자는 불만을 느낄 수 있으며, 이는 곧 앱 평가 하락으로 이어질 수 있습니다. Glide는 이러한 문제를 해결하기 위해 강력한 로딩 상태 관리 기능을 제공합니다. 본 문서에서는 Glide를 활용하여 이미지 로딩의 세 가지 주요 상태 — 로딩 중, 성공, 실패 — 를 효과적으로 처리하는 방법을 설명합니다.
RequestListener를 통한 상태 감지
Glide는 RequestListener 인터페이스를 통해 로딩 과정 전반에 걸쳐 이벤트를 캐치할 수 있도록 지원합니다. 이 인터페이스는 다음과 같은 두 가지 메서드를 포함하고 있습니다:
public interface RequestListener<R> {
boolean onResourceReady(R resource, Object model, Target<R> target,
DataSource dataSource, boolean isFirstResource);
boolean onLoadFailed(@Nullable GlideException e, Object model, Target<R> target,
boolean isFirstResource);
}
각 메서드는 부울 값을 반환하며, 이 값은 이벤트 전파 여부를 결정합니다. false를 반환하면 다음 리스너에게 전달되며, true일 경우 더 이상 전파하지 않습니다.
기본 구현 단계
1. UI 구성 요소 준비
각 상태에 맞는 UI 요소를 레이아웃에 정의해야 합니다. 아래 예제는 이미지, 프로그레스바, 오류 메시지를 포함한 기본 항목 레이아웃입니다.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="200dp" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="visible" />
<TextView
android:id="@+id/error_message"
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
android:text="이미지 로딩 실패"
android:textColor="#FF0000"
android:visibility="gone" />
</FrameLayout>
2. 리스너 로직 작성
뷰 홀더나 액티비티 내에서 RequestListener를 구현하여 각 상태에 따라 UI를 업데이트합니다.
private final RequestListener<Drawable> requestListener = new RequestListener<Drawable>() {
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target,
DataSource source, boolean isFirstResource) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.GONE);
return false; // 기본 동작 유지 (이미지 설정 허용)
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target,
boolean isFirstResource) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.VISIBLE);
Log.w("GlideLoad", "로딩 실패: " + e.getMessage());
return true; // 기본 에러 플레이스홀더 방지
}
};
3. Glide 요청에 리스너 연결
로딩 요청 시 listener() 메서드를 사용해 앞서 정의한 리스너를 첨부합니다.
Glide.with(context)
.load(imageUrl)
.placeholder(R.drawable.placeholder_loading)
.error(R.drawable.image_error_fallback)
.listener(requestListener)
.into(imageView);
고급 활용 패턴
RecyclerView 최적화
리사이클러뷰에서 사용할 경우, 리스너를 뷰 홀더 내부 클래스로 선언하여 외부 참조 누수를 방지하는 것이 좋습니다.
public class ImageViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
private final ProgressBar progressBar;
private final TextView errorMessage;
private final RequestListener<Drawable> listener = new RequestListener<Drawable>() {
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target,
DataSource dataSource, boolean isFirst) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.GONE);
return false;
}
@Override
public boolean onLoadFailed(GlideException e, Object model, Target<Drawable> target,
boolean isFirst) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.VISIBLE);
return true;
}
};
public void bind(String url) {
Glide.with(itemView.getContext())
.load(url)
.listener(listener)
.into(imageView);
}
}
자동 재시도 로직
임시 네트워크 오류 대응을 위해 일정 횟수만큼 재시도하는 전략을 추가할 수 있습니다.
private int attemptCount = 0;
private static final int MAX_RETRY = 3;
private void loadWithRetry(String url) {
Glide.with(context)
.load(url)
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(GlideException e, Object model, Target<Drawable> target,
boolean isFirstResource) {
if (attemptCount < MAX_RETRY) {
attemptCount++;
new Handler(Looper.getMainLooper()).postDelayed(() -> {
loadWithRetry(url);
}, (long) Math.pow(2, attemptCount) * 500); // 지수 백오프
} else {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.VISIBLE);
}
return true;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target,
DataSource dataSource, boolean isFirstResource) {
attemptCount = 0;
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.GONE);
return false;
}
})
.into(imageView);
}
주의사항 및 최적화 팁
메모리 누수 방지
액티비티 생명주기에 맞춰 Glide 요청을 적절히 정리해야 합니다.
@Override
protected void onDestroy() {
super.onDestroy();
Glide.with(this).clear(imageView); // 미완료 요청 취소
}
프로가드 설정
코드 난독화 환경에서 리스너가 제대로 작동하도록 보존 규칙을 추가하세요.
-keep class com.bumptech.glide.request.RequestListener { *; }
-keepclassmembers class * implements com.bumptech.glide.request.RequestListener {
<init>(...);
}
결론
Glide의 RequestListener는 이미지 로딩 상태를 정교하게 제어할 수 있는 강력한 도구입니다. 로딩 인디케이터 표시, 실패 시 사용자 친화적인 피드백 제공, 자동 재시도 등 다양한 UX 개선 기법을 적용할 수 있습니다. 특히 리스트 환경에서는 성능과 안정성을 고려한 구조 설계가 중요하며, ViewHolder 내부에서 리스너를 관리하는 것이 바람직합니다.