Подключиться к сети

Для выполнения сетевых операций в вашем приложении ваш манифест должен включать следующие разрешения:

<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() . Дополнительные сведения см. в кодовой лаборатории 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
           
}
       
});
   
}
}

Дополнительные сведения об этой теме см. в следующих соответствующих руководствах: