본문 바로가기

안드로이드/Network

[안드로이드] 서버 통신 Retrofit2

Retrofit은 OkHttp 기반으로 만들어진 Http 통신 라이브러리다.

 

OkHttp 란?

OkHttp는 효율적인 Http 클라이언트이다.

  • HTTP/2 지원을 통해 동일한 호스트에 대한 모든 요청이 소켓을 공유할 수 있다.
  • 연결 풀링은 요청 대기 시간을 줄인다.(HTTP/2를 사용할 수 없는 경우)
  • Transparent GZIP는 다운로드 크기를 줄인다.
  • 응답 캐싱은 반복적인 요청으로부터 네트워크를 완전히 피한다.

자세한 내용은 아래 링크를 참조 바랍니다.

https://square.github.io/okhttp/

 

Overview - OkHttp

OkHttp HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth. OkHttp is an HTTP client that’s efficient by default: HTTP/2 support allows all requests to

square.github.io

 

 

 

Retrofit2의 장점

  • 빠른 성능
  • 단순한 구현
  • 가독성

 

사용 방법

인터넷 권한 설정

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.android">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        ...
    </application>

</manifest>

 

인터넷 권한 설정: <uses-permission android:name="android.permission.INTERNET" />

서버와의 통신은 앱 외부와의 네트워크 통신이므로 인터넷 권한이 필요하다.

 

Gradle 의존성

build.gradle

 

dependencies {
    // Retrofit 라이브러리
    implementation 'com.squareup.retrofit2:retrofit:2.9.0' 

    // Gson 변환기 라이브러리
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'    

    // Scalars 변환기 라이브러리
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
}

 

Retrofit 객체 생성

object NetworkManager {
    fun getRetrofitInstance(): Retrofit{
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("http://localhost:8000")
            .build()
    }
    
    //RxJava
    fun getRetrofitInstance(): Retrofit{
        return Retrofit.Builder()
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl(url)
            .build()
    }
}

 

자주 전송할 헤더가 있다면

 

예를 들어 Token의 경우 서버에 데이터를 요청할 때마다 Header에 담아 인증 절차를 거쳐야 하므로 특별한 경우를 제외하고 항상 전송해줘야한다.

object NetworkManager {
    fun getRetrofitInstance(): Retrofit{
        return Retrofit.Builder()
            .client(provideOkhttpClient(AppInterceptor()))
            .addConverterFactory(GsonConverterFactory.create())
            .baseUrl("http://localhost:8000")
            .build()
    }

    private fun provideOkhttpClient(interceptor: AppInterceptor): OkHttpClient =
        OkHttpClient.Builder().run {
            addInterceptor(interceptor)
            build()
        }

    class AppInterceptor() : Interceptor {
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain)
                : Response = with(chain) {
            val token = MyApplication.prefs.getString("token", "")
            val newRequest = request().newBuilder()
                .addHeader("Authorization", "jwt $token")
                .build()

            proceed(newRequest)
        }
    }

}

 

이렇게 하면 Interface에 Header 어노테이션으로 토큰을 따로 넣어주지 않아도 토큰이 헤더에 담겨 서버로 전송된다.

 

Interface 생성

HTTP 메서드 어노테이션

  • @GET
  • @POST
  • @PUT
  • @DELETE
interface ApiService {
	
    @GET("/accounts/mydata/")
    fun getMyData() : Call<ArrayList<UserData>>

    @FormUrlEncoded
    @POST("/accounts/signup/")
    fun postSignup(@Field("username") username:String,
                   @Field("password") password:String): Call<signupDTO>

	
    @PUT("/update/urls/")
    fun update(): Call<Any>

	
    @DELETE("/api/posts/{post_id}/like/")
    fun unLikePost(@Path("post_id") post_id : Int) : Call<Any>
}

 

@Header

@GET("/accounts/mydata/")
fun getMyData(@Header("Authorization") token: String) : Call<ArrayList<UserData>>

 

보통 Header에 토큰을 담아 인증할 때 사용한다.

 

@Path

Url 사이에 값을 넣고 싶을 때

@DELETE("/api/posts/{post_id}/like/")
fun unLikePost(@Path("post_id") post_id : Int) : Call<Any>

 

post_id 가 1이라면 Url은 다음과 같다.

"http://localhost:8000/api/posts/1/like/"

 

@Field

Requst body에 데이터를 담아 보낼 때 사용한다.

@FormUrlEncoded
@POST("/accounts/signup/")
fun postSignup(@Field("username") username:String,
               @Field("password") password:String): Single<signupDTO>

 

@FormUrlEncoded : 데이터를 URL 인코딩 형태로 만들어 전송할 때

사용한다.- @Field 의 데이터를 형식에 맞춰 requset body에 담아 보내야하기 때문에 형식에 맞추는 인코딩 작업을 해주어야한다.

 

@Query

Url에 데이터를 담아 보내며, 주로 검색할 때 많이 사용된다.

@GET("/accounts/search/user/")
fun searchUser(@Query("key") key:String?) : Call<ArrayList<UserData>>

 

key의 값이 "Kim" 이라면 Url은 다음과 같다.

"http://localhost:8000/accounts/search/user/?key=Kim"

 

@QueryMap

Query에 담을 데이터가 많을 때 사용한다.

@GET("/accounts/search/user/")
fun searchUser(@QueryMap values: Map<String,String>) : Call<ArrayList<UserData>>

 

@Body

데이터 객체를 전송할 때 사용한다.

data class LoginRequest(
    val username : String,
    val password : String
)


@POST("/accounts/signin")
    fun requestLogin(@Body loginRequest: LoginRequest) : Call<LoginResponse>

 

DTO 생성

import com.google.gson.annotations.SerializedName

data class Comment(
    @SerializedName("id") val id: Int,
    @SerializedName("author") val author: User,
    @SerializedName("message") val message: String,
    @SerializedName("created_at") val created_at: String
)
{
    data class User(
        @SerializedName("id") val id: Int,
        @SerializedName("username") val username: String,
        @SerializedName("name") val name: String,
        @SerializedName("avatar_url") val avatar_url: String
    )
}

 

Json으로 직렬화되어 있는 데이터를 @SerializedName으로 역직렬화시켜 객체로 만들어준다.

 

+ 안드로이드 스튜디오에는 Json을 입력하면 Data Class로 변환시켜주는 플러그인이 있다. (Json To Kotlin Class)

 

MainActivity

Basic

private val retrofit = NetworkManager.getRetrofitInstance().create(ApiService::class.java)

retrofit.postSignup("username", "password")
            .enqueue(object : Callback<T>{
            	override fun onResponse(call: Call<T>, response: Response<T>) {
                        //todo 성공처리
                }
                
                override fun onFailure(call: Call<T>, t: Throwable) {
                        //todo 실패처리
                        Log.d(TAG,t.toString())
                }
            })

 

RxJava

private val retrofit = NetworkManager.getRetrofitInstance().create(ApiService::class.java)

retrofit.postSignup("username", "password")
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                {
                    //응답 성공
                    val data = it
                },
                {
                    //응답 실패
                }
            )

 

Coroutine

coroutine =
            try {
                CoroutineScope(Dispatchers.IO).launch {
                    val response = retrofit.postSignup("username", "password")
                    withContext(Dispatchers.Main){
                        if(response.isSuccessful){
                            //TODO
                        }
                    }
                }
            }
            catch (e:Exception){ Job() }

 

 

파일 전송 방법

@Multipart
@POST("/api/posts/")
fun writePost(@Part photo : MultipartBody.Part,
              @Part("caption") caption : RequestBody?,
              @Part("location") location : RequestBody):Call<Any>

 

val photofile = File(경로)
val photo = RequestBody.create("image/jpeg".toMediaTypeOrNull(), photofile)
val filePart : MultipartBody.Part = MultipartBody.Part.createFormData("photo","photo.jpg", photo)
val caption = RequestBody.create(
    "text/plain".toMediaTypeOrNull(),
    text
)
val location = RequestBody.create(
    "text/plain".toMediaTypeOrNull(),
   	text
)

retrofit.writePost(filePart, caption, location)

'안드로이드 > Network' 카테고리의 다른 글

[서버 통신] - Http 프로토콜  (0) 2023.11.03