连接到网络

若要在您的应用中执行网络操作,您的清单必须包含以下权限:

<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 以及连接池。在本主题中,我们使用 Retrofit HTTP 客户端库,您可以通过该库以声明方式创建 HTTP 客户端。Retrofit 还支持自动对请求正文进行序列化,以及对响应正文进行反序列化。

解析 DNS 查询

搭载 Android 10(API 级别 29)及更高版本的设备通过明文查找和“通过 TLS 执行 DNS”模式对专用 DNS 查找提供原生支持。DnsResolver API 提供通用的异步解析,使您能够查找 SRVNAPTR 和其他记录类型。请注意,解析响应由应用负责执行。

在搭载 Android 9(API 级别 28)及更低版本的设备上,平台 DNS 解析器仅支持 AAAAA 记录。这允许您查找与名称关联的 IP 地址,但不支持任何其他记录类型。

对于基于 NDK 的应用,请参阅 android_res_nsend

使用存储区封装网络操作

为了简化执行网络操作的流程,并减少应用各个部分中的代码重复,您可以使用存储区设计模式。存储区是用于处理数据操作并针对某些特定数据或资源提供干净的 API 抽象的类。

您可以使用 Retrofit 声明一个用于为网络操作指定 HTTP 方法、网址、参数和响应类型的接口,如以下示例所示:

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

在存储区类中,函数可以封装网络操作并公开其结果。这种封装可确保调用存储区的组件不需要知道数据的存储方式。数据存储方式将来发生的任何变化也只与存储区类相关。

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 要求在非主界面线程上执行网络操作;否则将抛出 NetworkOnMainThreadException。在上一个代码示例中显示的 UserRepository 类中,实际并不会触发网络操作。UserRepository 的调用方必须使用协程或使用 enqueue() 函数来实现线程处理。

让 activity 在配置变更后继续存在

当发生配置更改(例如屏幕旋转)时,您的 fragment 或 activity 会被销毁并重新创建。所有未在您的 fragment 或 activity 的实例状态中保存的数据(应该只是少量数据)都将丢失,您可能需要再次发出网络请求。

您可以使用 ViewModel 来确保数据在发生配置更改后继续存在。ViewModel 是一个组件,旨在以注重生命周期的方式存储和管理界面相关的数据。使用上面创建的 UserRepository 时,ViewModel 可以发出必要的网络请求,并使用 LiveData 向您的 fragment 或 activity 提供结果:

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

要进一步了解本主题,请参阅以下相关指南: