Чтобы выполнять сетевые операции в приложении, в манифесте должны быть указаны следующие разрешения:
<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.
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 }
Java
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) } }
Java
class UserRepository { private UserService userService; public UserRepository( UserService userService ) { this.userService = userService; } public Call<User> getUserById(String id) { return userService.getUser(id); } }
Чтобы избежать зависания интерфейса, не выполняйте сетевые операции в основном потоке. По умолчанию в Android сетевые операции должны выполняться в потоке, отличном от основного потока UI. Если вы попытаетесь выполнить сетевые операции в основном потоке, будет выдано исключение NetworkOnMainThreadException.
В предыдущем примере кода сетевая операция фактически не запускается. Вызывающий объект UserRepository должен реализовать потоковую передачу с помощью сопрограмм или функции enqueue(). Дополнительную информацию можно найти в практической работе Получение данных из интернета, в которой показано, как реализовать потоковую передачу с помощью сопрограмм Kotlin.
Сохранение состояния при изменении конфигурации
При изменении конфигурации, например повороте экрана, фрагмент или действие удаляется и создается заново. Любые данные, не сохраненные в состоянии экземпляра для действия фрагмента, которое может содержать только небольшие объемы данных, будут потеряны. В этом случае вам может потребоваться повторно выполнить сетевые запросы.
Чтобы данные не терялись при изменении конфигурации, можно использовать ViewModel. Компонент ViewModel предназначен для хранения данных, связанных с пользовательским интерфейсом, и управления ими с учетом жизненного цикла. Используя 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 } } } }
Java
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 } }); } }
Похожие руководства
Дополнительную информацию по этой теме можно найти в следующих руководствах:
- Как снизить расход заряда батареи при использовании сети
- Как минимизировать влияние регулярных обновлений
- Веб-контент
- Основы работы с приложением
- Руководство по архитектуре приложений