Khi một thành phần của ứng dụng bắt đầu và ứng dụng không có bất kỳ thành phần nào khác đang chạy, hệ thống Android sẽ bắt đầu một quy trình Linux mới cho ứng dụng với một luồng thực thi duy nhất. Theo mặc định, tất cả các thành phần của cùng một ứng dụng sẽ chạy trong cùng một quy trình và luồng, được gọi là luồng chính.
Nếu một thành phần của ứng dụng bắt đầu và đã có một quy trình cho ứng dụng đó, vì một thành phần khác của ứng dụng đã bắt đầu, thì thành phần đó sẽ bắt đầu trong quy trình đó và sử dụng cùng một luồng thực thi. Tuy nhiên, bạn có thể sắp xếp để các thành phần khác nhau trong ứng dụng chạy trong các quy trình riêng biệt và bạn có thể tạo thêm các luồng cho bất kỳ quy trình nào.
Tài liệu này thảo luận về cách hoạt động của các quy trình và luồng trong một ứng dụng Android.
Quá trình
Theo mặc định, tất cả các thành phần của một ứng dụng đều chạy trong cùng một quy trình và hầu hết các ứng dụng đều không thay đổi điều này. Tuy nhiên, nếu thấy cần kiểm soát quy trình mà một thành phần nhất định thuộc về, bạn có thể thực hiện việc này trong tệp kê khai.
Mục nhập tệp kê khai cho từng loại phần tử thành phần (<activity>, <service>, <receiver> và <provider>) hỗ trợ một thuộc tính android:process có thể chỉ định một quy trình mà thành phần chạy trong đó. Bạn có thể đặt thuộc tính này để mỗi thành phần chạy trong quy trình riêng hoặc để một số thành phần dùng chung quy trình trong khi những thành phần khác thì không.
Bạn cũng có thể đặt android:process để các thành phần của các ứng dụng khác nhau chạy trong cùng một quy trình, miễn là các ứng dụng đó chia sẻ cùng một mã nhận dạng người dùng Linux và được ký bằng cùng một chứng chỉ.
Phần tử <application> cũng hỗ trợ thuộc tính android:process. Bạn có thể dùng thuộc tính này để đặt một giá trị mặc định áp dụng cho tất cả các thành phần.
Android có thể quyết định tắt một quy trình tại một thời điểm nào đó, khi các quy trình khác cần tài nguyên để phục vụ người dùng ngay lập tức hơn. Do đó, các thành phần ứng dụng đang chạy trong quy trình bị tắt sẽ bị huỷ. Một quy trình sẽ bắt đầu lại cho những thành phần đó khi có việc cần làm.
Khi quyết định tắt quy trình nào, hệ thống Android sẽ cân nhắc tầm quan trọng tương đối của quy trình đó đối với người dùng. Ví dụ: hệ thống sẽ dễ dàng tắt hơn một quy trình lưu trữ các hoạt động không còn hiển thị trên màn hình, so với một quy trình lưu trữ các hoạt động hiển thị. Do đó, quyết định có nên chấm dứt một quy trình hay không phụ thuộc vào trạng thái của các thành phần đang chạy trong quy trình đó.
Thông tin chi tiết về vòng đời của quy trình và mối quan hệ của quy trình với các trạng thái ứng dụng được thảo luận trong bài viết Quy trình và vòng đời của ứng dụng.
Luồng
Khi một ứng dụng được khởi chạy, hệ thống sẽ tạo một luồng thực thi cho ứng dụng đó, gọi là luồng chính. Luồng này rất quan trọng vì có nhiệm vụ gửi các sự kiện đến những tiện ích giao diện người dùng thích hợp, bao gồm cả các sự kiện vẽ. Đây cũng gần như luôn là luồng mà ứng dụng của bạn tương tác với các thành phần trong các gói android.widget và android.view của bộ công cụ giao diện người dùng Android.
Do đó, luồng chính đôi khi được gọi là luồng giao diện người dùng. Tuy nhiên, trong những trường hợp đặc biệt, luồng chính của ứng dụng có thể không phải là luồng giao diện người dùng. Để biết thêm thông tin, hãy xem phần Chú thích luồng.
Hệ thống không tạo một luồng riêng cho từng phiên bản của một thành phần. Tất cả các thành phần chạy trong cùng một quy trình đều được tạo thực thể trong luồng giao diện người dùng và các lệnh gọi hệ thống đến từng thành phần sẽ được gửi từ luồng đó. Do đó, các phương thức phản hồi lệnh gọi lại hệ thống (chẳng hạn như onKeyDown() để báo cáo hành động của người dùng hoặc phương thức gọi lại trong vòng đời) luôn chạy trong luồng giao diện người dùng của quy trình.
Ví dụ: khi người dùng chạm vào một nút trên màn hình, luồng giao diện người dùng của ứng dụng sẽ gửi sự kiện chạm đến tiện ích. Tiện ích này sẽ đặt trạng thái đã nhấn và đăng một yêu cầu không hợp lệ vào hàng đợi sự kiện. Luồng giao diện người dùng sẽ loại bỏ yêu cầu khỏi hàng đợi và thông báo cho tiện ích để vẽ lại chính nó.
Trừ phi bạn triển khai ứng dụng đúng cách, nếu không mô hình một luồng này có thể mang lại hiệu suất kém khi ứng dụng của bạn thực hiện công việc chuyên sâu để phản hồi hoạt động tương tác của người dùng. Việc thực hiện các thao tác dài trong luồng giao diện người dùng, chẳng hạn như truy cập mạng hoặc truy vấn cơ sở dữ liệu, sẽ chặn toàn bộ giao diện người dùng. Khi luồng bị chặn, không có sự kiện nào có thể được gửi đi, kể cả sự kiện vẽ.
Theo quan điểm của người dùng, ứng dụng có vẻ như ngừng phản hồi. Thậm chí, nếu luồng giao diện người dùng bị chặn trong vài giây, người dùng sẽ thấy hộp thoại "ứng dụng không phản hồi" (ANR). Sau đó, người dùng có thể quyết định thoát ứng dụng của bạn hoặc thậm chí gỡ cài đặt ứng dụng.
Xin lưu ý rằng bộ công cụ giao diện người dùng Android không an toàn cho luồng. Vì vậy, đừng thao tác giao diện người dùng từ một luồng worker. Thực hiện mọi thao tác trên giao diện người dùng từ luồng giao diện người dùng. Mô hình một luồng của Android có 2 quy tắc:
- Đừng chặn luồng giao diện người dùng.
- Không truy cập vào bộ công cụ giao diện người dùng Android từ bên ngoài luồng giao diện người dùng.
Luồng worker
Do mô hình một luồng này, điều quan trọng là bạn không được chặn luồng giao diện người dùng để đảm bảo khả năng phản hồi của giao diện người dùng ứng dụng. Nếu bạn có các thao tác cần thực hiện không diễn ra ngay lập tức, hãy nhớ thực hiện các thao tác đó trong các luồng nền hoặc worker riêng biệt. Hãy nhớ rằng bạn không thể cập nhật giao diện người dùng từ bất kỳ luồng nào khác ngoài luồng giao diện người dùng hoặc luồng chính.
Để giúp bạn tuân thủ các quy tắc này, Android cung cấp một số cách để truy cập vào luồng giao diện người dùng từ các luồng khác. Sau đây là danh sách các phương pháp có thể giúp bạn:
Các ví dụ sau đây minh hoạ cách giảm tải một tác vụ sang luồng nền và cập nhật luồng giao diện người dùng sau khi tác vụ hoàn tất:
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Phương thức triển khai này an toàn cho luồng, vì thao tác ở chế độ nền được thực hiện từ một luồng riêng trong khi ImageView luôn được thao tác từ luồng giao diện người dùng.
Tuy nhiên, khi độ phức tạp của thao tác tăng lên, loại mã này có thể trở nên phức tạp và khó duy trì. Để xử lý các hoạt động tương tác phức tạp hơn với một luồng worker, bạn có thể cân nhắc sử dụng một Handler trong luồng worker để xử lý các thông báo được gửi từ luồng giao diện người dùng. Để biết nội dung giải thích đầy đủ về cách lên lịch hoạt động trên các luồng ở chế độ nền và giao tiếp trở lại với luồng giao diện người dùng, hãy xem bài viết Tổng quan về hoạt động trong nền.
Phương thức an toàn cho luồng
Trong một số trường hợp, các phương thức mà bạn triển khai được gọi từ nhiều luồng, do đó, bạn phải viết các phương thức này để đảm bảo an toàn cho luồng.
Điều này chủ yếu đúng với những phương thức có thể được gọi từ xa, chẳng hạn như các phương thức trong một dịch vụ ràng buộc. Khi một lệnh gọi trên phương thức được triển khai trong IBinder bắt nguồn từ cùng một quy trình mà IBinder đang chạy, phương thức này sẽ được thực thi trong luồng của phương thức gọi.
Tuy nhiên, khi lệnh gọi bắt nguồn từ một quy trình khác, phương thức này sẽ thực thi trong một luồng được chọn từ một nhóm các luồng mà hệ thống duy trì trong cùng một quy trình với IBinder.
Không được thực thi trong luồng giao diện người dùng của quy trình.
Ví dụ: trong khi phương thức onBind() của một dịch vụ được gọi từ luồng giao diện người dùng của quy trình dịch vụ, các phương thức được triển khai trong đối tượng mà onBind() trả về (chẳng hạn như một lớp con triển khai các phương thức lệnh gọi quy trình từ xa (RPC)) được gọi từ các luồng trong nhóm. Vì một dịch vụ có thể có nhiều ứng dụng, nên nhiều luồng nhóm có thể tham gia cùng một phương thức IBinder cùng lúc, vì vậy, các phương thức IBinder phải được triển khai để an toàn cho luồng.
Tương tự, trình cung cấp nội dung có thể nhận được các yêu cầu dữ liệu bắt nguồn từ các quy trình khác.
Các lớp ContentResolver và ContentProvider ẩn thông tin chi tiết về cách quản lý hoạt động giao tiếp liên quy trình (IPC), nhưng các phương thức ContentProvider phản hồi những yêu cầu đó (các phương thức query(), insert(), delete(), update() và getType()) được gọi từ một nhóm các luồng trong quy trình của trình cung cấp nội dung, chứ không phải luồng giao diện người dùng cho quy trình. Vì các phương thức này có thể được gọi từ bất kỳ số lượng luồng nào cùng một lúc, nên bạn cũng phải triển khai các phương thức này để đảm bảo an toàn cho luồng.
Giao tiếp liên quy trình
Android cung cấp một cơ chế cho IPC bằng cách sử dụng RPC, trong đó một phương thức được gọi bởi một hoạt động hoặc thành phần ứng dụng khác nhưng được thực thi từ xa trong một quy trình khác, với mọi kết quả được trả về cho phương thức gọi. Việc này bao gồm phân tách một lệnh gọi phương thức và dữ liệu của lệnh gọi đó đến mức hệ điều hành có thể hiểu được, truyền lệnh gọi đó từ quy trình và không gian địa chỉ cục bộ đến quy trình và không gian địa chỉ từ xa, sau đó lắp ráp lại và thực hiện lại lệnh gọi ở đó.
Sau đó, các giá trị trả về sẽ được truyền theo hướng ngược lại. Android cung cấp tất cả mã để thực hiện các giao dịch IPC này, vì vậy, bạn có thể tập trung vào việc xác định và triển khai giao diện lập trình RPC.
Để thực hiện IPC, ứng dụng của bạn phải liên kết với một dịch vụ bằng cách sử dụng bindService(). Để biết thêm thông tin, hãy xem bài viết Tổng quan về các dịch vụ.