네트워크 모듈과 인터셉터는 HTTP 통신을 관리하고, 요청 및 응답을 처리하는 데 중요한 역할을 합니다. 이들은 Retrofit과 OkHttp 라이브러리를 중심으로 설계되고 사용됩니다.
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를 기반으로 동작합니다.
인터셉터의 주요 기능
- 요청 데이터 수정
- 헤더 추가 (예: 인증 토큰).
- URL이나 파라미터 수정.
- 응답 처리
- 로깅 (요청/응답 데이터를 로그로 출력).
- 응답 데이터 변경.
- 오류 처리 (예: 401 Unauthorized 시 토큰 갱신).
- 공통 로직 추가
- 모든 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()
실무에서의 네트워크 모듈 설계 방식
- 단일 네트워크 모듈
- 하나의 모듈에서 모든 네트워크 관련 설정 및 인터셉터를 관리.
- 코드 간소화 및 재사용성을 높임.
- 환경별 설정
- 개발, 스테이징, 프로덕션 환경에 따라 BASE_URL과 인증 방식을 다르게 설정.
- 모듈화
- 네트워크 모듈을 별도의 파일로 분리하여 유지보수성을 높임.
- 예: 인증, 로깅, 에러 처리를 별도 클래스로 분리.
- 코루틴 사용
- Retrofit과 코루틴을 결합하여 비동기 처리를 간소화.
정리
- 네트워크 모듈은 API 호출의 중앙 허브로, 재사용성과 유지보수성을 높이기 위해 설계됩니다.
- 인터셉터는 요청과 응답의 흐름을 가로채어 데이터를 수정하거나, 로깅 및 오류 처리를 수행하는 데 사용됩니다.
- 이 두 가지를 결합하면 안드로이드 네트워크 통신의 핵심 구조를 만들 수 있습니다. 실무에서 Retrofit + OkHttp는 표준으로 자리 잡고 있으며, 인터셉터를 통해 공통 작업을 효율적으로 처리할 수 있습니다.
단일 네트워크 모듈 계층 구성
1. Data Layer (데이터 계층)
- API 통신과 관련된 모든 작업이 이루어지는 계층.
- 주요 역할:
- Retrofit/OkHttp 설정.
- API 호출 정의 및 실행.
- 인터셉터 설정.
- 응답 데이터 처리.
구성 요소:
- API Interface: API 요청 메서드를 정의.
- Network Client: Retrofit/OkHttp 인스턴스를 생성.
- DTO/Request/Response Models: 데이터 전송 모델 정의.
- 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()
}
}
모듈 계층 요약
- API Interface: 네트워크 요청 정의 (ApiService).
- Network Client: Retrofit, OkHttp 설정 및 인터셉터 관리 (NetworkModule).
- DTO/Request/Response Models: 데이터 객체 정의.
- Repository Layer: 네트워크 요청 실행 및 결과 가공 (UserRepository).
- ViewModel: Repository와 UI를 연결 (UserViewModel).
- UI: ViewModel의 데이터를 관찰하고 UI에 반영 (UserActivity).
https://f-lab.kr/insight/understanding-synchronous-and-asynchronous-processing-in-android
'Android' 카테고리의 다른 글
[Android] 앱 삭제 시에도 SharedPreference가 남아있는 현상 (0) | 2024.11.21 |
---|---|
[Android] FLAG_ACTIVITY_SINGLE_TOP 와 FLAG_ACTIVITY_CLEAR_TOP 차이점 (0) | 2024.11.19 |
[Android] gradle를 활용한 firebase App distribution 배포 후 slack 알람 연동 (0) | 2024.11.17 |
[Android] Activity 스택 관리와 LocalBroadcastManager 활용하기 (0) | 2024.11.14 |
[Android] 강제 업데이트 & 수동 업데이트 ? (0) | 2024.11.13 |