네트워크에 연결
컬렉션을 사용해 정리하기
내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
애플리케이션에서 네트워크 작업을 실행하려면 매니페스트에 다음 권한을 포함해야 합니다.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
안전한 네트워크 통신을 위한 권장사항
앱에 네트워킹 기능을 추가하기 전에 네트워크를 통해 앱의 데이터와 정보를 전송할 때 안전하게 보호되는지 확인해야 합니다. 데이터와 정보를 안전하게 유지하려면 다음 네트워킹 보안 권장사항을 따르세요.
- 네트워크를 통해 전송하는 데이터 중 민감하거나 개인적인 사용자 데이터의 양을 최소화합니다.
- 앱의 모든 네트워크 트래픽은 SSL을 통해 전송합니다.
- 네트워크 보안 구성을 만들어 봅니다. 이 구성을 사용하면 앱에서 안전한 통신을 위해 맞춤 CA(인증 기관)를 신뢰하거나 신뢰하는 시스템 CA 집합을 제한할 수 있습니다.
안전한 네트워킹 원칙을 적용하는 방법을 자세히 알아보려면 네트워킹 보안 도움말을 참고하세요.
HTTP 클라이언트 선택
네트워크에 연결된 앱의 대부분은 HTTP를 사용하여 데이터를 송수신합니다. Android 플랫폼에는 HttpsURLConnection
클라이언트가 포함되며 이 클라이언트는 TLS, 스트리밍 업로드 및 다운로드, 시간 제한 구성, IPv6, 연결 풀링을 지원합니다.
네트워킹 작업에 상위 수준 API를 제공하는 서드 파티 라이브러리도 사용할 수 있습니다. 이는 요청 본문의 직렬화, 응답 본문의 역직렬화와 같은 다양한 편의 기능을 지원합니다.
- Retrofit: Square의 JVM용 유형 안전 HTTP 클라이언트로, OkHttp를 기반으로 빌드됩니다. Retrofit을 사용하면 클라이언트 인터페이스를 선언적으로 만들 수 있으며 여러 직렬화 라이브러리를 지원합니다.
- Ktor: JetBrains의 HTTP 클라이언트로, Kotlin 전용으로 빌드되었으며 코루틴을 기반으로 합니다. Ktor는 다양한 엔진, 직렬 변환기, 플랫폼을 지원합니다.
DNS 쿼리 결정
Android 10(API 수준 29) 이상을 실행하는 기기에서는 일반 텍스트 조회와 DNS-over-TLS 모드를 통한 특수 DNS 조회가 기본적으로 지원됩니다.
DnsResolver
API는 일반적인 비동기식의 결정 방법을 제공하며 이를 사용하여 SRV
, NAPTR
및 기타 레코드 유형을 조회할 수 있습니다. 응답 파싱은 앱에서 실행합니다.
Android 9(API 수준 28) 이하를 실행하는 기기의 플랫폼 DNS 리졸버는 A
및 AAAA
레코드만 지원합니다. 이렇게 하면, 이름과 연결된 IP 주소를 조회할 수 있으며 다른 레코드 유형은 지원하지 않습니다.
NDK 기반 앱의 경우 android_res_nsend
를 참고하세요.
저장소로 네트워크 작업 캡슐화
네트워크 작업 실행 프로세스를 단순화하고 앱의 다양한 영역에서 코드 중복을 줄이려면 저장소 설계 패턴을 사용하면 됩니다. 저장소는 데이터 작업을 처리하고 특정 데이터 또는 리소스에 관해 명확한 API 추상화를 제공하는 클래스입니다.
다음 예와 같이 Retrofit을 사용하여 네트워크 작업에 필요한 HTTP 메서드, URL, 인수 및 응답 유형을 지정하는 인터페이스를 선언할 수 있습니다.
Kotlin
interface UserService {
@GET("/users/{id}")
suspend fun getUser(@Path("id") id: String): User
}
자바
public interface UserService {
@GET("/user/{id}")
Call<User> getUserById(@Path("id") String id);
}
저장소 클래스 내에서 함수는 네트워크 작업을 캡슐화하고 결과를 노출할 수 있습니다. 이러한 캡슐화를 통해 저장소를 호출하는 구성요소는 데이터가 저장되는 방식을 알 필요가 없습니다. 또한, 이후 데이터 저장 방식에 관한 모든 변경사항은 저장소 클래스와 분리됩니다. 예를 들어 API 엔드포인트 업데이트와 같은 원격 변경사항이 있을 수도 있고 로컬 캐싱을 구현해야 할 수도 있습니다.
Kotlin
class UserRepository constructor(
private val userService: UserService
) {
suspend fun getUserById(id: String): User {
return userService.getUser(id)
}
}
자바
class UserRepository {
private UserService userService;
public UserRepository(
UserService userService
) {
this.userService = userService;
}
public Call<User> getUserById(String id) {
return userService.getUser(id);
}
}
UI가 반응하지 않는 상황을 피하려면 기본 스레드에서 네트워크 작업을 실행하지 마세요. 기본적으로 Android에서는 기본 UI 스레드가 아닌 스레드에서 네트워크 작업을 실행해야 합니다. 기본 스레드에서 네트워크 작업을 실행하려고 하면 NetworkOnMainThreadException
이 발생합니다.
이전 코드 예에서 네트워크 작업은 실제로 트리거되지 않습니다. UserRepository
의 호출자는 코루틴 또는 enqueue()
함수를 사용하여 스레드를 구현해야 합니다. 자세한 내용은 Kotlin 코루틴을 사용하여 스레드 구현 방법을 보여주는 Codelab 인터넷에서 데이터 가져오기를 참고하세요.
구성 변경사항 유지
구성에 변경사항(예: 화면 회전)이 생기면 프래그먼트 또는 활동이 소멸되고 다시 생성됩니다. 소량의 데이터만 유지할 수 있는 프래그먼트 활동의 경우 인스턴스 상태에 저장되지 않은 데이터는 손실됩니다. 이 경우 네트워크를 다시 요청해야 할 수도 있습니다.
ViewModel
을 사용하여 구성 변경 후에도 데이터가 유지되도록 할 수 있습니다. ViewModel
구성요소는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었습니다. 위에서 생성된 UserRepository
를 사용하면 ViewModel
에서 필수 네트워크 요청을 실행하고 LiveData
를 사용하여 프래그먼트나 활동에 결과를 제공할 수 있습니다.
Kotlin
class MainViewModel constructor(
savedStateHandle: SavedStateHandle,
userRepository: UserRepository
) : ViewModel() {
private val userId: String = savedStateHandle["uid"] ?:
throw IllegalArgumentException("Missing user ID")
private val _user = MutableLiveData<User>()
val user = _user as LiveData<User>
init {
viewModelScope.launch {
try {
// Calling the repository is safe as it moves execution off
// the main thread
val user = userRepository.getUserById(userId)
_user.value = user
} catch (error: Exception) {
// Show error message to user
}
}
}
}
자바
class MainViewModel extends ViewModel {
private final MutableLiveData<User> _user = new MutableLiveData<>();
LiveData<User> user = (LiveData<User>) _user;
public MainViewModel(
SavedStateHandle savedStateHandle,
UserRepository userRepository
) {
String userId = savedStateHandle.get("uid");
Call<User> userCall = userRepository.getUserById(userId);
userCall.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
_user.setValue(response.body());
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// Show error message to user
}
});
}
}
이 주제에 관해 자세히 알아보려면 다음 관련 가이드를 참고하세요.
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-08-21(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-08-21(UTC)"],[],[],null,["# Connect to the network\n\nTo perform network operations in your application, your manifest must include\nthe following permissions: \n\n \u003cuses-permission android:name=\"android.permission.INTERNET\" /\u003e\n \u003cuses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /\u003e\n\n| **Note:** Both the [`INTERNET`](/reference/android/Manifest.permission#INTERNET) and [`ACCESS_NETWORK_STATE`](/reference/android/Manifest.permission#ACCESS_NETWORK_STATE) permissions are [normal\n| permissions](/develop/permissions/overview#normal-dangerous), which means they're granted at install time and don't need to be [requested at runtime](/training/permissions/requesting).\n\nBest practices for secure network communication\n-----------------------------------------------\n\nBefore you add networking functionality to your app, you need to ensure that\ndata and information within your app stay safe when you are transmitting over a\nnetwork. To do so, follow these networking security best practices:\n\n- Minimize the amount of sensitive or personal [user\n data](/privacy-and-security/security-tips#user-data) that you transmit over the network.\n- Send all network traffic from your app over [SSL](/training/articles/security-ssl).\n- Consider creating a [network security\n configuration](/training/articles/security-config), which lets your app trust custom certificate authorities (CAs) or restrict the set of system CAs that it trusts for secure communication.\n\nFor more information on how to apply secure networking principles, see the\n[networking security tips](/privacy-and-security/security-tips#networking).\n\nChoose an HTTP client\n---------------------\n\nMost network-connected apps use HTTP to send and receive data. The Android\nplatform includes the\n[`HttpsURLConnection`](/reference/javax/net/ssl/HttpsURLConnection) client,\nwhich supports TLS, streaming uploads and downloads, configurable timeouts,\nIPv6, and connection pooling.\n\nThird-party libraries that offer higher-level APIs for networking operations are\nalso available. These support various convenience features, such as the\nserialization of request bodies and deserialization of response bodies.\n\n- [Retrofit](https://square.github.io/retrofit/): a type-safe HTTP client for the JVM from Square, built on top of OkHttp. Retrofit lets you create a client interface declaratively and has support for several serialization libraries.\n- [Ktor](https://ktor.io/): an HTTP client from JetBrains, built entirely for Kotlin and powered by coroutines. Ktor supports various engines, serializers, and platforms.\n\nResolve DNS queries\n-------------------\n\nDevices that run Android 10 (API level 29) and higher have built-in support for\nspecialized DNS lookups through both cleartext lookups and DNS-over-TLS mode.\nThe [`DnsResolver`](/reference/android/net/DnsResolver) API provides generic,\nasynchronous resolution, which lets you look up `SRV`, `NAPTR`, and other\nrecord types. Parsing the response is left to the app to perform.\n\nOn devices that run Android 9 (API level 28) and lower, the platform DNS\nresolver supports only `A` and `AAAA` records. This lets you look up the IP\naddresses associated with a name but doesn't support any other record types.\n\nFor NDK-based apps, see\n[`android_res_nsend`](/ndk/reference/group/networking#android_res_nsend).\n\nEncapsulate network operations with a repository\n------------------------------------------------\n\nTo simplify the process of performing network operations and reduce code\nduplication in various parts of your app, you can use the repository design\npattern. A repository is a class that handles data operations and provides a\nclean API abstraction over some specific data or resource.\n\nYou can use Retrofit to declare an interface that specifies the HTTP method,\nURL, arguments, and response type for network operations, as in the following\nexample: \n\n### Kotlin\n\n```kotlin\ninterface UserService {\n @GET(\"/users/{id}\")\n suspend fun getUser(@Path(\"id\") id: String): User\n}\n```\n\n### Java\n\n```java\npublic interface UserService {\n @GET(\"/user/{id}\")\n Call\u003cUser\u003e getUserById(@Path(\"id\") String id);\n}\n```\n\nWithin a repository class, functions can encapsulate network operations and\nexpose their results. This encapsulation ensures that the components that call\nthe repository don't need to know how the data is stored. Any future changes to\nhow the data is stored are isolated to the repository class as well. For\nexample, you might have a remote change such as an update to API endpoints, or\nyou might want to implement local caching. \n\n### Kotlin\n\n```kotlin\nclass UserRepository constructor(\n private val userService: UserService\n) {\n suspend fun getUserById(id: String): User {\n return userService.getUser(id)\n }\n}\n```\n\n### Java\n\n```java\nclass UserRepository {\n private UserService userService;\n\n public UserRepository(\n UserService userService\n ) {\n this.userService = userService;\n }\n\n public Call\u003cUser\u003e getUserById(String id) {\n return userService.getUser(id);\n }\n}\n```\n\nTo avoid creating an unresponsive UI, don't perform network operations on the\nmain thread. By default, Android requires you to perform network operations on a\nthread other than the main UI thread. If you try to perform network operations\non the main thread, a\n[`NetworkOnMainThreadException`](/reference/android/os/NetworkOnMainThreadException)\nis thrown.\n\nIn the previous code example, the\nnetwork operation isn't actually triggered. The caller of the `UserRepository`\nmust implement the threading either using coroutines or using the `enqueue()`\nfunction. For more information, see the codelab [Get data from the\ninternet](/codelabs/basic-android-kotlin-training-getting-data-internet),\nwhich demonstrates how to implement threading using Kotlin coroutines.\n\nSurvive configuration changes\n-----------------------------\n\nWhen a configuration change occurs, such as a screen rotation, your fragment or\nactivity is destroyed and recreated. Any data not saved in the instance\nstate for your fragment activity, which can only hold small amounts of data,\nis lost. If this occurs, you might need to make your network requests again.\n\nYou can use a [`ViewModel`](/topic/libraries/architecture/viewmodel) to let\nyour data survive configuration changes. The `ViewModel` component is\ndesigned to store and manage UI-related data in a lifecycle-conscious\nway. Using the preceding `UserRepository`, the `ViewModel` can make the\nnecessary network requests and provide the result to your fragment or activity\nusing [`LiveData`](/topic/libraries/architecture/livedata): \n\n### Kotlin\n\n```kotlin\nclass MainViewModel constructor(\n savedStateHandle: SavedStateHandle,\n userRepository: UserRepository\n) : ViewModel() {\n private val userId: String = savedStateHandle[\"uid\"] ?:\n throw IllegalArgumentException(\"Missing user ID\")\n\n private val _user = MutableLiveData\u003cUser\u003e()\n val user = _user as LiveData\u003cUser\u003e\n\n init {\n viewModelScope.launch {\n try {\n // Calling the repository is safe as it moves execution off\n // the main thread\n val user = userRepository.getUserById(userId)\n _user.value = user\n } catch (error: Exception) {\n // Show error message to user\n }\n\n }\n }\n}\n```\n\n### Java\n\n```java\nclass MainViewModel extends ViewModel {\n\n private final MutableLiveData\u003cUser\u003e _user = new MutableLiveData\u003c\u003e();\n LiveData\u003cUser\u003e user = (LiveData\u003cUser\u003e) _user;\n\n public MainViewModel(\n SavedStateHandle savedStateHandle,\n UserRepository userRepository\n ) {\n String userId = savedStateHandle.get(\"uid\");\n Call\u003cUser\u003e userCall = userRepository.getUserById(userId);\n userCall.enqueue(new Callback\u003cUser\u003e() {\n @Override\n public void onResponse(Call\u003cUser\u003e call, Response\u003cUser\u003e response) {\n if (response.isSuccessful()) {\n _user.setValue(response.body());\n }\n }\n\n @Override\n public void onFailure(Call\u003cUser\u003e call, Throwable t) {\n // Show error message to user\n }\n });\n }\n}\n```\n\nRead related guides\n-------------------\n\nTo learn more about this topic, see the following related guides:\n\n- [Reduce network battery drain: Overview](/topic/performance/power)\n- [Minimize the effect of regular updates](/training/efficient-downloads)\n- [Web-based content](/guide/webapps)\n- [Application fundamentals](/guide/components/fundamentals)\n- [Guide to app architecture](/jetpack/guide)"]]