Khi một thành phần của ứng dụng khởi động và ứng dụng không có thành phần nào khác hệ thống Android sẽ khởi động một quy trình Linux mới cho ứng dụng bằng một luồng duy nhất thực thi. 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ó tên 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 từ ứng dụng đã khởi động, thì thành phần đó 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ể thu xếp để các thành phần khác nhau trong ứng dụng của bạn để chạy trong các tiến trình riêng biệt. Đồng thời, bạn có thể tạo thêm cho bất kỳ quy trình nào.
Tài liệu này thảo luận cách hoạt động của các quy trình và luồng trong ứng dụng Android.
Quá trình
Theo mặc định, tất cả thành phần của ứng dụng đều chạy trong cùng một quy trình và hầu hết các ứng dụng đừng thay đổi thông tin này. Tuy nhiên, nếu thấy cần kiểm soát quá trì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 mỗi loại phần tử thành phần – <activity>
, <service>
, <receiver>
và <provider>
– hỗ trợ thuộc tính android:process
có thể chỉ định một
xử lý thành phần chạy vào. 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 của nó hoặc do đó một số thành phần dùng chung một quy trình trong khi các 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 nhiều ứng dụng chạy trên cùng một ứng dụng
miễn là các ứng dụng có cùng mã nhận dạng người dùng Linux và được ký bằng
cùng một chứng chỉ.
<application>
cũng hỗ trợ thuộc tính android:process
mà bạn có thể sử dụng để đặ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 vào một thời điểm nào đó khi tài nguyên theo yêu cầu của các quy trình phân phối ngay lập tức cho người dùng. Mục đích sử dụng do đó các thành phần đang chạy trong quá trình tắt sẽ bị huỷ bỏ. Đã bắt đầu một quy trình cho các thành phần đó khi chúng cần phải làm việc.
Khi quyết định tắt quy trình nào, hệ thống Android cân nhắc tầm quan trọng tương đối của chúng với người dùng. Ví dụ: tính năng này dễ dàng tắt một quy trình lưu trữ các hoạt động không còn 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ị. Quyết định có chấm dứt một tiến trình, do đó 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 nó với các trạng thái của ứng dụng được thảo luận trong Quy trình và vòng đời của ứng dụng.
Luồng
Khi chạy một ứng dụng, hệ thống sẽ tạo một luồng thực thi cho ứng dụng,
có tên là luồng chính. Luồng này rất quan trọng vì chịu trách nhiệm gửi các sự kiện đến
các tiện ích giao diện người dùng thích hợp, bao gồm cả các sự kiện vẽ. Điều này cũng
hầu 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
qua android.widget
của bộ công cụ giao diện người dùng Android và
android.view
gói.
Vì lý do này, đôi khi luồng chính
có tên là luồng giao diện người dùng. Tuy nhiên, trong các trường hợp đặc biệt, vai trò chính của
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 Thread
chú thích.
Hệ thống không tạo luồng riêng cho mỗi thực thể 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 đượ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
mỗi thành phần sẽ được gửi đi từ luồng đó. Do đó, các phương thức phản hồi lại hệ thống
lệnh gọi lại, 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 nhấn 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 vào tiện ích, sự kiện này sẽ đặt trạng thái nhấn và đăng yêu cầu vô hiệu hoá thành hàng đợi sự kiện. Luồng giao diện người dùng sẽ xếp yêu cầu vào hàng đợi và thông báo cho tiện ích vẽ lại .
Mô hình đơn luồng này trừ phi bạn triển khai ứng dụng đúng cách có thể mang lại hiệu suất kém khi ứng dụng của bạn thực hiện nhiều công việc để phản hồi tương tác của người dùng. 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 các truy vấn cơ sở dữ liệu, chặn toàn bộ giao diện người dùng. Khi luồng bị chặn, không thể gửi sự kiện nào, bao gồm cả sự kiện vẽ.
Từ góc độ của người dùng, việc ứng dụng dường như bị treo. Thậm chí còn tệ hơn, nếu chuỗi giao diện người dùng bị chặn trong hơn vài giây, người dùng thấy thông báo lỗi "ứng dụng không " (ANR). Sau đó, người dùng có thể quyết định thoát khỏi ứng dụng của bạn hoặc thậm chí gỡ cài đặt nó.
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úng giao diện người dùng từ một luồng worker. Thực hiện mọi thao tác đối với giao diện người dùng từ giao diện người dùng chuỗi. Có 2 quy tắc cho mô hình đơn luồng của Android:
- Đừ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 đơn luồng này, điều quan trọng là khả năng phản hồi của giao diện người dùng của ứng dụng mà bạn không chặn luồng giao diện người dùng. Nếu bạn cần thực hiện các thao tác không diễn ra ngay lập tức, hãy nhớ thực hiện chúng trong nền riêng biệt hoặc worker. 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ừ luồng. Dưới đây là danh sách các phương thức có thể giúp ích cho bạn:
Ví dụ sau đây sử dụng View.post(Runnable)
:
Kotlin
fun onClick(v: View) { Thread(Runnable { // A potentially time consuming task. val bitmap = processBitMap("image.png") imageView.post { imageView.setImageBitmap(bitmap) } }).start() }
Java
public void onClick(View v) { new Thread(new Runnable() { public void run() { // A potentially time consuming task. final Bitmap bitmap = processBitMap("image.png"); imageView.post(new Runnable() { public void run() { imageView.setImageBitmap(bitmap); } }); } }).start(); }
Cách triển khai này an toàn cho luồng vì hoạt động ở chế độ nền được thực hiện qua một luồng riêng biệt
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 Handler
trong luồng worker
để xử lý thông báo được gửi từ luồng giao diện người dùng. Để xem nội dung giải thích đầy đủ về cách
lên lịch công việc trên các luồng ở chế độ nền và giao tiếp trở lại luồng giao diện người dùng, hãy xem
Tổng quan về công việc trong nền.
Các 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, và do đó phải được ghi để an toàn cho luồng.
Điều này chủ yếu đúng đối với các phương thức có thể gọi từ xa, chẳng hạn như các phương thức trong dịch vụ ràng buộc. Khi một cuộc gọi trên
phương thức được triển khai trong IBinder
bắt nguồn trong cùng một quy trình mà
IBinder
đang chạy, phương thức này đượ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 trong một quy trình khác, phương thức này sẽ thực thi trong một luồng đã chọn từ
một nhóm luồng mà hệ thống duy trì trong cùng một quy trình như IBinder
.
Không được thực thi trong luồng giao diện người dùng của quy trình này.
Ví dụ: trong khi dịch vụ
Phương thức onBind()
được gọi từ luồng giao diện người dùng của
quy trình của 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 gọi quy trình từ xa (RPC) được gọi từ các luồng
trong bể bơi. Vì một dịch vụ có thể có nhiều khách hàng, nên nhiều chuỗi nhóm có thể tương tác với nhau
cùng một phương thức IBinder
tại cùng một thời điểm, 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 yêu cầu dữ liệu bắt nguồn từ các quy trình khác.
ContentResolver
và ContentProvider
các lớp ẩn chi tiết về cách quản lý giao tiếp liên quy trình (IPC)
nhưng phương thức ContentProvider
phản hồi các yêu cầu đó – phương thức
query()
,
insert()
,
delete()
,
update()
,
và getType()
—là
đượ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 từ giao diện người dùng
luồng cho quy trình. Vì các phương thức này có thể được gọi từ số lượng luồng bất kỳ tại
đồng thời, các giá trị này cũng phải được triển khai để an toàn cho luồng.
Giao tiếp liên quy trình
Android cung cấp cơ chế cho IPC bằng RPC, trong đó một phương thức được hoạt động hoặc ứng dụng khác gọi thành phần nhưng lại được thực thi từ xa trong một quy trình khác, với bất kỳ kết quả nào đều được trả về người gọi. Quá trình này đòi hỏi phải phân tách một lệnh gọi phương thức và dữ liệu của lệnh gọi phương thức đó đến một cấp độ mà hệ điều hành có thể hiểu, truyền dữ liệu từ quy trình và không gian địa chỉ cục bộ đến quá trình từ xa và không gian địa chỉ của bạn rồi tập hợp và tái tạo cuộc gọi ở đó.
Giá trị trả về sẽ là sau đó được truyền theo hướng ngược lại. Android cung cấp tất cả các mã để thực hiện các IPC này giao dịch, do đó 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 bindService()
. Để biết thêm thông tin, hãy xem bài viết Tổng quan về dịch vụ.