Các quy trình ứng dụng trên Android không tồn tại độc lập. Các ứng dụng thường dựa vào các dịch vụ do các ứng dụng khác hoặc chính hệ thống cung cấp. Khi một quy trình kết nối với một quy trình khác thông qua Service Binding (Liên kết dịch vụ), quy trình đó sẽ tạo ra một phần phụ thuộc có ảnh hưởng sâu sắc đến cách khung Android quản lý bộ nhớ.
Trạng thái xử lý và điểm OOM
Khung Android sử dụng Trạng thái quy trình để theo dõi mức độ quan trọng của từng quy trình đang chạy. Sau đó, OomAdjuster sẽ dùng các trạng thái này để chỉ định giá trị Mức điều chỉnh điểm OOM (oom_score_adj), trong khoảng từ -1000 đến 1000.
oom_score_adj càng thấp thì quá trình càng quan trọng và ít có khả năng bị Low Memory Killer (LMK) dừng.
Các trạng thái quy trình phổ biến
Bảng sau đây cho thấy một số trạng thái quy trình phổ biến nhất và các giá trị oom_score_adj điển hình của chúng. Để xem danh sách đầy đủ và mới nhất, hãy tham khảo android.app.ActivityManager và com.android.server.am.psc.Constants trong mã nguồn Android.
| Trạng thái xử lý (Viết tắt) | Mô tả | oom_score_adj thông thường |
|---|---|---|
| PER (Liên tục) | Các quy trình hệ thống luôn phải chạy (ví dụ: Điện thoại). | -800 |
| TOP | Quy trình mà người dùng hiện đang tương tác. | 0 |
| VIS (Visible) | Quy trình này có một hoạt động hiển thị (ví dụ: đằng sau một hộp thoại mờ). | 100 |
| PERC (Có thể nhận biết) | Quy trình chạy trong nền mà người dùng biết (ví dụ: phát nhạc). | 200 |
| FGS | Quy trình lưu trữ một Dịch vụ trên nền trước. | 0 đến 200 (tuỳ theo) |
| BTOP (Bound Top) | Quy trình bị ràng buộc bởi một ứng dụng TOP. | 100 |
| BFGS | Dịch vụ trên nền trước được liên kết (thường là được liên kết với hệ thống). | 0 |
| TRƯỚC | Quy trình cuối cùng mà người dùng đã thực hiện trước quy trình hiện tại. | 700 |
| ĐƯỢC LƯU VÀO BỘ NHỚ ĐỆM | Các ứng dụng nền có thể bị loại bỏ một cách an toàn. | 900 đến 999 |
Tác động của các liên kết dịch vụ
Khi một quy trình ứng dụng (ví dụ: một ứng dụng ở trạng thái TOP) liên kết với một dịch vụ trong quy trình máy chủ, quy trình máy chủ thường kế thừa mức độ ưu tiên cao. Điều này đảm bảo rằng dịch vụ vẫn hoạt động miễn là ứng dụng cần.

Kiểm soát việc kế thừa bằng cờ BIND
Tính kế thừa là hành vi mặc định khi sử dụng Context.BIND_AUTO_CREATE.
Tuy nhiên, nhà phát triển có thể kiểm soát mức độ ảnh hưởng của hoạt động liên kết đến tầm quan trọng của quy trình đích bằng nhiều cờ trong bindService().
Các cờ BIND chính cho điểm OOM
Các cờ sau đây phù hợp nhất khi quản lý áp lực bộ nhớ trên toàn hệ thống:
BIND_AUTO_CREATE: Cờ phổ biến nhất. Điều này đảm bảo quy trình dịch vụ được bắt đầu và duy trì hoạt động miễn là có liên kết. Theo mặc định, nó cũng nâng mức độ ưu tiên của quy trình máy chủ để phù hợp với ứng dụng.BIND_NOT_FOREGROUND: Ngăn quy trình của dịch vụ mục tiêu được nâng lên mức độ ưu tiên lập lịch trên nền trước (mức độ ưu tiên CPU). Tuy nhiên, nó vẫn cho phép tăng mức độ ưu tiên bộ nhớ (oom_score_adj). Điều này hữu ích cho những tác vụ chạy ở chế độ nền không nên cạnh tranh với giao diện người dùng để có được các chu kỳ CPU nhưng vẫn phải được bảo vệ khỏi bị huỷ.BIND_WAIVE_PRIORITY: Một cờ rất mạnh hướng dẫn hệ thống không ảnh hưởng đến mức độ ưu tiên lập lịch hoặc quản lý bộ nhớ của quy trình đích. Quy trình dịch vụ sẽ được quản lý như thể đó là một quy trình nền thông thường trong danh sách LRU, khiến quy trình này đủ điều kiện để bị OOM loại bỏ ngay cả khi được liên kết.BIND_ABOVE_CLIENT: Cho biết dịch vụ này quan trọng hơn chính ứng dụng khách. Khi cần thu hồi bộ nhớ, hệ thống sẽ ưu tiên huỷ ứng dụng khách trước khi huỷ dịch vụ ràng buộc. Đây là một giải pháp "mạnh mẽ" hơnBIND_AUTO_CREATEvì nó cung cấp thêm một lớp bảo vệ cho dịch vụ mà không ảnh hưởng đến máy khách.BIND_NOT_PERCEPTIBLE: Giảm mức độ quan trọng của dịch vụ mục tiêu xuống dưới cấpPERCEPTIBLE, cho phép hệ thống thu hồi bộ nhớ để nhường chỗ cho các quy trình quan trọng hơn mà người dùng có thể nhận thấy.
Thực hành: quan sát các hiệu ứng liên kết
Chúng ta sẽ sử dụng ứng dụng MemoryLab để minh hoạ cách một liên kết từ ứng dụng TOP ảnh hưởng đến trạng thái của một quy trình riêng biệt.
1. Khởi chạy MemoryLab
Lệnh sau đây sẽ khởi chạy ứng dụng. Sau khi ứng dụng mở, hãy đảm bảo ứng dụng vẫn ở chế độ nền trước (chưa nhấn nút Trang chủ hoặc chuyển đổi ứng dụng).
adb shell am start -n com.android.memorylab/.MainActivity
2. Xác định quy trình
Kiểm tra trạng thái của quy trình trước khi liên kết. MemoryLab chạy giao diện người dùng chính trong một quy trình và có một RemoteService chạy trong quy trình :remote.
adb shell dumpsys activity processes com.android.memorylab
Đoạn mã đầu ra mẫu:
Process OOM control (154 total, non-act at 7, non-svc at 7):
Proc #0: fg T/A/TOP LCMNFUATI t: 0 13470:com.android.memorylab/u0a417 (top-activity)
oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
state: cur=TOP set=TOP lastRss=0.00 lastCachedRss=0.00
Bạn sẽ thấy quy trình chính com.android.memorylab ở trạng thái TOP. Quy trình :remote chưa bắt đầu.
3. Liên kết điều kiện kích hoạt
Gửi một thông báo truyền tin đến ứng dụng để kích hoạt việc liên kết dịch vụ:
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. Quan sát trạng thái nâng cao
Kiểm tra lại trạng thái của quy trình:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Đoạn mã đầu ra mẫu:
Proc # 1: vis F/ /BTOP ---NFUATI t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
state: cur=BTOP set=BTOP lastRss=0.00 lastCachedRss=0.00
Quy trình :remote hiện đang chạy và ở trạng thái BTOP (Bound TOP) với oom_score_adj là 100. Điều này được bảo vệ hơn đáng kể so với một dịch vụ nền thông thường (sẽ ở 500 trở lên). Ký hiệu <=Proc{...} cho biết quy trình nào chịu trách nhiệm cho việc tăng mức độ ưu tiên này.
5. Chuyển sang phát trong nền
Nhấn nút HOME (MÀN HÌNH CHÍNH) trên thiết bị. Kiểm tra lại các trạng thái:
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
Đoạn mã đầu ra mẫu:
Proc # 2: prev b/ /LAST --------I t: 0 13560:com.android.memorylab:remote/u0a417 (service)
com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=0.00 lastCachedRss=0.00
Proc # 1: prev b/ /LAST --------I t: 0 13470:com.android.memorylab/u0a417 (previous)
oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
state: cur=LAST set=LAST lastRss=209MB lastCachedRss=0.00
Giờ đây, cả hai quy trình đều chuyển sang trạng thái có mức độ ưu tiên thấp hơn (PREV / oom_score_adj 700), vì quy trình của ứng dụng khách không còn là TOP nữa. (Lưu ý: LAST trong kết xuất trạng thái đề cập đến trạng thái nội bộ LAST_ACTIVITY, được liên kết với PREV trong các bản tóm tắt cấp cao).
Phân tích bằng procstats
Công cụ procstats cung cấp chế độ xem nhật ký của các trạng thái này.
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
Đoạn mã đầu ra mẫu:
* com.android.memorylab / u0a417 / v37:
* Prc com.android.memorylab / u0a417 / v37:
TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
* Prc com.android.memorylab:remote / u0a417 / v37:
TOTAL: 0.19%
Bnd Top: 0.19%
Ở đây, Bnd Top cho biết tỷ lệ phần trăm thời gian mà quy trình từ xa đã dành để được một ứng dụng liên kết ở trạng thái TOP.
Ghi lại và phân tích các liên kết bằng Perfetto
Trong khi dumpsys cung cấp cho bạn thông tin tổng quan, Perfetto cho phép bạn xem chính xác thời điểm xảy ra một hoạt động liên kết và cách điểm OOM thay đổi theo thời gian thực.
1. Ghi lại dấu vết
Sử dụng cấu hình bao gồm linux.process_stats và danh mục am atrace:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
config {
name: "linux.process_stats"
process_stats_config { proc_stats_poll_ms: 100 }
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config { ftrace_events: "am/am_proc_bound" }
}
}
duration_ms: 15000
EOF
2. Truy vấn các lượt chuyển đổi điểm OOM
Khi dùng PerfettoSQL, bạn có thể thấy điểm OOM của quy trình từ xa đã thay đổi như thế nào so với quy trình giao diện người dùng:
SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
AND t.name = 'oom_score_adj'
ORDER BY ts;
3. Xác định các sự kiện liên kết
Để biết chính xác thời điểm thiết lập một phần phụ thuộc liên kết và quy trình nào đã khởi tạo phần phụ thuộc đó, hãy sử dụng truy vấn này:
SELECT
s.ts,
p.name AS process_name,
t.name AS thread_name,
s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';
Liên kết từ hệ thống đến ứng dụng
Bản thân hệ thống Android thường liên kết với các dịch vụ trong ứng dụng bên thứ ba để cung cấp chức năng cốt lõi. Mục tiêu của các liên kết này thường là giảm độ trễ. Bằng cách duy trì một quy trình đang hoạt động và trong bộ nhớ, hệ thống tránh được chi phí phát sinh lớn của quá trình "khởi động nguội" (tải APK, khởi động thời gian chạy và tạo đối tượng Ứng dụng) khi xảy ra một hoạt động tương tác quan trọng của người dùng. Các liên kết khác tồn tại để ngăn chặn việc khởi động nguội thường xuyên cho những ứng dụng cần xử lý luồng sự kiện trong nền.
Sau đây là một số ví dụ thực tế mà bạn có thể quan sát trên một thiết bị thông thường:
VoiceInteractor
Người dùng mong muốn có một trợ lý kỹ thuật số được nhúng trong hệ điều hành điện thoại, có thể gọi trợ lý này ngay lập tức bằng một từ khoá kích hoạt bằng giọng nói hoặc một cử chỉ nhập nhanh, đồng thời hoạt động tương tác diễn ra suôn sẻ và liền mạch.
Khi có một lệnh kích hoạt trợ lý (chẳng hạn như cụm từ kích hoạt "OK Google" trên điện thoại Google Pixel), trợ lý kỹ thuật số cần phản hồi ngay lập tức. Để đảm bảo điều này, system_server duy trì một mối liên kết vĩnh viễn với dịch vụ tương tác bằng giọng nói do người dùng chọn.

Nếu kiểm tra trạng thái của quy trình (ví dụ: bằng cách dùng dumpsys activity processes), bạn có thể thấy một quy trình như com.google.android.googlequicksearchbox:interactor ở trạng thái BFGS (Dịch vụ liên kết trên nền trước), được duy trì hoạt động bằng một liên kết từ system_server (UID 1000).
NotificationListenerService
Đối với một số liên kết từ hệ thống đến ứng dụng, mục tiêu không phải là độ trễ mà là ngăn chặn tình trạng khởi động nguội thường xuyên.
NotificationListenerService, một dịch vụ nhận các lệnh gọi từ hệ thống khi thông báo mới được đăng hoặc bị xoá, là một ví dụ điển hình. Một người dùng điện thoại thông minh điển hình có thể nhận được hàng trăm thông báo trong suốt cả ngày. Nếu hệ thống huỷ liên kết khỏi một trình nghe thông báo, thì quy trình của ứng dụng đó có thể chuyển sang trạng thái được lưu vào bộ nhớ đệm và có thể bị LMK huỷ.
Khi thông báo tiếp theo đến (có thể chỉ vài giây sau), hệ thống sẽ buộc phải khởi động nguội quy trình của ứng dụng một lần nữa chỉ để gửi sự kiện. Chu kỳ liên tục này của việc tắt và khởi động lại sẽ tiêu tốn nhiều CPU và pin hơn so với việc chỉ giữ cho quy trình được liên kết và hoạt động trong nền.
"Màn hình -1" (nguồn cấp dữ liệu tin tức) của trình chạy
Các ứng dụng Trình chạy hiện đại thường kết hợp chức năng điều hướng cốt lõi (các biểu tượng và tiện ích trên màn hình chính) với một nguồn cấp tin tức có trên một trong các màn hình trình chạy và được tích hợp liền mạch với UX Trình chạy. Nguồn cấp tin tức có thể do một ứng dụng khác cung cấp. Ví dụ: trên Google Pixel, Trình chạy tích hợp với một nguồn cấp dữ liệu do ứng dụng Google cung cấp.
Khi bạn vuốt sang trái trên màn hình chính để xem nguồn cấp dữ liệu tin tức, hiệu ứng chuyển đổi phải mượt mà. Trình chạy đạt được điều này bằng cách liên kết với một giao diện dịch vụ trong ứng dụng cung cấp nguồn cấp tin tức và duy trì mối liên kết đó trong thời gian trình chạy hoạt động. Việc này giúp nội dung trong nguồn cấp dữ liệu được kết xuất và sẵn sàng trong bộ nhớ ngay cả khi bạn không xem.
Các ví dụ phổ biến khác
- Trình chạy (HOME_APP_ADJ): Ứng dụng trình chạy (Home) có một vị trí đặc biệt trong danh sách ưu tiên. Mặc dù không phải lúc nào cũng bị ràng buộc bởi một dịch vụ, nhưng nó được chỉ định
HOME_APP_ADJ(thường là 600). Hệ thống ưu tiên duy trì hoạt động của trình chạy, vì người dùng thường xuyên quay lại trình chạy. Trên thực tế, hệ thống sẽ ưu tiên dừng ứng dụng đã dùng trước đó (PREV_APP_ADJ = 700) hơn là dừng trình chạy, vì việc dừng trình chạy sẽ khiến người dùng trải nghiệm chậm chạp khi thoát bất kỳ ứng dụng nào vì người dùng sẽ cần đợi trình chạy khởi động nguội. - Trình chỉnh sửa phương thức nhập (IME): Khi bạn nhập, hệ thống sẽ liên kết với ứng dụng bàn phím mà bạn chọn (ví dụ: Gboard). Điều này giúp duy trì quy trình bàn phím ở trạng thái nâng cao ngay cả khi bàn phím tạm thời bị ẩn. Điều này đảm bảo bàn phím có thể xuất hiện lại ngay lập tức khi bạn nhấn vào một trường văn bản khác.
- Thanh toán qua NFC: Khi bạn chạm điện thoại để thanh toán, hệ thống sẽ liên kết với dịch vụ thanh toán NFC (ví dụ: Google Wallet). Các giao dịch này thường có yêu cầu nghiêm ngặt về thời gian thực từ thiết bị đầu cuối của người bán. Nếu ứng dụng thanh toán phải khởi động nguội, thì giao dịch có thể hết thời gian chờ và không thành công.
Những điểm lợi, hại và hiệu suất giảm sút
Mặc dù các liên kết là cần thiết để đảm bảo hiệu suất và tính chính xác, nhưng chúng ảnh hưởng đến tình trạng bộ nhớ của hệ thống.
- Giảm tính linh hoạt: Mọi quy trình liên kết đều là quy trình mà LMK không dễ dàng loại bỏ. Điều này làm giảm "khoảng đệm" của các tiến trình lưu vào bộ nhớ đệm mà hệ thống có thể dùng để giải phóng bộ nhớ khi chịu áp lực.
- Làm trầm trọng thêm tình trạng suy giảm hiệu suất: Nếu có quá nhiều quy trình được liên kết, hệ thống có thể thấy rằng hầu như không có quy trình nào ở chế độ nền có thể bị loại bỏ. Khi áp lực bộ nhớ tăng lên, hệ thống sẽ "giảm hiệu suất" nhanh hơn nhiều, vì hệ thống buộc phải loại bỏ các quy trình quan trọng hơn hoặc xoá bộ nhớ đệm trang.
← Địa phương | ↑ Lên | Toàn hệ thống →