안드로이드에서 서버와의 통신을 위해 사용하는 라이브러리는 여러가지가 있습니다. 이 글에서 소개할 Retrofit은 물론이고, Volly와 OkHttp도 있습니다. 물론, 이번에 소개할 Retrofit은 OkHttp를 래핑한 통신 라이브러리라, OkHttp와 비교되지는 않고 Volly의 대척점에 있는 라이브러리로 볼 수 있습니다.
현재는 Volly 보다 Retrofit의 점유율이 더 높아 보이는 추세이기도 하고, 가독성이나(호불호가 갈리긴 하지만) 속도면에서 Retrofit의 손을 더 들어주는 모양새라 시간이 지나도 Retrofit의 인기가 줄어들 것 같지는 않습니다.
Retrofit을 사용하기 위해 작성해야 하는 코드는 크게 3가지입니다. 먼저 Retrofit 빌더를 담을 singleton class(이는 권장사항이며 무조건! 지켜야 하는 공식은 아닙니다.)와 통신을 도와줄 API interface, 마지막으로는 통신 response와 request에 사용할 DTO(이 역시도 권장사항이며 무조건 DTO를 생성해서 통신을 해야할 필요는 없습니다.) 입니다.
물론 그것들을 작성하기 전에 필수적으로 해야하는 일은 ,Manifest에 Internet 권한을 열어주는 일입니다.
<uses-permission android:name="android.permission.INTERNET" />
당연하게도 통신을 위한 라이브러리 이기 때문에, 인터넷 권한이 없는 경우에 제대로 된 동작이 되지 않습니다!
<application
...
android:usesCleartextTraffic="true"
>
</application>
또한 권장하지는 않지만, http 통신을 하게 되는 경우, 문제가 생기지 않도록 설정을 걸어줄 수도 있습니다.(하지만 보안 상의 문제로 모든 사이트들이 https로 넘어가는 추세이기 때문에 통신하는 서버가 http로 이루어져 있지 않다면, usesCleartextTraffic 설정은 false로 유지하는 것을 권장합니다.)
def retrofitVersion = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
//implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
그 다음은 Build.gradle에 retrofit을 Implementation 해서 실 코드에서 사용 가능하도록 합니다. 기본적인 것은 retrofit만 implementation해도 충분하지만, 기본적으로는 data class의 사용을 위해서 converter-gson까지 같이합니다.(다른 옵션들도 존재합니다.) 그러고 나면 이제 Retrofit을 사용할 준비가 완료됩니다.
//통신을 위한 retorfit object
object TestRetroUtil {
val api : TestNetWorkAPI by lazy { apiInit() }
private var testRetrofit : Retrofit? = null
private const val TEST_ADDR = "http://192.168.10.226:8080"
private fun apiInit() : TestNetWorkAPI {
val testRetrofit = testRetrofit
val retrofit = testRetrofit ?: run {
Retrofit.Builder()
.baseUrl("$TEST_ADDR/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.also { this.testRetrofit = it }
}
return retrofit.create(TestNetWorkAPI::class.java)
}
}
//API 인터페이스
interface TestNetWorkAPI {
@GET("test")
fun test(@Body data : GetBody): Call<JsonObject>
}
//서버와의 통신에 사용할 파라미터 data class
data class GetBody(
@SerializedName("name") val name : String,
@SerializedName("phone_number") val phoneNo : String,
@SerializedName("is_management") val management : Boolean
)
Retrofit을 사용하기 위한 가장 기본적인 object, data class, API입니다. 여기서 object의 경우 여러가지 커스터마이징을 통해 통신과정을 더 매끄럽고 간편하게 해줄 수 있는 기능들이 존재합난니다.
//통신을 위한 retorfit object
object TestRetroUtil {
val api : TestNetWorkAPI by lazy { apiInit() }
private var testRetrofit : Retrofit? = null
private const val TEST_ADDR = "http://서버 주소:8080"
val httpClientBuilder = OkHttpClient().newBuilder()
private fun apiInit() : TestNetWorkAPI {
val testRetrofit = testRetrofit
val retrofit = testRetrofit ?: run {
Retrofit.Builder()
.baseUrl("$TEST_ADDR/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.also { this.testRetrofit = it }
}
httpClientBuilder.addInterceptor {
val builder = it.request().newBuilder()
// builder.addHeader("Accept", "application/json")
builder.addHeader("Content-Type", "application/x-www-form-urlencoded")
builder.build()
it.proceed(request)
}
return retrofit.create(TestNetWorkAPI::class.java)
}
}
위와 같이 OkHttpClient()를 이용해서 basic api 전체에 헤더를 추가하거나 하는 기능 역시 가능합니다. 이 부분은 서버와의 통신에 따라 크게 추가되거나 제거되는 부분이 많기 때문에, 실제 개발시에는 서버 개발자와 어떤 방식으로 통신을 할 지에 대한 이야기를 통해 이해를 충분히 한 후, 코드 작성을 진행하는 편이 효율적입니다.
//API 인터페이스
interface TestNetWorkAPI {
@GET("test")
fun test(@Body data : GetBody): Call<JsonObject>
}
그 다음은 API 인터페이스인데요. @GET과 같은 어노테이션 안의 value 값을 통해 api 주소를 참조하게 됩니다. 위의 base url이 'http://서버 주소:8080/' 로 되어 있었으므로 위의 메소드를 실행시키면 'http://서버 주소:8080/text'라는 api를 통하게 됩니다.(물론 get 방식의 호출이니 뒤에 파라미터가 ?~ 부터 붙는 것 까지 포함됩니다.)
기본적으로 통신을 위한 HTTP 요청 메소드 GET, POST, PUT, DELETE 네 가지 모두 사용 가능합니다. 이는 각각 @GET, @POST, @PUT, @DELETE를 통해서 호출할 수 있습니다.
그리고 메소드 위로 붙는 어노테이션에는 @FormUrlEncoded, @Multipart 등이 존재하고, 메소드의 파라미터에 붙는 어노테이션에는 @Path, @Header, @Body, @Query, @QueryMap, @Field, @FieldMap, @Part 등이 있습니다.
먼저 FormUrlEncoded는, Field 형식의 어노테이션을 통해 파라미터를 넘길 때 필수적으로 작성해줘야 하는 것으로 파라미터의 전송 형태를 form-urlencoded 형태로 보내게 된다는 것을 의미합니다.
Multipart 어노테이션은 서버에 이미지를 전송하기 위해 사용되는 어노테이션으로 MultipartBody.Part나 RequestBody와 함께 사용됩니다. 이 방식으로 데이터를 전송할 때, 이미지가 압축되어 있지 않은 경우 전송이 실패할 가능성도 있고, 리스트로 전송하는 경우에도 고려해야할 것들이 있는 등, 이미지 전송은 생각보다 손이 많이 가는 것을 알 수 있게 됩니다.
Path는 파라미터가 아닌 url에 가변적인 값이 들어가야 하는 경우(예를 들어 test/이후 userId 값이 들어가는 등의)에 @Path 어노테이션을 변수에 추가해 url로 읽는 것을 도와줍니다.
Header는 특정 API 주소의 헤더값이 다른 API와 다르게 배정되어야 할 때, 변수명을 통해 사용합니다. 아래 코드와 같이 예제를 통해 사용할 수 있습니다. 이 헤더에 들어가는 값도 통신 규약을 어기지 않는 선에서 뭐든 가능합니다.
//API 인터페이스
interface TestNetWorkAPI {
@GET("test")
fun test(
@Header("X-header") headerX: Int = 1,
@Body data : GetBody
): Call<JsonObject>
}
Body의 경우에는 data class를 이용해 변수명을 키 값으로(SerializedName이 부착된 경우 SerializedName을 키 값으로) 그 변수의 값을 value로 매칭해 값을 넘겨주도록 하는 어노테이션입니다. 이때, SerializedName을 부착하지 않는 경우 자동으로 변수명을 키 값으로 사용하는데, 난독화 과정을 거치게 되면 난독화된 변수명이 서버로 전달되게 되니 주의해야합니다.(사실 생각해보면 당연한 이야긴데 그걸 간과해서 테스트용 서버를 하나 새로 만들어서 변수를 넘겨보고 난 후 깨달아서 이 글을 작성중입니다.)
Query는 키 값을 value로 받아 변수 안의 값을 전달해주는 어노테이션입니다. 이를 여러개로 묶어서 HashMap<String, Any> 변수 타입으로 표현해서 사용하면 QueryMap으로 사용할 수 있습니다. Query로 넘긴 변수는 url 상에서 확인이 가능한 형태로 작성됩니다.
Field와 FieldMap은 Query와 유사하나, FormUrlEncoded을 사용해 url 상에서 파라미터를 확인할 수 없는 형태로 작성됩니다.
@Multipart
@POST("/test")
fun writeReview(
@Part id: MultipartBody.Part,
@Part file: MultipartBody.Part
): Call<JsonObject>
Part의 경우에는 Multpart 어노테이션과 함께 쓰이며, 주로 Mime 타입의 데이터를 보낼 때 사용되는데, 주로 이미지 파일을 업로드할 때 같이 사용합니다.
레트로핏의 기본적인 키워드들을 기록해두었습니다. 시간이 난다면 하나하나 예제를 추가해보는 것도 나쁘지는 않겠다는 생각이 듭니다. 되게 오랫동안 사용하는 라이브러리인데, 어째 정리가 늦은 감이 없지않아 있지만, 꾸준히 정리해봐야 겠습니다.
요즘은 회사에서 사용중인 서드파티가 자꾸만 사라져서 점점 라이브러리에 대한 불신이 커지고 있지만, 그래도 아예 사용하지 않는 것 보다는 교체비용이 덜 드니 써야겠죠...
'안드로이드 > 코틀린' 카테고리의 다른 글
Kotlin - Object 키워드(with SingleTon) (1) | 2023.02.17 |
---|---|
Coroutine 관련 설명 글 링크 (0) | 2022.06.30 |
안드로이드 startActivityForResult의 대체 (0) | 2022.04.24 |
(android) bumptech - Glide (0) | 2022.04.18 |
안드로이드 jetpack compose - modifier (0) | 2022.04.17 |
댓글