Android Retrofit 설명

Retrofit은 Square에서 개발한 안드로이드 및 JVM 환경에서 사용되는 타입-세이프(type-safe) HTTP 클라이언트 라이브러리입니다.

REST API 기반의 네트워크 통신을 간편하고 효율적으로 처리할 수 있도록 도와줍니다.

Retrofit의 주요 특징:

  1. 타입 안정성: Retrofit은 인터페이스를 사용하여 API 엔드포인트를 정의하고, 어노테이션을 통해 HTTP 요청 메서드(GET, POST, PUT, DELETE 등)와 파라미터를 명시합니다.
    • 이를 통해 컴파일 시점에 오류를 확인할 수 있어 안정적인 코드 작성이 가능합니다.
  2. 선언적인 API: Retrofit은 선언적인 방식으로 API 인터페이스를 정의하여 코드의 가독성을 높이고 유지보수를 용이하게 합니다.
  3. 비동기 처리: Retrofit은 기본적으로 OkHttp 라이브러리를 사용하여 네트워크 요청을 비동기적으로 처리합니다.
    • 따라서 UI 스레드를 차단하지 않고 백그라운드 스레드에서 네트워크 작업 수행
  4. 확장성: Retrofit은 Converter, CallAdapter 등 다양한 확장 기능을 제공하여 JSON, XML 등 다양한 데이터 형식을 지원하고, RxJava, Coroutines 등 다양한 비동기 처리 방식을 사용할 수 있습니다.

Retrofit 구성 요소

1. 인터페이스 (Interface):

  • REST API의 엔드포인트를 정의합니다.
  • 각 엔드포인트는 HTTP 메서드(GET, POST, PUT, DELETE 등)와 URL 경로, 파라미터 등을 포함합니다.
  • Retrofit은 이 인터페이스를 기반으로 동적인 프록시 객체를 생성하여 실제 네트워크 요청을 처리합니다.
1
2
3
4
5
6
7
interface ApiService {
    @GET("users/{user}")
    fun getUser(@Path("user") user: String): Call<User>

    @POST("users/new")
    fun createUser(@Body user: User): Call<User>
}

2. 어노테이션 (Annotation):

  • 인터페이스의 메서드에 사용되어 HTTP 요청 정보를 명시합니다.
  • @GET, @POST, @PUT, @DELETE, @PATCH, @HEAD 등의 HTTP 메서드 어노테이션을 사용하여 요청 방식을 지정합니다.
  • @Path, @Query, @Body, @Header 등의 어노테이션을 사용하여 URL 경로, 쿼리 파라미터, 요청 본문, 헤더 등을 설정합니다.
1
2
@GET("users/{user}")
fun getUser(@Path("user") user: String): Call<User>

3. Retrofit 객체:

  • 인터페이스를 기반으로 동적인 프록시 객체를 생성합니다.
  • Retrofit.Builder를 사용하여 Retrofit 객체를 생성하고, 베이스 URL, Converter, CallAdapter 등을 설정합니다.
1
2
3
4
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

4. Call 객체:

  • 네트워크 요청(Request) 을 나타내는 객체입니다.
  • enqueue() 메서드를 호출하여 비동기 요청을 보내고,
  • execute() 메서드를 호출하여 동기 요청을 보냅니다.
  • 콜백 메서드(onResponse, onFailure)를 통해 요청 결과를 처리합니다.
1
2
3
4
val call = apiService.getUser("JohnDoe")
call.enqueue(object : Callback<User> {
    // ...
})

5. Converter:

  • 서버 응답 데이터(JSON)를 객체로 변환하는 역할을 합니다.
  • Gson, Moshi, Jackson 등의 라이브러리를 Converter로 사용할 수 있습니다.
  • Retrofit.BuilderaddConverterFactory() 메서드를 사용하여 Converter를 추가합니다.

Android Retrofit 사용 시 주의 사항

1. 메인 스레드 네트워크 작업 금지:

  • Retrofit은 기본적으로 OkHttp를 사용하여 네트워크 요청을 처리합니다.
  • 안드로이드에서는 메인 스레드에서 네트워크 작업을 수행하면 NetworkOnMainThreadException 예외가 발생합니다.
  • 따라서, Coroutine, RxJava, 또는 Executor 등을 사용하여 네트워크 요청을 백그라운드 스레드에서 처리해야 합니다.

2. 에러 핸들링:

  • 네트워크 요청은 항상 실패할 가능성이 있으므로, 예외 처리를 철저히 해야 합니다.
  • Retrofit은 onFailure() 콜백 또는 RxJava의 onError() 메서드를 통해 에러를 처리할 수 있습니다.
  • HTTP 상태 코드(404, 500 등)에 따른 적절한 에러 메시지를 사용자에게 표시해야 합니다.

3. 응답 데이터 처리:

  • 서버에서 받은 응답 데이터는 JSON, XML 등 다양한 형식일 수 있습니다.
  • Retrofit은 Gson, Moshi, Jackson 등의 Converter를 사용하여 응답 데이터를 자바 객체로 변환할 수 있습니다.
  • 데이터 변환 과정에서 발생할 수 있는 예외를 처리해야 합니다.

Android Retrofit 에서 coroutine을 사용하면, 백그라운드 스레드에서 처리되는 이유

1. Main Thread 안전성 (Main Thread Safety):

  • 안드로이드는 UI 업데이트와 사용자 입력 처리를 담당하는 메인 스레드(Main Thread) 를 가지고 있습니다.

  • 메인 스레드에서 시간이 오래 걸리는 작업(예: 네트워크 요청)을 수행하면 UI가 멈추거나 응답하지 않는 현상이 발생할 수 있습니다.

    • 이를 ANR(Application Not Responding) 이라고 합니다.
  • Coroutine은 suspend 함수를 통해 비동기 작업을 간편하게 처리할 수 있도록 도와주는 기능입니다.

  • Retrofit과 Coroutine을 함께 사용하면 suspend 함수 내에서 네트워크 요청을 수행하고, 요청이 완료될 때까지 해당 Coroutine을 일시 중단(suspend)시킵니다.

  • 이때 Coroutine은 백그라운드 스레드로 전환되어 네트워크 요청을 처리 하고, 요청이 완료되면 다시 메인 스레드로 돌아와 결과를 처리 합니다.

  • 이러한 방식으로 메인 스레드를 차단하지 않고 네트워크 요청을 처리하여 UI의 반응성을 유지할 수 있습니다.

2. Retrofit의 Main Safety 보장:

  • Retrofit 2.6.0 버전부터는 Coroutine을 공식적으로 지원하며, suspend 함수를 사용하여 네트워크 요청을 수행할 수 있습니다.
  • Retrofit은 내부적으로 OkHttp 라이브러리를 사용하여 네트워크 요청을 처리하는데, OkHttp는 기본적으로 백그라운드 스레드에서 네트워크 작업을 수행하도록 설계되어 있다.
  • 따라서 Retrofit에서 Coroutine을 사용하더라도, 실제 네트워크 요청은 OkHttp에 의해 백그라운드 스레드에서 처리됩니다.
  • 이는 Coroutine의 Main Safety 보장 기능과 함께 작동하여, 개발자가 명시적으로 백그라운드 스레드를 지정하지 않아도 안전하게 네트워크 요청을 처리할 수 있도록 합니다.

Android OkHttp 설명

OkHttp는 Square에서 개발한 강력하고 효율적인 HTTP 클라이언트 라이브러리입니다. 안드로이드 뿐만 아니라 자바 및 코틀린 환경에서도 사용할 수 있습니다.

OkHttp는 HTTP/2, WebSocket 등 최신 웹 기술을 지원하며, 안정성과 성능이 뛰어나다는 장점을 가지고 있습니다.

OkHttp 주요 특징:

  • HTTP/2 지원: HTTP/2 프로토콜을 지원하여 다중화, 헤더 압축, 서버 푸시 등의 기능을 활용하여 네트워크 성능을 향상시킵니다.
  • 연결 풀링: 연결 풀링을 통해 여러 요청을 효율적으로 처리하고, 연결 재사용을 통해 성능을 최적화합니다.
  • 투명한 GZIP 압축: 응답 데이터를 자동으로 압축하여 네트워크 대역폭을 절약합니다.
  • 응답 캐싱: 응답 데이터를 캐싱하여 동일한 요청을 반복할 때 네트워크 트래픽을 줄이고 응답 시간을 단축합니다.
  • 요청 재시도: 네트워크 오류 발생 시 자동으로 요청을 재시도하여 안정성을 높입니다.
  • Interceptor 지원: Interceptor를 사용하여 요청 및 응답을 가로채서 로깅, 헤더 추가, 인증 등 다양한 작업을 수행할 수 있습니다.
  • WebSocket 지원: WebSocket 프로토콜을 지원하여 실시간 양방향 통신을 구현할 수 있습니다.

OkHttp와 Retrofit의 관계:

Retrofit은 OkHttp의 기능을 활용하면서, 더욱 간편하고 직관적인 API를 제공하여 REST API 통신을 쉽게 구현할 수 있도록 도와줍니다.

  • OkHttp: 저수준 HTTP 클라이언트, 강력하고 유연하지만 사용하기 복잡

  • Retrofit: OkHttp 기반 고수준 라이브러리, 간편하고 직관적인 API, 선언적 방식, 비동기 처리 지원

OkHttp 사용 방법:

  1. OkHttpClient 생성: OkHttpClient 객체를 생성하고 필요에 따라 설정을 변경합니다.

    1
    2
    3
    4
    5
    
    val client = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        })
        .build()
    
  2. Request 생성: Request.Builder를 사용하여 요청 객체를 생성하고 URL, 헤더, 요청 본문 등을 설정합니다.

  3. 요청 실행: OkHttpClient 객체의 newCall() 메서드를 사용하여 요청을 실행하고 enqueue() 메서드를 통해 비동기적으로 응답을 처리합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // 요청 실패 처리
        }
        override fun onResponse(call: Call, response: Response) {
            // 응답 성공 처리
            val body = response.body?.string()
        }
    })
    

OkHttp Interceptor 설명

OkHttp Interceptor는 네트워크 요청 및 응답을 가로채서 추가 작업을 수행하거나 수정할 수 있는 강력한 기능입니다.

로깅, 인증, 캐싱, 헤더 수정, 요청/응답 데이터 변환 등 다양한 목적으로 활용될 수 있습니다.

1. Application Interceptor:

  • 역할: 애플리케이션 레벨에서 요청 및 응답을 가로챕니다. 실제 네트워크 연결이 이루어지기 전후에 동작하며, 캐시된 응답에도 적용됩니다.

  • 주요 용도:

    • 로깅 (Logging): 요청 및 응답 정보를 로그로 출력하여 디버깅을 돕습니다.
    • 헤더 추가 (Header Modification): 모든 요청에 공통적으로 추가해야 하는 헤더(예: 인증 토큰, API 키)를 설정합니다.
    • 인증 (Authentication): 요청에 인증 정보를 추가하거나, 응답을 기반으로 인증 토큰을 갱신합니다.
    • 요청/응답 수정 (Request/Response Rewriting): 요청 URL, 파라미터, 응답 데이터 등을 수정합니다.
  • OkHttpClient.BuilderaddInterceptor() 메서드를 사용하여 추가합니다.

2. Network Interceptor:

  • 역할: 네트워크 레벨에서 요청 및 응답을 가로챕니다. 실제 네트워크 연결이 이루어질 때만 동작하며, 캐시된 응답에는 적용되지 않습니다.
  • 주요 용도:
    • 캐싱 (Caching): 응답 데이터를 캐시에 저장하거나 캐시된 응답을 반환하여 네트워크 트래픽을 줄입니다.
    • 압축 (Compression): 요청 및 응답 데이터를 압축하여 네트워크 대역폭을 절약합니다.
    • 요청 재시도 (Retry): 네트워크 오류 발생 시 자동으로 요청을 재시도합니다.
    • 네트워크 연결 변경 (Connection Modification): 특정 조건에 따라 다른 네트워크 연결을 사용하도록 변경합니다.
  • OkHttpClient.BuilderaddNetworkInterceptor() 메서드를 사용하여 추가합니다.

Interceptor 사용 예시:

1
2
3
4
5
6
val client = OkHttpClient.Builder()
    .addInterceptor(HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    })
    .addNetworkInterceptor(StethoInterceptor())
    .build()
  • HttpLoggingInterceptor: 요청 및 응답 정보를 로그로 출력하는 Interceptor입니다. 디버깅에 유용합니다.

커스텀 Interceptor 생성:

1
2
3
4
5
6
7
8
9
class AuthInterceptor(private val token: String) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val newRequest = originalRequest.newBuilder()
            .header("Authorization", "Bearer $token")
            .build()
        return chain.proceed(newRequest)
    }
}

위 예시는 요청 헤더에 인증 토큰을 추가하는 커스텀 Interceptor를 보여줍니다.

Interceptor 활용 팁:

  • 여러 Interceptor를 사용할 때는 순서가 중요합니다. 요청(Request)은 추가된 순서대로 Interceptor를 거치고, 응답(Reply)은 역순으로 Interceptor를 거칩니다.
  • Interceptor는 강력한 기능을 제공하지만, 과도하게 사용하면 성능 저하를 일으킬 수 있습니다.
  • Interceptor를 사용하여 민감한 정보(예: API 키, 개인 정보)를 로깅하지 않도록 주의해야 합니다.