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 bạn truyền qua mạng. Để làm vậy, hãy thực hiện theo các phương pháp hay nhất sau đây về bảo mật mạng:

  • 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, qua đó 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.

Ngoài ra còn có thư viện của bên thứ ba cung cấp API cấp cao hơn cho hoạt động nối mạng. Các thư viện này hỗ trợ nhiều tính năng tiện lợi, chẳng hạn như chuyển đổi tuần tự nội dung yêu cầu và huỷ chuyển đổi tuần tự nội dung phản hồi.

  • Retrofit: một ứng dụng HTTP an toàn về loại cho JVM từ Square, được xây dựng dựa trên OkHttp. Retrofit cho phép bạn tạo giao diện ứng dụng theo cách khai báo, đồng thời hỗ trợ một số thư viện chuyển đổi tuần tự.
  • Ktor: một ứng dụng HTTP của JetBrains, được xây dựng hoàn toàn cho Kotlin và sử dụng coroutine. Ktor hỗ trợ nhiều công cụ, trình chuyển đổi tuần tự và nền tảng.

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ợ tích hợp sẵn 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. 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ữ. Ví dụ: bạn có một thay đổi từ xa, chẳng hạn như cập nhật các điểm cuối API hoặc bạn muốn triển khai chức năng lưu vào bộ nhớ đệm cục bộ.

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 bạn cố gắng thực hiện hoạt động mạng trên luồng chính, thì hệ thống sẽ trả về NetworkOnMainThreadException.

Trong ví dụ nêu trên về mã, 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 bằng hàm enqueue(). Để biết thêm thông tin, hãy xem lớp học lập trình Lấy dữ liệu từ Internet. Tài liệu này minh hoạ cách triển khai luồng bằng coroutine của Kotlin.

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ỉ có thể chứa một lượng nhỏ dữ liệu) đều sẽ bị mất. Nếu điều này xảy ra, bạn có thể cần gửi lại yêu cầu mạng.

Bạn có thể dùng ViewModel để dữ liệu của mình vẫn tồn tại sau khi thay đổi về cấu hình. Thành phần ViewModel đượ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 nhận biết được vòng đời. Bằng việc sử dụng UserRepository nêu 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 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
            }
        });
    }
}

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