Skip to content

안드로이드 네트워크 연결

HR edited this page Aug 29, 2023 · 6 revisions

안드로이드 스튜디오에서의 네트워크 연결

안드로이드 스튜디오에서의 네트워크의 연결을 구현하는 방법에는 여러가지가 있다. 대표적으로 아래의 3가지의 방법이 있다. ( 그 외에도 더 있다. )

  1. Retrofit과 OkHttp
    Retrofit은 HTTP 클라이언트 라이브러리인 OkHttp와 함께 사용하여 편리한 API 호출과 네트워크 통신을 처리할 수 있다. Retrofit은 API 요청과 응답을 쉽게 모델 클래스에 매핑해주는 장점이 있다.

  2. HttpClient
    HttpClient를 사용하여 비동기적으로 네트워크 통신을 처리할 수 있다. 이 방법은 Kotlin의 표준 라이브러리를 활용하여 네트워크 요청을 보내고 응답을 처리할 수 있다.

  3. HttpURLConnection
    안드로이드에서 기본적으로 제공하는 HttpURLConnection 클래스를 사용하여 네트워크 통신을 구현할 수 있다.

이번 프로젝트에서는 그 중 Retrofit을 사용해서 네트워크 통신을 해보고자 한다.

Retrofit 사용법

우선 retrofit을 사용하기 위해선 공식 문서에 기입된 사용방법에 대해서 알고 있어야한다. 아래는 Retrofit과 OkHttp를 사용하여 안드로이드 스튜디오에서 네트워크 연결을 설정하는 예이다.

  1. 의존성 추가
    build.gradle 파일에 Retrofit, OkHttp 및 코루틴의 의존성을 추가한다.
dependencies {
    // ...
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:okhttp:4.9.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
}
  1. API 서비스 인터페이스 정의

네트워크 호출을 위한 API 서비스 인터페이스를 정의한다.
GET, POST, PUT, DELETE를 사용해 api 통신을 주로 하는데, 각각의 어노테이션의 역할은 아래와 같다.

  1. @GET
    @GET 어노테이션은 GET HTTP 요청을 나타낸다. 이를 사용하여 서버로부터 데이터를 가져올 때 사용한다. @GET 어노테이션 다음에 경로를 지정하여 해당 엔드포인트에 대한 요청을 수행할 수 있다.
interface ApiService {
    @GET("endpoint")
    suspend fun fetchData(): dataDTO
}

2) @POST
@POST 어노테이션은 POST HTTP 요청을 나타낸다. 이를 사용하여 서버로 데이터를 보낼 때 사용한다. 보통 폼 데이터나 JSON 데이터를 서버로 전송할 때 사용된다.
interface ApiService {
    @POST("submit")
    suspend fun submitData(@Body requestBody: RequestBody): dataDTO
}

3) @PUT
@PUT 어노테이션은 PUT HTTP 요청을 나타낸다. 이를 사용하여 서버에 데이터를 업데이트하거나 리소스를 생성하는 데 사용될 수 있다.
interface ApiService {
    @PUT("update/{id}")
    suspend fun updateData(@Path("id") itemId: String, @Body updatedData: YourData): dataDTO
}

4) @DELETE
@DELETE 어노테이션은 DELETE HTTP 요청을 나타낸다. 이를 사용하여 서버에서 리소스를 삭제할 때 사용된다.
interface ApiService {
    @DELETE("delete/{id}")
    suspend fun deleteData(@Path("id") itemId: String): dataDTO
}

***
쿼리(Query)와 경로(Path) 파라미터를 사용하는 방법도 있다.

  1. 쿼리(Query)
    API 엔드포인트에 쿼리 파라미터를 전달하려면 @Query 어노테이션을 사용한다.
    예를 들어, 검색어를 전달하는 경우 아래와 같이 작성한다.
interface ApiService {
    @GET("search")
    suspend fun searchItems(@Query("query") searchTerm: String): itemDTO
}

이 때, 호출할 때 `searchItems("keyword")`와 같이 검색어를 전달하면 URL에 `?query=keyword` 형태로 쿼리 파라미터가 추가된다.

  1. 경로(Path)
    경로 파라미터를 사용하려면 @Path 어노테이션을 사용한다. 예를 들어, 리소스의 ID를 경로 파라미터로 전달하는 경우에는 아래와 같이 작성한다.
interface ApiService {
    @GET("items/{id}")
    suspend fun getItem(@Path("id") itemId: String): itemDTO
}

호출할 때 `getItem("item_id")`와 같이 아이템 ID를 전달하면 URL에 `items/item_id` 형태로 경로 파라미터가 추가된다.
본 프로젝트에서는 GET, POST, Query, Path를 사용한다.

  1. 네트워크 설정 및 호출
    액티비티 혹은 프래그먼트에서 네트워크 설정 및 API 호출 부분을 처리한다.
    코루틴으로도 호출할 수 있고, enqueue로도 호출할 수 있다. 아래의 예는 코루틴으로 처리하는 방법이다.
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {

    private val retrofit = Retrofit.Builder()
        .baseUrl("BASE_URL")
        .addConverterFactory(GsonConverterFactory.create())
        .build()

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 코루틴 스코프 내에서 API 호출 실행
        runBlocking {
            launch(Dispatchers.IO) {
                try {
                    val response = apiService.fetchData()
                    if (response.isSuccessful) {
                        val data = response.body()
                        // 데이터 처리
                    } else {
                        // 실패 처리
                    }
                } catch (e: Exception) {
                    // 예외 처리
                }
            }
        }
    }
}

프로젝트에서의 사용

실제 프로젝트에서 사용했던 방법


문제가 생긴 페이지다. 우리 프로젝트에서는 온보딩 페이지를 제외한 모든 페이지에서 api의 요청을 받아와야하는데, 그중 제일 처음 맞이하는 페이지에서 한가지 문제가 생겼다.

우선 해당 페이지의 네트워크 통신 방식에 대해 설명하겠다.
“트림 선택 페이지" 에서 사용되는 API는 아래의 3가지다.
  • 차종
  • 카테고리
  • 디폴트 옵션

상단의 2개의 API는 문제없이 받아올 수 있으나, 3번째의 디폴트 옵션에 대해서는 문제가 있었다.

/trims/{id}/default-components?categoryId={categoryId}&page={page}&size={size}

위의 endpoint는 아래의 정보를 받아와야한다.

  • {id} : 트림의 id.
  • {categoryId} : 옵션들에 대한 모든 카테고리 정보.(디폴트 옵션과 선택옵션 전부가 다 있음) 트림의 id는 차종 api를 받아와야만 알 수 있고, 카테고리의 정보는 카테고리 api를 받아와야지만 알 수 있다.
    한마디로, 미리 지정해주지 않는 경우에 init을 통해서 3가지의 api를 한번에 받아올 수 없다는 뜻이다.

그래서 첫번째 방법을 생각해냈다.

트림과 카테고리에 대한 api를 받아온 후, 트림 선택 카드를 누를 시 해당 트림에 대한 id와 카테고리를 받아와 한번에 모든 api를 불러오자!
“api가 분리되어있기때문에 한번에 데이터 처리가 불가능하다고 생각했다. 그렇다면, 해당 하는 모든 데이터를 다 받아온 후 처리하는건 어떨까?”

현재 구상한 api 구조

  • 트림선택 프래그먼트로 넘어갈 때 init을 통해 트림에 대한 데이터와 카테고리에 대한 api 데이터를 받아온다.
  • 맨 위의 트림 선택 카드 클릭시, 해당 트림의 id를 받아오며 그 아이디에 대한 카테고리데이터들을 한번에 받아온다.

크게 문제가 될 일은 없을 것이라고 생각했지만, 문제가 발생했다. 트림 선택 카드를 클릭해야지만 해당 api를 불러오는 코루틴이 실행이 되는 것이었다. 따라서 원래라면 나와야하는 전체 탭에 해당하는 데이터가 들어오지 않았다. 카테고리 데이터가 십여개가 되는데, 부르는 타이밍이 틀렸다. 서브 탭에대한 데이터를 받아오려고 했으나, 카드 클릭시 해당 정보들이 들어오는 코루틴이 실행되었고, 그것이 곧바로 화면에 나타나지 못했다.
다만, 카테고리 탭 클릭시 필터링은 문제없이 작동했다. 코루틴이 진행되고 끝이 나긴 했으나, 화면에 적용될 수 없었던 이유는 api를 받아왔다는 것을 화면에 나타낼 수 있는 방법이 없었기 때문이었다.


그래서 이번에는 두번째 방법을 생각해냈다.

탭 버튼을 누를때마다 해당 데이터를 불러오자. 탭을 누를때마다 해당 api를 받아온다면 조금은 속도도 빨라지고 ui에 나타낼 수 있지 않을까 생각했다.
프론트 엔드의 경우에도 탭을 누를때마다 해당 데이터를 불러오는 식으로 데이터를 처리한다고 했다. 그와 비슷하게 코드를 작성했으나 여전히 문제는 유지되었다.

결국은 동일한 코드를 작성한것이나 마찬가지가 되었으나 이 두 방법에서의 차이점을 알 수 있었다.

첫 번째 방법:

  • 한 번에 모든 데이터를 불러오는 접근 방식은 초기 데이터 로딩에 시간이 걸릴 수 있다.
  • 모든 데이터를 받아온 후 처리하므로, 로딩 시간이 길어질 수 있다.
  • 디스크 용량과 초기 네트워크 부하가 크게 증가할 수 있다.

두 번째 방법:

  • 필요한 데이터를 필요할 때마다 불러오기 때문에 초기 로딩 시간이 단축될 수 있다.
  • 탭을 누를 때마다 필요한 데이터를 불러와서 사용자 경험을 향상시킬 수 있다.
  • 그러나, 여러 번의 API 요청을 처리해야 하므로, 불필요한 네트워크 부하가 발생할 수 있다.

후자로 수정한것이 그래도 나은 편이었다.

프론트는 됐고, 안드로이드는 안되었던 이유가 무엇인지 고민해봤다. 생각을 해보니 프론트엔드는 모든 트림에 대한 api를 받아온 후 한번에 화면에 보여야만 했고, 안드로이드는 모든 트림에 대한 api를 받아온 후 각각의 트림에 대한 데이터를 따로 화면에 보여야했다.

우선, 제일 중요한 페이지는 트림들 중 “르블랑”에 해당하는 정보이기 때문에 프래그먼트를 처음 init 할 때,

/trims/2/default-components?categoryId=0&page=1&size=5

트림의 id를 르블랑으로 고정하고, 전체를 받는 카테고리 아이디인 0을 지정해준 후, 5개만 우선적으로 받아오는 식으로 처리했다. 그 후에는 탭을 눌렀을때 즉각적으로 api를 받아와 다시금 화면에 채우는 식으로 코드를 수정했다.

Clone this wiki locally