본문 바로가기
Android

[Android] 안드로이드 앱 비동기 처리 및 interceptor

by 박매트 2024. 11. 20.

네트워크 모듈인터셉터HTTP 통신을 관리하고, 요청 및 응답을 처리하는 데 중요한 역할을 합니다. 이들은 RetrofitOkHttp 라이브러리를 중심으로 설계되고 사용됩니다.


1. 네트워크 모듈(Network Module)

네트워크 모듈은 API 통신을 중앙에서 관리하는 구조로, 앱의 네트워크 요청을 효율적으로 처리하기 위한 설계입니다.

역할

  • API 클라이언트 설정 및 초기화.
  • 공통적인 네트워크 관련 설정 관리.
  • 요청 인터셉터, 응답 처리, 오류 처리 등 통합 관리.

네트워크 모듈 설계 예제

Step 1: Gradle 의존성 추가

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
 

Step 2: Retrofit 인스턴스 생성

  • 네트워크 모듈에서 Retrofit과 OkHttpClient를 설정.
object NetworkModule {

    private const val BASE_URL = "https://api.example.com/"

    // OkHttpClient 설정
    private val okHttpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .addInterceptor(LoggingInterceptor()) // 로깅 인터셉터 추가
            .addInterceptor(AuthInterceptor()) // 인증 인터셉터 추가
            .connectTimeout(30, TimeUnit.SECONDS) // 연결 타임아웃 설정
            .readTimeout(30, TimeUnit.SECONDS) // 읽기 타임아웃 설정
            .build()
    }

    // Retrofit 설정
    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient) // OkHttpClient 설정
            .addConverterFactory(GsonConverterFactory.create()) // JSON 변환
            .build()
    }

    // API Service 생성
    inline fun <reified T> createService(): T {
        return retrofit.create(T::class.java)
    }
}

 


Step 3: API 인터페이스 정의

  • 각 API의 요청/응답 정의.
interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response<List<User>>

    @POST("login")
    suspend fun login(@Body loginRequest: LoginRequest): Response<LoginResponse>
}
 

 

Step 4: 서비스 사용

val apiService = NetworkModule.createService<ApiService>()

suspend fun fetchUsers() {
    val response = apiService.getUsers()
    if (response.isSuccessful) {
        val users = response.body()
        // 데이터 처리
    } else {
        // 오류 처리
    }
}

2. 인터셉터(Interceptor)

인터셉터는 네트워크 요청(Request)이나 응답(Response)을 가로채서 원하는 작업을 수행할 수 있도록 해주는 역할을 합니다. OkHttpClient를 기반으로 동작합니다.


인터셉터의 주요 기능

  1. 요청 데이터 수정
    • 헤더 추가 (예: 인증 토큰).
    • URL이나 파라미터 수정.
  2. 응답 처리
    • 로깅 (요청/응답 데이터를 로그로 출력).
    • 응답 데이터 변경.
    • 오류 처리 (예: 401 Unauthorized 시 토큰 갱신).
  3. 공통 로직 추가
    • 모든 API 요청에 공통적으로 적용되는 로직 처리.

인터셉터 구현

1) 로깅 인터셉터

요청과 응답 정보를 로깅합니다.

class LoggingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val startTime = System.nanoTime()

        println("Sending request to URL: ${request.url()}")

        val response = chain.proceed(request)
        val endTime = System.nanoTime()

        println("Received response from URL: ${response.request.url()} in ${(endTime - startTime) / 1e6}ms")
        return response
    }
}
 

2) 인증 인터셉터

모든 요청에 Authorization 헤더를 추가합니다.

class AuthInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val original = chain.request()
        val request = original.newBuilder()
            .header("Authorization", "Bearer YOUR_ACCESS_TOKEN")
            .build()
        return chain.proceed(request)
    }
}
 

3) 오류 처리 인터셉터

특정 응답 코드(예: 401 Unauthorized)를 처리합니다.

class ErrorHandlingInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())

        if (response.code == 401) {
            // 토큰 갱신 로직 수행
            println("Unauthorized! Refreshing token...")
        }
        return response
    }
}

OkHttpClient에 인터셉터 추가

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(LoggingInterceptor()) // 로깅 인터셉터
    .addInterceptor(AuthInterceptor())    // 인증 인터셉터
    .addInterceptor(ErrorHandlingInterceptor()) // 오류 처리
    .build()

실무에서의 네트워크 모듈 설계 방식

  1. 단일 네트워크 모듈
    • 하나의 모듈에서 모든 네트워크 관련 설정 및 인터셉터를 관리.
    • 코드 간소화 및 재사용성을 높임.
  2. 환경별 설정
    • 개발, 스테이징, 프로덕션 환경에 따라 BASE_URL과 인증 방식을 다르게 설정.
  3. 모듈화
    • 네트워크 모듈을 별도의 파일로 분리하여 유지보수성을 높임.
    • 예: 인증, 로깅, 에러 처리를 별도 클래스로 분리.
  4. 코루틴 사용
    • Retrofit과 코루틴을 결합하여 비동기 처리를 간소화.

정리

  • 네트워크 모듈은 API 호출의 중앙 허브로, 재사용성과 유지보수성을 높이기 위해 설계됩니다.
  • 인터셉터는 요청과 응답의 흐름을 가로채어 데이터를 수정하거나, 로깅 및 오류 처리를 수행하는 데 사용됩니다.
  • 이 두 가지를 결합하면 안드로이드 네트워크 통신의 핵심 구조를 만들 수 있습니다. 실무에서 Retrofit + OkHttp는 표준으로 자리 잡고 있으며, 인터셉터를 통해 공통 작업을 효율적으로 처리할 수 있습니다.

 

단일 네트워크 모듈 계층 구성

1. Data Layer (데이터 계층)

  • API 통신과 관련된 모든 작업이 이루어지는 계층.
  • 주요 역할:
    • Retrofit/OkHttp 설정.
    • API 호출 정의 및 실행.
    • 인터셉터 설정.
    • 응답 데이터 처리.

구성 요소:

  1. API Interface: API 요청 메서드를 정의.
  2. Network Client: Retrofit/OkHttp 인스턴스를 생성.
  3. DTO/Request/Response Models: 데이터 전송 모델 정의.
  4. Error Handling: 네트워크 에러를 통합적으로 처리.

2. Repository Layer (리포지토리 계층)

  • 네트워크 계층과 앱의 다른 계층(예: ViewModel) 간의 중간 역할.
  • 주요 역할:
    • 네트워크 요청을 실행하고 결과 데이터를 가공.
    • ViewModel에서 사용할 수 있는 형식으로 데이터 반환.

3. Domain Layer (도메인 계층 - 선택)

  • 비즈니스 로직을 캡슐화.
  • API 호출 결과를 추가로 처리하거나 변환.
  • 규모가 큰 프로젝트에서 주로 사용.

4. Presentation Layer (프레젠테이션 계층)

  • Repository 또는 Domain Layer의 데이터를 ViewModel에서 사용.
  • ViewModel은 UI와 데이터를 연결하며, 네트워크 요청 상태(로딩, 성공, 실패)를 UI에 전달.

계층 구성 상세

1. API Interface

각 API 요청을 정의하는 인터페이스.

interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response<List<User>>

    @POST("login")
    suspend fun login(@Body request: LoginRequest): Response<LoginResponse>
}

2. Network Client (단일 네트워크 모듈)

Retrofit 및 OkHttpClient를 설정하는 클래스.

object NetworkModule {

    private const val BASE_URL = "https://api.example.com/"

    // OkHttpClient 설정
    private val okHttpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .addInterceptor(LoggingInterceptor())
            .addInterceptor(AuthInterceptor())
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    // Retrofit 설정
    val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    // ApiService 생성
    inline fun <reified T> createService(): T {
        return retrofit.create(T::class.java)
    }
}

3. DTO/Request/Response Models

데이터 전송 객체 정의.

data class User(
    val id: Int,
    val name: String,
    val email: String
)

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

data class LoginResponse(
    val token: String,
    val user: User
)

4. Repository Layer

Repository에서 네트워크 요청을 실행하고 데이터를 반환.

class UserRepository(private val apiService: ApiService) {

    suspend fun getUsers(): List<User>? {
        val response = apiService.getUsers()
        return if (response.isSuccessful) {
            response.body()
        } else {
            null // 에러 처리 추가 가능
        }
    }

    suspend fun login(username: String, password: String): LoginResponse? {
        val response = apiService.login(LoginRequest(username, password))
        return if (response.isSuccessful) {
            response.body()
        } else {
            null // 에러 처리 추가 가능
        }
    }
}

5. ViewModel

ViewModel에서 Repository의 데이터를 호출하고 UI와 연결.

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {

    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> get() = _users

    fun fetchUsers() {
        viewModelScope.launch {
            val userList = userRepository.getUsers()
            _users.postValue(userList)
        }
    }
}

 


6. UI

UI는 ViewModel의 데이터를 관찰하고 변경 사항을 반영.

class UserActivity : AppCompatActivity() {

    private val userViewModel: UserViewModel by viewModels()

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

        userViewModel.users.observe(this) { users ->
            // RecyclerView 업데이트
            println("Fetched Users: $users")
        }

        userViewModel.fetchUsers()
    }
}

모듈 계층 요약

  1. API Interface: 네트워크 요청 정의 (ApiService).
  2. Network Client: Retrofit, OkHttp 설정 및 인터셉터 관리 (NetworkModule).
  3. DTO/Request/Response Models: 데이터 객체 정의.
  4. Repository Layer: 네트워크 요청 실행 및 결과 가공 (UserRepository).
  5. ViewModel: Repository와 UI를 연결 (UserViewModel).
  6. UI: ViewModel의 데이터를 관찰하고 UI에 반영 (UserActivity).

 

https://f-lab.kr/insight/understanding-synchronous-and-asynchronous-processing-in-android

 

동기와 비동기 처리의 이해 및 안드로이드에서의 적용

동기와 비동기 처리의 기본 개념과 차이점을 이해하고, 안드로이드 애플리케이션 개발에서 이를 어떻게 적용할 수 있는지에 대해 설명합니다.

f-lab.kr