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

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

<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
}

Java

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)
    }
}

Java

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() . Для получения дополнительной информации см. практическое руководство « Получение данных из интернета» , в котором демонстрируется реализация многопоточности с использованием сопрограмм 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
            }

        }
    }
}

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
            }
        });
    }
}

Чтобы узнать больше по этой теме, ознакомьтесь со следующими руководствами: