Retrofit2

강보훈·2021년 12월 28일
0

Retrofit2란?

  • OkHTTP기반으로 Type-safe하고 REST API 통신을 위한 오픈소스 라이브러리이다.

내가 이해한 작동방식

  • Call.Factory를 상속받은 OKHTTPClient를 통해서 송, 수신을 하고 있다.

  • interface RetrofitAPI {
    
        @POST("/")
        fun getStudent(
            @Query("school_id") schoolID: Int,
            @Query("grade") grade: Int,
            @Query("classroom") classroom: Int
        ): Call<Student>
    
    }
    • endpoint로 사용할 메서드들은 interface로 선언해야한다. retrofit.create()이 호출 되면 가장 먼저하는게 interface인지 검사하는 것이다.
    • Reflection을 이용해서 메서드에 어떤 Annotation이 붙어있는지 가져올 수 있다. 그래서 POST, GET등 HTTP Method가 없다면 에러를 반환한다.
    • 메서드가 제대로 작성된건지에 대한 검사와 URL작성, Annotion의 정보를 이용한 파싱은 RequestFactory.Builder()에서 실행된다.
      • @POST, @GET등 이 Annotation에 value로 준 url이 build()에서 메서드에 붙은 Annotation을 검사하는 parseMethodAnnotation()으로 넘어오고 parseHttpMethodAndPath()여기에서 해당 메서드의 HTTP method 타입과 상세 url이 builder에 저장된다.
  • Retrofit.Builder()에서 생성된 Retrofit객체에서 Proxy를 이용해서 동적으로 interface를 구현한다.

  • Retrofit객체는 내부적으로 우리가 작성한 service에 대한 메서드들을 들고 있다. 그래서 우리가 getStudent()라는 Call을 실행하면 ServiceMethod를 상속받은 구현체 중 하나가 만들어지고 실행하는 것 같은데 더 자세한 내용은 이해하지 못했다. Adapter패턴이 사용되었다는 것 말고는 그 이상으로는 이해하기가 힘들다.

Gradle

    def retrofitVersion = "2.9.0"
    implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}"
    implementation "com.squareup.retrofit2:converter-gson:${retrofitVersion}"
    implementation "com.squareup.retrofit2:converter-scalars:${retrofitVersion}"

    implementation 'com.google.code.gson:gson:2.8.6'

간단한 사용방법 예시

  • 테스트를 하는 여러 방법이 있지만 로컬에서 간단하게 httpServer를 만들고 테스트를 했다.

  • 우선 사용할 Data class를 만든다

    data class Student(
    
        @SerializedName("student_id")
        val studentID : String,
        @SerializedName("name")
        val name : String,
        @SerializedName("phoneNumber")
        val phoneNumber : String
    ) {
    
        override fun toString(): String {
            return "Student(studentID='$studentID', name='$name', phoneNumber='$phoneNumber')"
        }
    }
  • 다음에는 service를 만든다.
    이때 @Query를 사용하면 uri에 포함되서 날라오게된다 서버측에서는
    RequestURI /?grade=4&classroom=2 이렇게 받을 수 있다.
    @Body를 사용하면 uri에 보이지않고 서버측에서는 requestBody 1이렇게 받는다.

    interface RetrofitAPI {
    
        @POST("/")
        fun getStudent(
            @Query("grade") grade: Int,
            @Query("classroom") classroom: Int
        ): Call<List<Student>>
    
        @POST("/body")
        fun getStudent2(
            @Body studentID : String
        ) : Call<Student>
    
    }
  • Retrofit객체 생성 및 service interface 만들기

    val gson = GsonBuilder().setLenient().create()
    
    val retrofit = Retrofit.Builder()
        .baseUrl("http://10.0.2.2:10101/")
        .addConverterFactory(ScalarsConverterFactory.create())
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build()
    
    val requestApi = retrofit.create(RetrofitAPI::class.java)
  • 이제 생성한 interface에서 메소드를 실행시키면 끝이다.
    enqueue()를 사용해서 비동기로 실행시킬 수 있고 execute()를 사용해서 동기적으로도 실행시킬 수 있다.

    val callGetStudent: Call<List<Student>> = requestApi.getStudent(grade, classroom)
    
            callGetStudent.enqueue(object : Callback<List<Student>> {
                override fun onResponse(
                    call: Call<List<Student>>,
                    response: Response<List<Student>>
                ) {
                    if (response.isSuccessful) {
                        Log.e("Success", "Headers ${response.headers().toMultimap()}")
                        Log.e("Success", response.body()!!.toString())
                    } else { //400 에러 처리
                        Log.e("ResponseFail", response.message())
                    }
                }
    
                override fun onFailure(call: Call<List<Student>>, t: Throwable) {
                    // 500 에러 처리
                    Log.e("onFailure", t.localizedMessage)
                }
            })
  • 간단한 서버 코드
    Teriser 프로젝트를 하면서 삽질했던 경험이 굉장히 많이 도움이 된다.


class Server {

    val server = HttpServer.create(InetSocketAddress("127.0.0.1", 10101), 0)
    val gson = GsonBuilder().create()

    init {
        server.createContext("/").setHandler { httpExchange ->
            println("RequestURI ${httpExchange.requestURI}")
            println("RequestMethod ${httpExchange.requestMethod}")

            if (httpExchange.requestMethod == "POST") {
                if (httpExchange.requestURI.path.contains("body")) {
                    println("uri path ${httpExchange.requestURI.path}")
                    val stream = httpExchange.requestBody
                    val streamArr = stream.readBytes()

                    println("request body ${String(streamArr)}")
                    var data = Student("idIS100", "nameISKotlin", "PhoneNumberIsNone")
                    val stringData = gson.toJson(data)
                    sendData(stringData, httpExchange)
                } else {
                    var data = Student("idIS100", "nameISKotlin", "PhoneNumberIsNone")
                    val list = listOf<Student>(data)
                    val stringData = gson.toJson(list)
                    sendData(stringData, httpExchange)
                }
            }
        }
    }

    private fun sendData(stringData: String, httpExchange: HttpExchange) {
        val byteData = StandardCharsets.UTF_8.encode(stringData)

        val arr = ByteArray(byteData.limit())

        byteData.get(arr, 0, byteData.limit())

        httpExchange.responseHeaders.apply {
            add("Content-Type", "application/json")
            add("Content-Length", arr.size.toString())
        }

        httpExchange.sendResponseHeaders(200, arr.size.toLong())

        httpExchange.responseBody.run {
            write(arr)
            flush()
            close()
        }
    }

    fun start() = server.start()
}
profile
3년차 안드로이드 개발자입니다.

0개의 댓글