Для выполнения сетевых операций в вашем приложении ваш манифест должен включать следующие разрешения:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Лучшие практики для безопасной сетевой коммуникации
Прежде чем добавлять сетевые функции в свое приложение, вам необходимо убедиться, что данные и информация в вашем приложении остаются в безопасности при передаче по сети. Для этого следуйте этим рекомендациям по сетевой безопасности:
- Минимизируйте объем конфиденциальных или личных данных пользователя , передаваемых по сети.
- Отправляйте весь сетевой трафик из вашего приложения по протоколу SSL .
- Рассмотрите возможность создания конфигурации сетевой безопасности , которая позволит вашему приложению доверять пользовательским центрам сертификации (ЦС) или ограничить набор системных ЦС, которым оно доверяет для обеспечения безопасной связи.
Дополнительную информацию о применении принципов безопасности сетей см. в разделе «Советы по безопасности сетей» .
Выберите HTTP-клиент
Большинство сетевых приложений используют HTTP для отправки и получения данных. Платформа Android включает клиент HttpsURLConnection
, который поддерживает TLS, потоковую загрузку и скачивание, настраиваемые тайм-ауты, IPv6 и пул соединений.
Также доступны сторонние библиотеки, которые предлагают API более высокого уровня для сетевых операций. Они поддерживают различные удобные функции, такие как сериализация тел запросов и десериализация тел ответов.
- Retrofit : типобезопасный HTTP-клиент для JVM от Square, построенный на основе OkHttp. Retrofit позволяет декларативно создавать клиентский интерфейс и поддерживает несколько библиотек сериализации.
- Ktor : HTTP-клиент от JetBrains, полностью созданный для Kotlin и работающий на сопрограммах. Ktor поддерживает различные движки, сериализаторы и платформы.
Разрешение DNS-запросов
Устройства под управлением Android 10 (уровень API 29) и выше имеют встроенную поддержку специализированных DNS-запросов как через открытый текст, так и через режим DNS-over-TLS. API DnsResolver
обеспечивает общее асинхронное разрешение, которое позволяет искать SRV
, NAPTR
и другие типы записей. Анализ ответа остается за приложением.
На устройствах под управлением Android 9 (уровень API 28) и ниже DNS-резолвер платформы поддерживает только записи A
и AAAA
. Это позволяет искать IP-адреса, связанные с именем, но не поддерживает никакие другие типы записей.
Для приложений на основе NDK см. android_res_nsend
.
Инкапсуляция сетевых операций с помощью репозитория
Чтобы упростить процесс выполнения сетевых операций и уменьшить дублирование кода в различных частях вашего приложения, вы можете использовать шаблон проектирования репозитория. Репозиторий — это класс, который обрабатывает операции с данными и предоставляет чистую абстракцию API над некоторыми конкретными данными или ресурсом.
Вы можете использовать Retrofit для объявления интерфейса, который определяет метод HTTP, URL, аргументы и тип ответа для сетевых операций, как в следующем примере:
Котлин
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, или вы можете захотеть реализовать локальное кэширование.
Котлин
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); } }
Чтобы избежать создания неотзывчивого пользовательского интерфейса, не выполняйте сетевые операции в основном потоке. По умолчанию Android требует, чтобы вы выполняли сетевые операции в потоке, отличном от основного потока пользовательского интерфейса. Если вы попытаетесь выполнить сетевые операции в основном потоке, будет выдано исключение NetworkOnMainThreadException
.
В предыдущем примере кода сетевая операция фактически не запускается. Вызывающий UserRepository
должен реализовать потоки либо с помощью сопрограмм, либо с помощью функции enqueue()
. Для получения дополнительной информации см. codelab Get data from the internet , в которой показано, как реализовать потоки с помощью сопрограмм Kotlin.
Пережить изменения конфигурации
При изменении конфигурации, например повороте экрана, ваш фрагмент или активность уничтожаются и создаются заново. Любые данные, не сохраненные в состоянии экземпляра для вашей активности фрагмента, которая может содержать только небольшие объемы данных, теряются. Если это произойдет, вам может потребоваться снова выполнить сетевые запросы.
Вы можете использовать ViewModel
, чтобы ваши данные пережили изменения конфигурации. Компонент ViewModel
предназначен для хранения и управления данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. Используя предыдущий UserRepository
, ViewModel
может выполнять необходимые сетевые запросы и предоставлять результат вашему фрагменту или действию с помощью LiveData
:
Котлин
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 } }); } }
Прочитайте соответствующие руководства
Чтобы узнать больше по этой теме, ознакомьтесь со следующими руководствами:
- Уменьшение разрядки батареи сети: Обзор
- Минимизируйте эффект регулярных обновлений
- Веб-контент
- Основы применения
- Руководство по архитектуре приложения
Для выполнения сетевых операций в вашем приложении ваш манифест должен включать следующие разрешения:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Лучшие практики для безопасной сетевой коммуникации
Прежде чем добавлять сетевые функции в свое приложение, вам необходимо убедиться, что данные и информация в вашем приложении остаются в безопасности при передаче по сети. Для этого следуйте этим рекомендациям по сетевой безопасности:
- Минимизируйте объем конфиденциальных или личных данных пользователя , передаваемых по сети.
- Отправляйте весь сетевой трафик из вашего приложения по протоколу SSL .
- Рассмотрите возможность создания конфигурации сетевой безопасности , которая позволит вашему приложению доверять пользовательским центрам сертификации (ЦС) или ограничить набор системных ЦС, которым оно доверяет для обеспечения безопасной связи.
Дополнительную информацию о применении принципов безопасности сетей см. в разделе «Советы по безопасности сетей» .
Выберите HTTP-клиент
Большинство сетевых приложений используют HTTP для отправки и получения данных. Платформа Android включает клиент HttpsURLConnection
, который поддерживает TLS, потоковую загрузку и скачивание, настраиваемые тайм-ауты, IPv6 и пул соединений.
Также доступны сторонние библиотеки, которые предлагают API более высокого уровня для сетевых операций. Они поддерживают различные удобные функции, такие как сериализация тел запросов и десериализация тел ответов.
- Retrofit : типобезопасный HTTP-клиент для JVM от Square, построенный на основе OkHttp. Retrofit позволяет декларативно создавать клиентский интерфейс и поддерживает несколько библиотек сериализации.
- Ktor : HTTP-клиент от JetBrains, полностью созданный для Kotlin и работающий на сопрограммах. Ktor поддерживает различные движки, сериализаторы и платформы.
Разрешение DNS-запросов
Устройства под управлением Android 10 (уровень API 29) и выше имеют встроенную поддержку специализированных DNS-запросов как через открытый текст, так и через режим DNS-over-TLS. API DnsResolver
обеспечивает общее асинхронное разрешение, которое позволяет искать SRV
, NAPTR
и другие типы записей. Анализ ответа остается за приложением.
На устройствах под управлением Android 9 (уровень API 28) и ниже DNS-резолвер платформы поддерживает только записи A
и AAAA
. Это позволяет искать IP-адреса, связанные с именем, но не поддерживает никакие другие типы записей.
Для приложений на основе NDK см. android_res_nsend
.
Инкапсуляция сетевых операций с помощью репозитория
Чтобы упростить процесс выполнения сетевых операций и уменьшить дублирование кода в различных частях вашего приложения, вы можете использовать шаблон проектирования репозитория. Репозиторий — это класс, который обрабатывает операции с данными и предоставляет чистую абстракцию API над некоторыми конкретными данными или ресурсом.
Вы можете использовать Retrofit для объявления интерфейса, который определяет метод HTTP, URL, аргументы и тип ответа для сетевых операций, как в следующем примере:
Котлин
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, или вы можете захотеть реализовать локальное кэширование.
Котлин
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); } }
Чтобы избежать создания неотзывчивого пользовательского интерфейса, не выполняйте сетевые операции в основном потоке. По умолчанию Android требует, чтобы вы выполняли сетевые операции в потоке, отличном от основного потока пользовательского интерфейса. Если вы попытаетесь выполнить сетевые операции в основном потоке, будет выдано исключение NetworkOnMainThreadException
.
В предыдущем примере кода сетевая операция фактически не запускается. Вызывающий UserRepository
должен реализовать потоки либо с помощью сопрограмм, либо с помощью функции enqueue()
. Для получения дополнительной информации см. codelab Get data from the internet , в которой показано, как реализовать потоки с помощью сопрограмм Kotlin.
Пережить изменения конфигурации
При изменении конфигурации, например повороте экрана, ваш фрагмент или активность уничтожаются и создаются заново. Любые данные, не сохраненные в состоянии экземпляра для вашей активности фрагмента, которая может содержать только небольшие объемы данных, теряются. Если это произойдет, вам может потребоваться снова выполнить сетевые запросы.
Вы можете использовать ViewModel
, чтобы ваши данные пережили изменения конфигурации. Компонент ViewModel
предназначен для хранения и управления данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. Используя предыдущий UserRepository
, ViewModel
может выполнять необходимые сетевые запросы и предоставлять результат вашему фрагменту или действию с помощью LiveData
:
Котлин
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 } }); } }
Прочитайте соответствующие руководства
Чтобы узнать больше по этой теме, ознакомьтесь со следующими руководствами:
- Уменьшение разрядки батареи сети: Обзор
- Минимизируйте эффект регулярных обновлений
- Веб-контент
- Основы применения
- Руководство по архитектуре приложения