กระบวนการของแอปใน Android ไม่ได้ทำงานแยกกัน แอปพลิเคชันมักต้องอาศัยบริการที่แอปพลิเคชันอื่นหรือระบบเองเป็นผู้ให้บริการ เมื่อกระบวนการหนึ่ง เชื่อมต่อกับอีกกระบวนการหนึ่งผ่านการผูกบริการ กระบวนการนั้นจะสร้างการพึ่งพาซึ่งส่งผล อย่างมากต่อวิธีที่เฟรมเวิร์ก Android จัดการหน่วยความจำ
สถานะกระบวนการและคะแนน OOM
เฟรมเวิร์ก Android ใช้สถานะกระบวนการ เพื่อติดตามความสำคัญของแต่ละกระบวนการที่กำลังทำงาน จากนั้น OomAdjuster จะใช้สถานะเหล่านี้เพื่อกำหนดค่าการปรับคะแนน OOM (oom_score_adj) ซึ่งมีค่าตั้งแต่ -1000 ถึง 1000
oom_score_adj ที่ต่ำลงหมายความว่ากระบวนการมีความสำคัญมากขึ้นและมีแนวโน้มน้อยลงที่จะถูกหยุดทำงานโดย Low Memory Killer (LMK)
สถานะกระบวนการทั่วไป
ตารางต่อไปนี้แสดงสถานะกระบวนการที่พบบ่อยที่สุดบางสถานะและค่า oom_score_adj โดยทั่วไป หากต้องการดูรายการที่สมบูรณ์และเป็นปัจจุบัน โปรดดู android.app.ActivityManager และ com.android.server.am.psc.Constants ในซอร์สโค้ด Android
| สถานะกระบวนการ (ตัวย่อ) | คำอธิบาย | oom_score_adj โดยทั่วไป |
|---|---|---|
| PER (ถาวร) | กระบวนการของระบบที่ต้องทำงานอยู่เสมอ (เช่น โทรคมนาคม) | -800 |
| TOP | กระบวนการที่ผู้ใช้กำลังโต้ตอบด้วย | 0 |
| VIS (มองเห็นได้) | กระบวนการมีกิจกรรมที่มองเห็นได้ (เช่น อยู่เบื้องหลังกล่องโต้ตอบโปร่งแสง) | 100 |
| PERC (รับรู้ได้) | กระบวนการเบื้องหลังที่ผู้ใช้รับรู้ (เช่น การเล่นเพลง) | 200 |
| FGS | กระบวนการที่โฮสต์บริการที่ทำงานอยู่เบื้องหน้า | 0 ถึง 200 (แตกต่างกันไป) |
| BTOP (ผูกไว้กับ TOP) | กระบวนการที่แอปพลิเคชัน TOP ผูกไว้ | 100 |
| BFGS | บริการที่ทำงานอยู่เบื้องหน้าที่ผูกไว้ (โดยปกติจะผูกไว้กับระบบ) | 0 |
| PREV (ก่อนหน้า) | กระบวนการล่าสุดที่ผู้ใช้ใช้งานก่อนกระบวนการปัจจุบัน | 700 |
| CACHED | แอปเบื้องหลังที่หยุดทำงานได้อย่างปลอดภัย | 900 ถึง 999 |
ผลกระทบของการผูกบริการ
เมื่อกระบวนการไคลเอ็นต์ (เช่น แอปในสถานะ TOP) ผูกกับบริการในกระบวนการเซิร์ฟเวอร์ กระบวนการเซิร์ฟเวอร์มักจะรับ สิทธิ์เข้าถึงลำดับความสำคัญที่สูงขึ้น ซึ่งจะช่วยให้บริการยังคงพร้อมใช้งานตราบใดที่ไคลเอ็นต์ต้องการ

การควบคุมการรับสิทธิ์เข้าถึงด้วยแฟล็ก BIND
การรับสิทธิ์เข้าถึงเป็นลักษณะการทำงานเริ่มต้นเมื่อใช้ Context.BIND_AUTO_CREATE
อย่างไรก็ตาม นักพัฒนาแอปสามารถควบคุมวิธีที่การผูกส่งผลต่อความสำคัญของกระบวนการเป้าหมายได้โดยใช้แฟล็กต่างๆ ใน bindService()
แฟล็ก BIND หลักสำหรับคะแนน OOM
แฟล็กต่อไปนี้มีความเกี่ยวข้องมากที่สุดเมื่อจัดการแรงกดดันด้านหน่วยความจำทั่วทั้งระบบ
BIND_AUTO_CREATE: แฟล็กที่ใช้บ่อยที่สุด ซึ่งจะช่วยให้กระบวนการบริการเริ่มต้นและทำงานอยู่ตราบใดที่การผูกยังคงอยู่ โดยค่าเริ่มต้น แฟล็กนี้จะเพิ่มลำดับความสำคัญของกระบวนการเซิร์ฟเวอร์ให้ตรงกับไคลเอ็นต์ด้วยBIND_NOT_FOREGROUND: ป้องกันไม่ให้กระบวนการของบริการเป้าหมายได้รับการเพิ่มลำดับความสำคัญในการจัดกำหนดการ เบื้องหน้า (ลำดับความสำคัญของ CPU) อย่างไรก็ตาม แฟล็กนี้ยังคงอนุญาต ให้เพิ่มลำดับความสำคัญของหน่วยความจำ (oom_score_adj) ซึ่งมีประโยชน์สำหรับงานเบื้องหลังที่ไม่ควรแข่งขันกับ UI เพื่อใช้รอบ CPU แต่ควรได้รับการป้องกันไม่ให้ถูกหยุดทำงานBIND_WAIVE_PRIORITY: แฟล็กที่ทรงพลังมากซึ่งสั่งให้ระบบไม่ ส่งผลต่อลำดับความสำคัญในการจัดกำหนดการหรือการจัดการหน่วยความจำของกระบวนการเป้าหมาย ระบบจะจัดการกระบวนการบริการราวกับว่าเป็นกระบวนการเบื้องหลังปกติในรายการ LRU ซึ่งทำให้กระบวนการมีสิทธิ์ถูกหยุดทำงานเนื่องจากหน่วยความจำไม่เพียงพอแม้ว่าจะผูกไว้ก็ตามBIND_ABOVE_CLIENT: ระบุว่าบริการมีความสำคัญมากกว่าแอปไคลเอ็นต์เอง เมื่อระบบต้องการเรียกคืนหน่วยความจำ ระบบจะเลือกหยุดทำงานแอปไคลเอ็นต์ก่อนที่จะหยุดทำงานบริการที่มีผลผูกพัน แฟล็กนี้ "ทรงพลัง" กว่าBIND_AUTO_CREATEเนื่องจากมีการป้องกันบริการเพิ่มเติมโดยแลกกับการทำงานของไคลเอ็นต์BIND_NOT_PERCEPTIBLE: ลดความสำคัญของบริการเป้าหมายให้อยู่ต่ำกว่าPERCEPTIBLEระดับ ซึ่งช่วยให้ระบบเรียกคืนหน่วยความจำเพื่อเพิ่มพื้นที่ว่าง สำหรับกระบวนการที่สำคัญกว่าซึ่งผู้ใช้รับรู้ได้
ลงมือปฏิบัติจริง: การสังเกตผลกระทบของการผูก
เราจะใช้แอปพลิเคชัน MemoryLab เพื่อแสดงให้เห็นว่าการผูกจากแอป TOP ส่งผลต่อสถานะของกระบวนการแยกต่างหากอย่างไร
1. เปิดใช้ MemoryLab
คำสั่งต่อไปนี้จะเปิดใช้แอป หลังจากที่แอปเปิดขึ้นแล้ว ตรวจสอบว่าแอปยังคงทำงานอยู่เบื้องหน้า (อย่ากดปุ่มโฮมหรือสลับแอปในตอนนี้)
adb shell am start -n com.android.memorylab/.MainActivity
2. ระบุกระบวนการ
ตรวจสอบสถานะกระบวนการก่อนการผูก MemoryLab เรียกใช้ UI หลักในกระบวนการหนึ่งและมี RemoteService ที่ทำงานในกระบวนการ :remote
adb shell dumpsys activity processes com.android.memorylab
ตัวอย่างข้อมูลโค้ดเอาต์พุต
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
คุณจะเห็นกระบวนการหลัก com.android.memorylab ในสถานะ TOP กระบวนการ :remote ยังไม่เริ่มต้น
3. ทริกเกอร์การผูก
ส่งการออกอากาศไปยังแอปเพื่อทริกเกอร์การผูกบริการ
adb shell am broadcast -a com.android.memorylab.LEAK_BINDER
4. สังเกตสถานะที่เพิ่มขึ้น
ตรวจสอบสถานะกระบวนการอีกครั้ง
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
ตัวอย่างข้อมูลโค้ดเอาต์พุต
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
ตอนนี้กระบวนการ :remote กำลังทำงานและอยู่ในสถานะ BTOP (ผูกไว้กับ TOP) โดยมี oom_score_adj เป็น 100 ซึ่งได้รับการป้องกันมากกว่าบริการที่ทำงานอยู่เบื้องหลังทั่วไปอย่างมาก (ซึ่งจะมีค่า 500 ขึ้นไป) สัญกรณ์
<=Proc{...} แสดงกระบวนการที่รับผิดชอบในการเพิ่มลำดับความสำคัญนี้
5. ส่งไปยังเบื้องหลัง
กดปุ่มโฮม บนอุปกรณ์ ตรวจสอบสถานะอีกครั้ง
adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"
ตัวอย่างข้อมูลโค้ดเอาต์พุต
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
ตอนนี้กระบวนการทั้ง 2 รายการเปลี่ยนไปอยู่ในสถานะลำดับความสำคัญที่ต่ำกว่า (PREV / oom_score_adj 700) เนื่องจากกระบวนการไคลเอ็นต์ไม่ได้อยู่ในสถานะ TOP อีกต่อไป (หมายเหตุ:
LAST ในการดัมพ์สถานะหมายถึงสถานะภายใน LAST_ACTIVITY ซึ่ง
แมปกับ PREV ในข้อมูลสรุประดับสูง)
การวิเคราะห์ด้วย procstats
เครื่องมือ procstats จะแสดงมุมมองย้อนหลังของสถานะเหล่านี้
# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab
ตัวอย่างข้อมูลโค้ดเอาต์พุต
* 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%
ในที่นี้ Bnd Top ระบุเปอร์เซ็นต์ของเวลาที่กระบวนการระยะไกลใช้ไปในการผูกไว้กับแอปพลิเคชันในสถานะ TOP
การบันทึกและวิเคราะห์การผูกด้วย Perfetto
แม้ว่า dumpsys จะแสดงภาพรวม แต่ Perfetto ช่วยให้คุณเห็นช่วงเวลาที่เกิดการผูกและวิธีที่คะแนน OOM เปลี่ยนไปแบบเรียลไทม์ได้อย่างแม่นยำ
1. บันทึกการติดตาม
ใช้การกำหนดค่าที่มี linux.process_stats และหมวดหมู่ 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. ค้นหาการเปลี่ยนคะแนน OOM
การใช้ PerfettoSQL จะช่วยให้คุณเห็นวิธีที่คะแนน OOM ของกระบวนการระยะไกลเปลี่ยนไปเมื่อเทียบกับกระบวนการ UI
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. ระบุเหตุการณ์การผูก
หากต้องการดูเวลาที่สร้างการพึ่งพาการผูกและกระบวนการที่เริ่มต้นการผูกอย่างแม่นยำ ให้ใช้การค้นหานี้
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%';
การผูกจากระบบไปยังแอป
ระบบ Android เองมักจะผูกกับบริการในแอปของบุคคลที่สามเพื่อมอบฟังก์ชันหลัก โดยเป้าหมายของการผูกเหล่านี้มักจะเป็นการลดเวลาในการตอบสนอง การทำให้กระบวนการทำงานและอยู่ในหน่วยความจำจะช่วยให้ระบบหลีกเลี่ยงค่าใช้จ่ายที่สูงของ "Cold Start" (การโหลด APK, การเริ่มต้นรันไทม์ และการสร้างออบเจ็กต์แอปพลิเคชัน) เมื่อเกิดการโต้ตอบของผู้ใช้ที่สำคัญ นอกจากนี้ยังมีการผูกอื่นๆ เพื่อป้องกันการเริ่มต้นแบบเย็นบ่อยครั้งสำหรับแอปที่ต้องจัดการสตรีมเหตุการณ์เบื้องหลัง
ต่อไปนี้คือตัวอย่างในโลกแห่งความเป็นจริงที่คุณสังเกตได้ในอุปกรณ์ทั่วไป
VoiceInteractor
ผู้ใช้คาดหวังว่าผู้ช่วยดิจิทัลจะฝังอยู่ในระบบปฏิบัติการของโทรศัพท์ สามารถเรียกผู้ช่วยได้ทันทีด้วยคำสั่งเสียงหรือท่าทางสัมผัสป้อนข้อมูลอย่างรวดเร็ว และการโต้ตอบจะเป็นไปอย่างราบรื่น
เมื่อเกิดทริกเกอร์ผู้ช่วย (เช่น คำสั่งเสียง "Ok Google" ในโทรศัพท์ Google Pixel) ผู้ช่วยดิจิทัลต้องตอบสนองทันที เพื่อให้เป็นเช่นนี้
system_server จะรักษาการผูกถาวรกับบริการการโต้ตอบด้วยเสียง
ที่ผู้ใช้เลือก

หากคุณตรวจสอบสถานะกระบวนการ (เช่น ใช้ dumpsys activity processes) คุณอาจเห็นกระบวนการอย่าง com.google.android.googlequicksearchbox:interactor ในสถานะ BFGS (บริการที่ทำงานอยู่เบื้องหน้าที่ผูกไว้) ซึ่งทำงานอยู่โดยการผูกจาก system_server (UID 1000)
NotificationListenerService
สำหรับการผูกจากระบบไปยังแอปบางรายการ เป้าหมายไม่ใช่เวลาในการตอบสนอง แต่เป็นการป้องกันการเริ่มต้นแบบเย็นบ่อยครั้ง
NotificationListenerServiceซึ่งเป็นบริการที่รับการเรียกจากระบบเมื่อมีการโพสต์หรือนำการแจ้งเตือนใหม่ออก เป็นตัวอย่างที่สำคัญ ผู้ใช้สมาร์ทโฟนทั่วไปอาจได้รับการแจ้งเตือนหลายร้อยรายการตลอดทั้งวัน หากระบบยกเลิกการผูกจาก Listener การแจ้งเตือน กระบวนการของแอปนั้นมีแนวโน้มที่จะเปลี่ยนไปอยู่ในสถานะแคชและอาจถูกหยุดทำงานโดย LMK
เมื่อมีการแจ้งเตือนครั้งถัดไปมาถึง ซึ่งอาจใช้เวลาเพียงไม่กี่วินาที ระบบจะถูกบังคับให้ Cold Start กระบวนการของแอปอีกครั้งเพียงเพื่อส่งเหตุการณ์ วงจรการหยุดทำงานและการเริ่มต้นแบบเย็นอย่างต่อเนื่องนี้จะใช้ CPU และแบตเตอรี่มากกว่าการผูกกระบวนการและทำให้กระบวนการทำงานอยู่เบื้องหลัง
หน้าจอ "-1" ของ Launcher (ฟีดข่าว)
แอป Launcher สมัยใหม่มักจะรวมฟังก์ชันการทำงานหลักในการนำทาง (ไอคอนและวิดเจ็ตหน้าแรก) เข้ากับฟีดข่าวที่พร้อมใช้งานในหน้าจอ Launcher หน้าจอหนึ่งและผสานรวมกับ UX ของ Launcher ได้อย่างราบรื่น แอปอื่นอาจเป็นผู้ให้บริการฟีดข่าว ตัวอย่างเช่น ใน Google Pixel, Launcher จะผสานรวมกับฟีดที่แอป Google เป็นผู้ให้บริการ
เมื่อคุณปัดไปทางซ้ายในหน้าจอหลักเพื่อดูฟีดข่าว การเปลี่ยนหน้าจอต้องราบรื่น Launcher ทำได้โดยการผูกกับอินเทอร์เฟซบริการในแอปที่ให้บริการฟีดข่าว และรักษาการผูกนั้นให้ทำงานอยู่ตราบใดที่ Launcher ยังทำงานอยู่ ซึ่งจะทำให้เนื้อหาฟีดแสดงผลและพร้อมใช้งานในหน่วยความจำแม้ว่าคุณจะไม่ได้ดูอยู่ก็ตาม
ตัวอย่างทั่วไปอื่นๆ
- Launcher (HOME_APP_ADJ): แอป Launcher (หน้าแรก) มีช่องพิเศษของตัวเอง
ในรายการลำดับความสำคัญ แม้ว่าจะไม่ได้ผูกไว้กับบริการเสมอไป แต่ระบบจะกำหนด
HOME_APP_ADJ(โดยปกติคือ 600) ให้ ระบบจะเลือกทำให้ Launcher ทำงานอยู่เสมอ เนื่องจากผู้ใช้กลับมาใช้ Launcher บ่อยครั้ง ในความเป็นจริง ระบบจะเลือกหยุดทำงานแอปที่ใช้ก่อนหน้านี้ (PREV_APP_ADJ = 700) มากกว่าที่จะหยุดทำงาน Launcher เนื่องจากหากหยุดทำงาน Launcher จะทำให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ช้าลงเมื่อออกจากแอปใดก็ตาม เนื่องจากผู้ใช้จะต้องรอให้ Launcher เริ่มต้นแบบเย็น - ตัวแก้ไขวิธีการป้อนข้อมูล (IME): เมื่อคุณพิมพ์ ระบบจะผูกกับแอปแป้นพิมพ์ที่คุณ เลือก (เช่น Gboard) ซึ่งจะทำให้กระบวนการแป้นพิมพ์อยู่ในสถานะที่เพิ่มขึ้นแม้ว่าแป้นพิมพ์จะซ่อนอยู่ชั่วคราวก็ตาม ซึ่งจะช่วยให้แป้นพิมพ์ปรากฏขึ้นอีกครั้งได้ทันทีเมื่อคุณแตะช่องข้อความอื่น
- การชำระเงินผ่าน NFC: เมื่อคุณแตะโทรศัพท์เพื่อชำระเงิน ระบบจะเชื่อมโยงกับบริการชำระเงิน (เช่น Google Wallet) ธุรกรรมเหล่านี้มักมีข้อกำหนดแบบเรียลไทม์ที่เข้มงวดจากเทอร์มินัลของร้านค้า หากแอปการชำระเงินต้อง Cold Start ธุรกรรมอาจหมดเวลาและล้มเหลว
ข้อดีข้อเสียและจุดที่ประสิทธิภาพตกฮวบ
แม้ว่าการผูกจะจำเป็นต่อประสิทธิภาพและความถูกต้อง แต่ก็ส่งผลเสียต่อสุขภาพหน่วยความจำของระบบ
- ความยืดหยุ่นลดลง: กระบวนการที่ผูกไว้ทุกกระบวนการคือกระบวนการที่ LMK ไม่ สามารถหยุดทำงานได้อย่างง่ายดาย ซึ่งจะลด "การรองรับ" ของกระบวนการที่แคชไว้ซึ่งระบบสามารถใช้เพื่อเพิ่มหน่วยความจำว่างเมื่อมีแรงกดดัน
- ทำให้จุดที่ประสิทธิภาพตกฮวบรุนแรงขึ้น: หากมีการผูกกระบวนการมากเกินไป ระบบอาจพบว่าไม่มีกระบวนการเบื้องหลังที่หยุดทำงานได้เลย เมื่อแรงกดดันด้านหน่วยความจำเพิ่มขึ้น ระบบจะ "ตกจากจุดที่ประสิทธิภาพตกฮวบ" เร็วขึ้นมาก เนื่องจากถูกบังคับให้หยุดทำงานกระบวนการที่สำคัญกว่าหรือทำให้แคชของเพจเกิดการสลับไปมา
← ความเฉพาะเจาะจง | ↑ ขึ้น | ทั่วทั้งระบบ →