초창기 안드로이드에서 네트워크 통신을 위해서 HttpURLConnection 혹은 Apache HTTP Client 를 사용했습니다. 하지만 위 두가지 방법 모두 단점이 많았기에 Deprecated되었고 OkHttp, Volley, OkHttp 라이브러리를 기반으로 구현된 Retrofit 등의 Third-Party 라이브러리가 사용하게 되었습니다.
Retrofit은 서버와 HTTP 통신을 해서 주고받은 데이터를 앱에서 특정 형태로 활용 수 있게 하는 라이브러리 입니다. 즉, Retrofit은 REST API을 구현한 상태입니다. 그래서 간단하게 GET, POST, PUT, DELETED 등을 전달하면 서버에서 처리 후 xml, json, text, rss 등으로 응답을 제공받을 수 있습니다.
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
외부 라이브러리를 사용하기 위해 gradle에 dependencies implementation을 해줍니다
1. Endpoint를 생성
interface MapApiService {
@GET(Url.GET_TMAP_REVERSE_GEO_CODE)
suspend fun getReverseGeoCode(
@Header("appKey") appKey: String = Key.TMAP_API,
@Query("version") version: Int = 1,
@Query("callback") callback: String? = null,
@Query("lat") lat: Double,
@Query("lon") lon: Double,
@Query("coordType") coordType: String? = null,
@Query("addressType") addressType: String? = null
): Response<AddressInfoResponse>
}
위와 같이 Endpoint interface를 만들어서 API에 접근해서 통신할 수 있게 해줍니다. Endpoint에서 URL에 대한 결과값을 반환 받을 메서드는 GET으로 하였습니다.
@GET은 HTTP 통신 방식으로 Parameter들이 URL에 추가되는 GET 방식으로 하겠다는 의미이고 @Header는 Request Header에 TMAP_API의 Key값을 추가한다는 의미입니다. @Query는 Request시 담을 Parameter들이며 아래와 같이 URL이 구성되며 Request를 보내게 됩니다.
https://apis.openapi.sk.com/tmap/geo/reversegeocoding?version={version}&lat={lat}&lon={lon}&coordType={coordType}&addressType={addressType}&callback={callback}&appKey={appKey}
Retrofit은 interface을 정의하고, 이 interface class을 Retrofit에 초기화를 하는 과정을 거치면, HTTP 통신을 할 준비가 완료됩니다.
2. Retrofit과 OkHttpClient
object RetrofitUtil {
private var instance: Retrofit? = null
val mapApiService: MapApiService by lazy {
getRetrofit().create(MapApiService::class.java)
} //Retrofit을 초기화합니다.
private fun getRetrofit(): Retrofit {
if(instance == null) {
Retrofit.Builder() //객체를 생성해 줍니다.
.baseUrl(Url.TMAP_URL) //통신할 서버 주소를 설정합니다.
.addConverterFactory(GsonConverterFactory.create())
.client(buildOkHttpClient())
.build()
}
return instance!!
}
private fun buildOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.NONE
}
return OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build()
}
}
RetrofitUtil은 기존의 객체를 재활용 하기 위해서 싱글턴으로 구현하였습니다. 그리고 Response와 Request에 대한 정보를 보기 위해서 OkHttpClient를 사용하여 val interceptor = HttpLoggingInterceptor() 객체를 생성해서 Retrofit에 추가해 줍니다.
GsonConverterFactory는 Retrofit에서 사용되는 ConverterFactory 중 하나로서, JSON 형식의 데이터를 자바 객체로 변환하거나 자바 객체를 JSON 형식으로 변환할 수 있도록 지원하는 역할을 합니다. Gson 라이브러리를 사용하여 JSON 형식의 데이터와 자바 객체간의 변환을 처리할 수 있습니다. GsonConverterFactory를 사용하면 Retrofit에서 JSON 형식의 응답 데이터를 쉽게 처리할 수 있으며, 서버와 클라이언트 간의 데이터 통신에 유용하게 사용됩니다. ReverseGeo의 Response JSON은 아래와 같습니다.
{"addressInfo": {
"fullAddress": "서울시 마포구 상수동",
"addressKey": "3004545827",
"roadAddressKey": "4001807949",
"addressType": "A10",
"city_do": "서울시",
"gu_gun": "마포구",
"eup_myun": "",
"adminDong": "망원2동",
"adminDongCode": "1150056000",
"legalDong": "상수1동",
"legalDongCode": "1132010500",
"ri": "죽전리",
"roadName": "강남대로",
"buildingIndex": "11-11",
"buildingName": "영진그린빌라",
"mappingDistance": 0,
"roadCode": "261104175064",
"bunji": "121-12",
"adminDongCoord":{
"lat": "37.56624167",
"lon": "126.90194167",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
},
"legalDongCoord":{
"lat": "37.56624167",
"lon": "126.90194167",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
},
"roadCoord":{
"lat": "37.56631111",
"lon": "126.90161389",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
}}
위와 같은 형식의 JSON이 AddressInfo를 담고 있는 AddressInfoResponse로 변환이 됩니다. AddressInfoResponse 내부에 존재하는 AddressInfo의 구조는 아래와 같습니다.
retrofit은 이러한 응답을 간단하게 변환할 수 있도록, 다양한 Converter을 제공하고 있습니다. 바로 addConverterFactory()로 추가하는 방법입니다.
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
gradle에 dependencies implementation을 해줍니다 앞서 미리 했기 때문에 이미 하신분들은 안해도 됩니다.
.addConverterFactory(GsonConverterFactory.create())//데이터 파싱을 설정합니다.
네트워크 통신을 위해 JSON 형식의 데이터를 주고받을 때 Kotlin에서는 JSON 데이터를 쉽게 처리할 수 있도록 data class를 사용합니다. 이 data class는 @SerializedName과 @Expose 어노테이션을 함께 사용하여 JSON 데이터의 필드 이름과 해당 필드를 직렬화할지 여부를 지정할 수 있습니다.
@SerializedName 어노테이션은 Kotlin 클래스의 필드 이름과 JSON 데이터의 필드 이름을 매핑시킵니다. JSON 데이터의 필드 이름과 Kotlin 클래스의 필드 이름이 다른 경우 이 어노테이션을 사용하여 매핑시켜줄 수 있습니다. 예를 들어, 다음과 같은 JSON 데이터가 있다고 가정해 봅시다.
{
"first_name": "John",
"last_name": "Doe",
"age": 30
}
이 JSON 데이터를 처리하기 위해 다음과 같은 data class를 작성할 수 있습니다.
data class User(
@SerializedName("first_name") val firstName: String,
@SerializedName("last_name") val lastName: String,
val age: Int
)
위의 코드에서 @SerializedName 어노테이션을 사용하여 firstName 필드와 lastName 필드가 JSON 데이터의 first_name과 last_name 필드와 매핑되도록 설정해 주었습니다.
@Expose 어노테이션은 해당 필드를 직렬화할지 여부를 지정합니다. 기본적으로 Gson 라이브러리에서는 모든 필드를 직렬화합니다. 따라서 @Expose 어노테이션을 사용하지 않아도 됩니다. 하지만 특정 필드를 직렬화하지 않고 무시하려는 경우에는 이 어노테이션을 사용할 수 있습니다.
예를 들어, 다음과 같은 Kotlin data class가 있다고 가정해 봅시다.
data class User(
@SerializedName("first_name") val firstName: String,
@Expose val lastName: String,
@Expose(serialize = false) val age: Int
)
위의 코드에서 lastName 필드와 age 필드에 @Expose 어노테이션을 사용하여 각각 직렬화할지 여부를 지정해 주었습니다. lastName 필드는 기본적으로 직렬화하도록 설정되어 있지만, age 필드는 serialize = false로 설정하여 직렬화하지 않도록 설정해 주었습니다.
@Expose 어노테이션은 Gson 라이브러리를 사용할 때 객체를 JSON 형식으로 직렬화(serialization)할 때 필드를 제어하는 기능을 제공합니다.
@Expose 어노테이션의 기본값은 serialize와 deserialize 모두 true 입니다. serialize=false로 설정하면 객체를 JSON 형식으로 직렬화할 때 해당 필드는 제외됩니다. deserialize=false로 설정하면 JSON을 객체로 역직렬화할 때 해당 필드는 무시됩니다.
아래는 Kotlin으로 @Expose 어노테이션을 사용한 예제입니다.
data class User(
@Expose val name: String,
@Expose(serialize = false) val age: Int,
@Expose(deserialize = false) val email: String
)
val user = User("John", 25, "john@example.com")
val gson = Gson()
val json = gson.toJson(user)
println(json)
// {"name":"John"}
val json2 = "{\"name\":\"Jane\",\"age\":30,\"email\":\"jane@example.com\"}"
val user2 = gson.fromJson(json2, User::class.java)
println(user2)
// User(name=Jane, age=0, email=null)
위 예제에서 age 필드는 serialize=false로 설정되어 JSON 형식으로 직렬화할 때 제외됩니다. email 필드는 deserialize=false로 설정되어 JSON을 객체로 역직렬화할 때 무시됩니다.
직렬화(serialization)는 객체를 외부 저장소에 저장하기 위해 문자열 형태로 변환하는 과정을 말합니다. 역직렬화(deserialization)는 저장소에서 문자열 형태로 저장된 객체를 다시 원래 객체 형태로 변환하는 과정을 말합니다. 예를 들어, 객체를 파일이나 네트워크 전송을 위해 문자열로 변환하고, 다시 파일에서 읽거나 네트워크로부터 수신한 문자열을 객체로 변환하는 과정이 직렬화와 역직렬화에 해당합니다.
Retrofit과 생성한 interface을 연결하였습니다. 응답받는 곳에서는 다음과 같이 처리할 수 있습니다.
Retrofit은 HTTP 요청과 응답을 처리하기 위해 OkHttp를 사용하며, OkHttp는 내부적으로 비동기적인 방식으로 Thread를 처리합니다. Retrofit은 이를 활용하여 비동기적인 HTTP 요청을 처리할 수 있습니다. 그리고 이 후 비동기든/동기든 추후 선택을해서 데이터를 넘겨줍니다.
1. 동기적인 예제 코드
val client = OkHttpClient()
val request = Request.Builder()
.url("주소")
.build()
try {
val response = client.newCall(request).execute()
if (response.isSuccessful()) {
val responseBody = response.body()?.string()
// responseBody 처리
} else {
// 실패 처리
}
} catch (e: IOException) {
// 예외 처리
}
2. 비동기적인 예제 코드
val client = OkHttpClient()
val request = Request.Builder()
.url("주소")
.build()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
// 실패 처리
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
val responseBody = response.body()?.string()
// responseBody 처리
} else {
// 실패 처리
}
}
})
비동기적인 코드에서는 enqueue() 메소드를 사용하여 콜백 함수를 등록합니다. 콜백 함수의 onFailure() 메소드는 요청이 실패한 경우에 호출되며, onResponse() 메서드에서는 HTTP 상태 코드가 200일 때 응답 데이터를 처리합니다. 이때 response.body()를 호출하면 List 객체를 얻을 수 있습니다. 콜백 함수를 사용하여 응답 결과를 비동기적인 방식으로 요청을 처리하면 UI 스레드가 차단되지 않으므로 앱의 성능이 향상됩니다.
Retrofit은 기본적으로 enqueue()를 사용하여 비동기 네트워크 요청을 처리할 때 작업 스레드를 사용합니다. enqueue()를 사용하면 Retrofit이 자동으로 작업 스레드를 관리하고, 네트워크 요청이 완료되면 결과를 메인 스레드로 전달합니다.
그러나 execute()를 사용하면 동기적으로 네트워크 요청이 처리됩니다. 이 경우 execute()를 호출한 스레드에서 네트워크 요청이 처리되며, 해당 스레드가 요청이 완료될 때까지 기다립니다. 따라서 메인 스레드에서 execute()를 사용하면 사용자 인터페이스가 블로킹됩니다.
execute()를 사용하려면 작업 스레드를 직접 생성하고 해당 스레드에서 execute()를 호출해야 합니다. 이렇게 하면 메인 스레드가 블로킹되지 않고 작업 스레드에서 네트워크 요청이 처리됩니다. 이 경우, Retrofit 자체는 작업 스레드를 관리하지 않으며 개발자가 직접 스레드를 관리해야 합니다.
요약하면, Retrofit은 enqueue()를 사용할 때 작업 스레드를 자동으로 관리하고 사용합니다. 그러나 execute()를 사용하면 개발자가 직접 작업 스레드를 생성하고 관리해야 합니다.
이렇게 Retrofit을 사용하여 REST API를 호출하고 응답을 처리하는 방법에 대해 살펴보았습니다. Retrofit은 간편하게 REST API를 호출하고 응답을 처리할 수 있도록 도와주는 라이브러리입니다.
https://square.github.io/retrofit/
https://square.github.io/okhttp/
https://thdev.tech/androiddev/2016/11/13/Android-Retrofit-Intro/