连接到网络

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

<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 以及连接池。

此外,某些第三方库也提供更高级别的网络操作 API。这些库支持各种便捷功能,例如对请求正文进行序列化,以及对响应正文进行反序列化。

  • Retrofit:来自 Square 的 JVM 的类型安全 HTTP 客户端,基于 OkHttp 构建。Retrofit 可让您以声明方式创建客户端接口,并支持多个序列化库。
  • Ktor:来自 JetBrains 的 HTTP 客户端,完全针对 Kotlin 构建,并由协程提供支持。Ktor 支持各种引擎、序列化器和平台。

解析 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 方法、网址、参数和响应类型的接口,如以下示例所示:

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,其中演示了如何使用 Kotlin 协程实现线程处理。

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

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

您可以使用 ViewModel 让数据在配置更改后仍然保留。ViewModel 组件旨在以注重生命周期的方式存储和管理界面相关的数据。使用前面的 UserRepository 时,ViewModel 可以发出必要的网络请求,并使用 LiveData 向您的 fragment 或 activity 提供结果:

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

如需详细了解此主题,请参阅以下相关指南: