Kết nối mạng

Để thực hiện hoạt động mạng trong ứng dụng của bạn, tệp kê khai phải bao gồm các quyền sau đây:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Các phương pháp hay nhất để giao tiếp an toàn qua mạng

Trước khi thêm chức năng kết nối mạng vào ứng dụng của mình, bạn cần đảm bảo rằng dữ liệu và thông tin trong ứng dụng luôn an toàn khi truyền tải qua một mạng. Để làm vậy, hãy thực hiện theo các phương pháp hay nhất về bảo mật mạng sau đây:

  • Giảm thiểu lượng dữ liệu người dùng có tính chất nhạy cảm hoặc cá nhân mà bạn truyền tải qua mạng.
  • Gửi tất cả lưu lượng truy cập mạng từ ứng dụng của bạn qua SSL.
  • Cân nhắc tạo một cấu hình bảo mật mạng, cho phép ứng dụng của bạn tin tưởng các tổ chức phát hành chứng chỉ (CA) tuỳ chỉnh hoặc hạn chế nhóm CA trong hệ thống mà ứng dụng đó tin tưởng để giao tiếp an toàn.

Để biết thêm thông tin về cách áp dụng các nguyên tắc kết nối mạng an toàn, hãy xem mẹo bảo mật mạng.

Chọn một ứng dụng HTTP

Hầu hết các ứng dụng kết nối mạng đều dùng HTTP để gửi và nhận dữ liệu. Nền tảng Android có ứng dụng HttpsURLConnection. Ứng dụng này hỗ trợ TLS (Bảo mật tầng truyền tải), tính năng tải lên và tải xuống trực tuyến, thời gian chờ có thể định cấu hình, IPv6 và vùng kết nối. Trong chủ đề này, chúng tôi dùng Thư viện ứng dụng HTTP Retrofit. Thư viện này cho phép bạn tạo một ứng dụng HTTP theo cách khai báo. Retrofit cũng hỗ trợ tự động tuần tự hoá nội dung yêu cầu và huỷ tuần tự hoá nội dung phản hồi.

Phân giải truy vấn DNS

Các thiết bị chạy Android 10 (API cấp 29) trở lên có dịch vụ hỗ trợ gốc cho hoạt động tra cứu DNS chuyên dụng thông qua cả tra cứu văn bản thô lẫn chế độ DNS qua TLS. API DnsResolver cung cấp hoạt động phân giải chung không đồng bộ để bạn có thể tra cứu SRV, NAPTR và các loại bản ghi khác. Lưu ý rằng việc phân tích cú pháp phản hồi sẽ do ứng dụng thực hiện.

Trên các thiết bị chạy Android 9 (API cấp 28) trở xuống, trình phân giải hệ thống tên miền (DNS) của nền tảng chỉ hỗ trợ các bản ghi AAAAA. Việc này cho phép bạn tra cứu địa chỉ IP liên kết với tên, nhưng không hỗ trợ loại bản ghi nào khác.

Đối với các ứng dụng dựa trên NDK, hãy xem android_res_nsend.

Đóng gói hoạt động mạng bằng kho lưu trữ

Để đơn giản hoá quá trình thực hiện hoạt động mạng và giảm hiện tượng trùng lặp mã trong nhiều phần của ứng dụng, bạn có thể dùng mẫu thiết kế kho lưu trữ. Kho lưu trữ là một lớp xử lý hoạt động dữ liệu và cung cấp bản tóm tắt API rõ ràng đối với một số dữ liệu hoặc tài nguyên cụ thể.

Bạn có thể dùng Retrofit để khai báo một giao diện chỉ định phương thức HTTP, URL, đối số và loại phản hồi cho hoạt động mạng, như trong ví dụ sau:

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

Trong lớp kho lưu trữ, các hàm có thể đóng gói hoạt động mạng và hiển thị kết quả tương ứng. Việc đóng gói này đảm bảo rằng các thành phần gọi kho lưu trữ không cần phải biết cách dữ liệu được lưu trữ. Mọi thay đổi sau này về cách dữ liệu được lưu trữ cũng được tách riêng vào lớp kho lưu trữ.

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

Để tránh tạo giao diện người dùng không phản hồi, đừng thực hiện hoạt động mạng trên luồng chính. Theo mặc định, Android yêu cầu bạn thực hiện hoạt động mạng trên một luồng không phải luồng giao diện người dùng chính. Nếu không, hệ thống sẽ trả về NetworkOnMainThreadException. Trong lớp UserRepository minh hoạ ở ví dụ về mã nêu trên, hoạt động mạng không thực sự được kích hoạt. Lệnh gọi của UserRepository phải triển khai luồng bằng coroutine hoặc sử dụng hàm enqueue().

Vẫn tồn tại sau khi thay đổi về cấu hình

Khi diễn ra thay đổi về cấu hình, chẳng hạn như xoay màn hình, thì mảnh hoặc hoạt động của bạn sẽ bị huỷ bỏ và tạo lại. Mọi dữ liệu không được lưu trong trạng thái thực thể cho mảnh hoặc hoạt động của bạn (chỉ nên chứa lượng nhỏ dữ liệu) sẽ bị mất và bạn có thể cần gửi lại yêu cầu mạng.

Bạn có thể dùng ViewModel để đảm bảo rằng dữ liệu của mình vẫn tồn tại sau khi thay đổi về cấu hình. ViewModel là một thành phần được thiết kế nhằm lưu trữ và quản lý dữ liệu liên quan đến giao diện người dùng theo cách có nhận biết vòng đời. Bằng cách sử dụng UserRepository được tạo ở trên, ViewModel có thể gửi các yêu cầu mạng cần thiết và cung cấp kết quả cho mảnh hoặc hoạt động của bạn thông qua 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 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
            }
        });
    }
}

Để tìm hiểu thêm về chủ đề này, hãy xem các hướng dẫn liên quan sau đây: