Retrofit2
Retrofit은 OkHttp와 마찬가지로 Square 사에서 개발한 오픈 소스 Android 및 Java 용 HTTP 클라이언트 라이브러리이다. 주로 Restful API와의 통신을 간소화하고 편리하게 만들기 위해 설계되었다.
간결한 API 사용
Retrofit은 간결하고 사용하기 쉬운 API를 제공한다. 인터페이스 기반으로 API를 정의하고, 각 메서드는 원격 서버에 요청을 나타낸다.
// Retrofit 인스턴스 생성
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// API 인터페이스 정의
// 각 메서드는 특정 엔드포인트와 HTTP 메서드를 어노테이션으로 명시
interface ApiService {
@GET("posts/{id}") // 엔드포인트에 {id}는 Path Variable로 특정한 데이터를 요청할 때 사용하며 인자로 넘어온 postId값이 어노테이션으로 설정한 키와 매칭되어 URL에 설정된다.
fun getPost(@Path("id") postId: Int): Call<Post>
@GET("posts")
fun getPosts(): Call<List<Post>>
@POST("posts")
fun createPost(@Body post: Post): Call<Post> // POST메서드로 데이터 업로드를 요청할 때는 요청하는 데이터에 @Body 어노테이션을 붙인다
}
// API 인터페이스의 구현체 생성
val client = retrofit.create(JsonPlaceholderApi::class.java)
// 1번 게시물 가져오기
val callGetPost = client.getPost(1)
val responseGetPost = callGetPost.execute()
val post = responseGetPost.body()
// 모든 게시물 가져오기
val callGetPosts = client.getPosts()
val responseGetPosts = callGetPosts.execute()
val posts = responseGetPosts.body()
// 새로운 게시물 생성
val newPost = Post(userId = 1, id = 101, title = "New Post", body = "This is a new post.")
val callCreatePost = client.createPost(newPost)
val responseCreatePost = callCreatePost.execute()
val createdPost = responseCreatePost.body()
미리 요청할 API 인터페이스를 정의하고 API 인터페이스의 구현체를 생성해놓으면 사용할 때 base url이나 메서드 설정을 할 필요없이 호출하는 것만으로 해당 API를 호출하고 응답을 받을 수 있다.
Call
Call은 Retrofit에서 제공하는 비동기 HTTP 요청을 나타내는 인터페이스이다. Retrofit은 원격 서버와 통신하기 위해 사용자에게 Call 객체를 반환한다. Call 인터페이스는 다양한 메서드를 제공하여 HTTP 요청의 비동기적인 실행 및 취소, 결과 처리 등을 다룰 수 있게 해준다.
execute()
동기적으로 요청을 실행하고, 응답을 반환한다. 주로 백그라운드 스레드에서 사용되어야 한다. UI 스레드에서 호출 시 네트워크 작업이 UI를 차단할 수 있다.
enqueue(Callback<T> callback)
비동기적으로 요청을 실행하고, 응답을 처리하기 위해 콜백을 등록한다. 콜백의 onResponse 또는 onFailure 메서드가 호출된다.
// 1번 게시물 가져오기 - excute 사용: 동기적인 요청
val callGetPost = client.getPost(1)
val responseGetPost = callGetPost.execute()
val post = responseGetPost.body()
// 1번 게시물 가져오기 - enqueue 사용: 비동기적인 요청
val callGetPost: Call<Post> = client.getPost(1)
callGetPost.enqueue(object : Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
val post: Post? = response.body()
// 응답 처리
}
override fun onFailure(call: Call<Post>, t: Throwable) {
// 요청 실패 처리
}
})
Response
Response 는 Retrofit에서 HTTP 응답을 나타내는 클래스이다. Retrofit은 요청을 실행하고 서버로부터 받은 응답을 Response 객체로 감싸서 반환한다. 이 객체는 응답의 상태 코드, 헤더, 바디 등을 포함하고 있다.
Response 클래스는 제네릭으로 선언되어, 해당 응답의 바디를 어떤 타입으로 변환해야 하는지를 나타낸다. 예를 들어, Response<Post>는 서버에서 받은 응답을 Post 객체로 변환하는 데 사용된다.
일반적으로 Response 객체는 Retrofit에서 비동기적인 요청을 수행할 때 Callback을 통해 제공된다. Callback의 onResponse 메서드에서 Response 객체를 통해 서버 응답을 확인하고 처리할 수 있다.
"fun getPost(@Path("id") postId: Int): Response<Post>" 이렇게 Call 대신 Response로 감싸 콜백 형태가 아닌, 응답을 직접 다룰 수 있으며, Kotlin의 코루틴과 조합하여 사용할 때 유용하다
val callGetPost: Call<Post> = client.getPost(1)
callGetPost.enqueue(object : Callback<Post> {
override fun onResponse(call: Call<Post>, response: Response<Post>) {
if (response.isSuccessful) {
val post: Post? = response.body()
// 서버 응답 처리
} else {
// 서버가 에러 응답을 반환한 경우
val errorBody: String? = response.errorBody()?.string()
println("Error: $errorBody")
}
}
override fun onFailure(call: Call<Post>, t: Throwable) {
// 요청 실패 처리
}
})
response.isSuccessful
Retrofit에서 제공하는 Response 객체의 메서드 중 하나로, 해당 응답이 성공적인지 여부를 확인하는데 사용된다. 이 메서드는 HTTP 응답 코드가 200에서 299 사이인지를 체크한다. 만약 성공적인 응답이라면 true를 반환하고, 그렇지 않으면 false를 반환한다.
response.code()
HTTP 응답 코드를 반환한다. 성공적인 응답의 경우 200, 201, 204 등의 코드가 반환되며, 실패한 경우에는 4xx(클라이언트 오류) 또는 5xx(서버 오류) 코드가 반환된다.
val code: Int = response.code()
if (response.isSuccessful) {
println("Request successful. Status code: $code")
} else {
println("Request failed. Status code: $code")
}
response.body()
HTTP 응답의 바디를 반환한다. 성공적인 경우에는 해당 타입으로 변환된 객체가 반환되며, 실패한 경우에는 null이 반환된다.
val post: Post? = response.body()
if (post != null) {
println("Post title: ${post.title}")
} else {
println("Failed to parse response body.")
}
response.errorBody()
실패한 경우에만 호출 가능하며, HTTP 응답의 에러 바디를 반환한다. 이는 주로 서버에서 반환한 에러 메시지 등을 확인하는 데 사용된다.
val errorBody: String? = response.errorBody()?.string()
println("Error response body: $errorBody")
response.headers()
서버 측에서 설정한 HTTP 응답의 헤더를 반환한다. 이 메서드를 통해 헤더의 값을 확인할 수 있다.
val headers: Headers = response.headers()
val contentType: String? = headers.get("Content-Type")
println("Content-Type: $contentType")
Annotation 기반 메서드 선언
Retrofit은 어노테이션을 사용하여 HTTP요청을 어떻게 구성할지 선언적으로 정의한다. 예를 들어, @GET, @POST, @Query 등의 어노테이션을 사용하여 요청 메서드와 매개변수를 정의할 수 있다.
interface ApiService {
@GET("posts/{id}/comments")
fun getComments(
@Path("id") postId: Int,
@Query("sort") sort: String // @Query는 특정한 데이터보다는 필터링된 데이터를 요청할 때 주로 사용한다.
): Call<List<Comment>>
}
이처럼 어노테이션을 활용해 요청에 대한 정의를 다양하게 조합할 수 있다. 예시의 API 메서드는 특정 아이디의 게시물에 해당하는 댓글들을 정렬해서 가져오라는 뜻의 API 메서드이다.
요청될 때 URL를 예시로 살펴보면 "https://example.com/posts/1/comments?sort=recency" 로 변환된다.
타입 변환 지원
Retrofit은 기본적으로 JSON 응답을 변환하여 모델 클래스로 제공한다. 경우에 따라 Jackson, XML등의 타입 변환도 필요할 수 있다. Retrofit은 데이터의 포맷에 맞는 변환기 (Converter)를 지원하며, 사용자 정의 변환기도 구현할 수 있다.
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
Retrofit 인스턴스 생성할 때 addConverterFactory 를 통해 변환기를 등록해준다. 여기서 JSON 데이터 포맷을 변환해주는 Gson 변환기를 설정해준다.
이런식으로 Retrofit은 자동으로 서버와의 요청 및 응답의 데이터 포맷에 맞게 변환작업을 수행해주며, 사용자는 따로 데이터 변환에 필요한 작업을 추가하지 않아도 된다.
OkHttp & Retrofit
Retrofit은 OkHttp를 감싸는 더 높은 수준의 API이다.
- Retrofit은 OkHttp 위에서 실제로 무엇을 제공하는가?
- OkHttp를 직접 사용하는 것을 고려해야 하는가?
Refrofit은 구조화된 URL 및 매개변수를 구성한다.
Retrofit 에게 BASE_URL을 제공함으로써 Retrofit object를 만들 수 있다.
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
서비스 인터페이스 API에서 원하는 요청 방식과 파라미터에 대해 어노테이션을 통해 정의할 수 있다.
@GET(BASE_PATH)
fun hitCountCheckCall(
@Query(PARAM_ACTION) action: String,
@Query(PARAM_FORMAT) format: String,
@Query(PARAM_LIST) list: String,
@Query(PARAM_SRSEARCH) srsearch: String)
: Call<Model.Result>
이후 네트워크 호출을 할 때마다 미리 정의해놓은 인터페이스 메서드에 필요한 데이터들을 전달해서 호출할 수 있다.
wikiApiServe.hitCountCheckCall(
VALUE_QUERY,
VALUE_JSON,
VALUE_SEARCH,
searchString
)
반면, OkHttp만을 이용해 네트워크 요청을 하려면 다음과 같이 수동으로 요청을 구성해야 한다.
val request = Request.Builder().url(
"$BASE_URL$BASE_PATH/?" +
"$PARAM_ACTION=$VALUE_QUERY&$PARAM_FORMAT=$VALUE_JSON&" +
"$PARAM_LIST=$VALUE_SEARCH&$PARAM_SRSEARCH=$searchString"
).build()
이는 개발자의 실수가 생길 확률이 높으며 가독성 면에서 좋지 않다.
네트워크 응답 객체에 대해서도 두 라이브러리 간에 차이가 존재한다.
JSON은 코틀린에서 바로 사용할 수 있는 데이터 형식이 아니기 때문에 JSON을 데이터 클래스로 변환해줄 컨버터를 사용해야 한다. Retrofit objectf를 생성할 때 GsonConverterFactory를 연결한다면, Retrofit 통신 성공 시 응답 객체의 body를 별도의 변환 과정 없이 바로 사용할 수 있다.
call?.enqueue(
object : Callback<Model.Result> {
override fun onFailure(call: Call<Model.Result>, t: Throwable) {
...
}
override fun onResponse(call: Call<Model.Result>,
response: Response<Model.Result>) {
response.body()?.let { //바로 사용 가능 }
}
그러나 OkHttp에서는 반환받은 JSON 객체를 데이터클래스로 바로 변환해주지 못하기 때문에 별도의 과정을 통해 직접 변환해서 사용해야 한다.
client.newCall(request).enqueue(
object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
...
}
override fun onResponse(call: okhttp3.Call,
response: okhttp3.Response) {
response.body()?.let {
val result = Gson().fromJson(it.string(), Model.Result::class.java)
useResult(result)
}
}
)
또한 결과가 반환된 직후 Retrofit의 경우 enqueue를 사용하면 네트워크 호출이 자동으로 백그라운드에서 이루어지며 결과값이 자동으로 메인스레드에 전달되기 때문에 Toast와 같은 UI관련 메서드에서 결과값을 사용할 수 있다.
override fun onResponse(call: Call<Model.Result>,
response: Response<Model.Result>) {
Toast.makeText(...).show()
})
OkHttp도 enqueue를 사용하면 네트워크 호출이 자동으로 백그라운드에서 수행되지만 결과가 반환되어도 여전히 백그라운드에 남아있기 때문에 결과값을 메인스레드에서 사용하기 위해서는 runOnUiThread 등을 사용해야 한다.
override fun onResponse(call: okhttp3.Call,
response: okhttp3.Response) {
runOnUiThread {
Toast.makeText(...).show()
}
}
결론적으로 Retrofit을 OkHttp와 같이 사용하면 다음과 같은 장점이 있다.
- 어노테이션 사용으로 코드의 가독성이 좋고, 직관적인 설계가 가능하다.
- JSON 형식의 통신 결과값을 직접 변환할 필요가 없다.
- 결과값을 메인스레드에서 바로 사용할 수 있다.
Retrofit은 Http 통신을 할 때 OkHttp에 의존하고 있고, OkHttp는 OkHttp Client에 네트워크 Intercepter를 설정해 API가 통신되는 모든 활동을 모니터링 할 수 있으며 서버 통신 시간 조절이 가능하다. 따라서 최고의 성능을 내기 위해서는 Retrofit과 OkHttp를 함께 사용하는 것이 좋다.
출처