เมื่อเธรด UI ของแอป Android ถูกบล็อกนานเกินไป ระบบจะทริกเกอร์ข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR) หากแอปอยู่เบื้องหน้า ระบบจะแสดงกล่องโต้ตอบแก่ผู้ใช้ ดังที่แสดงในรูปที่ 1 กล่องโต้ตอบ ANR จะให้โอกาสผู้ใช้ในการบังคับปิดแอป
ANR เป็นปัญหาเนื่องจากชุดข้อความหลักของแอปซึ่งมีหน้าที่อัปเดต UI ไม่สามารถประมวลผลเหตุการณ์การป้อนข้อมูลของผู้ใช้หรือวาดภาพได้ ซึ่งทำให้ผู้ใช้หงุดหงิด ดูข้อมูลเพิ่มเติมเกี่ยวกับเทรดหลักของแอปได้ที่ภาพรวมกระบวนการและเทรด
ระบบจะทริกเกอร์ ANR สำหรับแอปของคุณเมื่อเกิดเงื่อนไขอย่างใดอย่างหนึ่งต่อไปนี้
- การส่งอินพุตหมดเวลา: หากแอปไม่ตอบสนองต่อเหตุการณ์อินพุต (เช่น การกดแป้นหรือการแตะหน้าจอ) ภายใน 5 วินาที
- การเรียกใช้บริการ: หากบริการที่แอปของคุณประกาศไม่สามารถเรียกใช้
Service.onCreateและService.onStartCommand/Service.onBindให้เสร็จภายในไม่กี่วินาที - ไม่ได้เรียกใช้
Service.startForeground: หากแอปใช้Context.startForegroundServiceเพื่อเริ่มบริการใหม่ในเบื้องหน้า แต่บริการไม่เรียกใช้startForegroundภายใน 5 วินาที - การประกาศ Intent: หาก
BroadcastReceiverทำงานไม่เสร็จภายในระยะเวลาที่กำหนด หากแอปมีกิจกรรมในเบื้องหน้า ระยะหมดเวลานี้จะเท่ากับ 5 วินาที - การโต้ตอบ
JobScheduler: หากJobServiceไม่แสดงผลจากJobService.onStartJobหรือJobService.onStopJobภายในไม่กี่วินาที หรือหากงานที่ผู้ใช้เริ่มเริ่มต้นขึ้นและแอปของคุณไม่เรียกใช้JobService.setNotificationภายในไม่กี่วินาทีหลังจากเรียกใช้JobService.onStartJobสำหรับแอปที่กำหนดเป้าหมายเป็น Android 13 และต่ำกว่า ANR จะเกิดขึ้นโดยไม่มีการแจ้งเตือนและไม่มีการรายงานไปยังแอป สำหรับแอปที่กำหนดเป้าหมายเป็น Android 14 ขึ้นไป ANR จะเกิดขึ้นอย่างชัดเจนและมีการรายงานไปยังแอป
หากแอปของคุณพบ ANR คุณสามารถใช้คำแนะนำในเอกสารนี้เพื่อวิเคราะห์และแก้ไขปัญหาได้
วิเคราะห์ ANR
รูปแบบที่พบได้ทั่วไปซึ่งควรระวังเมื่อวินิจฉัย ANR มีดังนี้
- แอปกำลังดำเนินการที่ช้าซึ่งเกี่ยวข้องกับ I/O ในเทรดหลัก
- แอปกำลังทำการคำนวณที่ใช้เวลานานในชุดข้อความหลัก
- เทรดหลักกำลังเรียกใช้ Binder แบบซิงโครนัสไปยังกระบวนการอื่น และกระบวนการอื่นนั้นใช้เวลานานในการแสดงผล
- เทรดหลักถูกบล็อกเนื่องจากรอการบล็อกที่ซิงค์สำหรับการดำเนินการที่ใช้เวลานานซึ่งเกิดขึ้นในเทรดอื่น
- เทรดหลักอยู่ในภาวะติดตายกับเทรดอื่น ไม่ว่าจะในกระบวนการของคุณหรือผ่านการเรียก Binder เทรดหลักไม่ได้แค่รอให้การดำเนินการที่ใช้เวลานานเสร็จสิ้น แต่ยังอยู่ในสถานการณ์เดดล็อกด้วย
เทคนิคต่อไปนี้จะช่วยคุณระบุสาเหตุของ ANR ได้
HealthStats
HealthStats แสดงเมตริกเกี่ยวกับประสิทธิภาพของแอปพลิเคชันโดยการบันทึกเวลาทั้งหมดของผู้ใช้และระบบ, เวลา CPU, เครือข่าย, สถิติวิทยุ, เวลาเปิด/ปิดหน้าจอ และการปลุกให้ตื่น ซึ่งจะช่วยให้คุณวัดการใช้งาน CPU โดยรวมและการใช้แบตเตอรี่ได้
แก้ไขข้อบกพร่อง
Debug ช่วยคุณตรวจสอบแอปพลิเคชัน Android ในระหว่างการพัฒนา รวมถึงการติดตามและจำนวนการจัดสรรเพื่อระบุอาการกระตุกและแล็กในแอป
นอกจากนี้ คุณยังใช้ Debug เพื่อดูตัวนับหน่วยความจำรันไทม์และหน่วยความจำเนทีฟ รวมถึงเมตริกหน่วยความจำที่ช่วยระบุหน่วยความจำที่ใช้ของกระบวนการหนึ่งๆ ได้ด้วย
ApplicationExitInfo
ApplicationExitInfo พร้อมใช้งานใน Android 11 (ระดับ API 30) ขึ้นไป และให้ข้อมูลเกี่ยวกับสาเหตุที่แอปพลิเคชันออก ซึ่งรวมถึง ANR, หน่วยความจำเหลือน้อย, แอปขัดข้อง, การใช้งาน CPU มากเกินไป, การรบกวนผู้ใช้, การรบกวนระบบ และการเปลี่ยนแปลงสิทธิ์รันไทม์
โหมดจำกัด
การใช้ StrictMode ช่วยให้คุณค้นหาการดำเนินการ I/O โดยไม่ตั้งใจในเทรดหลักขณะพัฒนาแอป คุณสามารถใช้ StrictMode ที่ระดับแอปพลิเคชันหรือกิจกรรมได้
เปิดใช้กล่องโต้ตอบ ANR ในเบื้องหลัง
Android จะแสดงกล่องโต้ตอบ ANR สำหรับแอปที่ใช้เวลานานเกินไปในการประมวลผลข้อความประกาศก็ต่อเมื่อเปิดใช้แสดง ANR ทั้งหมดในตัวเลือกสำหรับนักพัฒนาแอปของอุปกรณ์ ด้วยเหตุนี้ กล่องโต้ตอบ ANR ในเบื้องหลังจึงไม่แสดงต่อผู้ใช้เสมอไป แม้ว่าแอปจะประสบปัญหาด้านประสิทธิภาพก็ตาม
คอขวดของการจัดองค์ประกอบใหม่
ใช้ Android Studio Profiler และเครื่องมือตรวจสอบเลย์เอาต์เพื่อติดตามคอขวดของการจัดองค์ประกอบใหม่ ดูข้อมูลเพิ่มเติมได้ที่ประสิทธิภาพของ Jetpack Compose
ดึงไฟล์การติดตาม
Android จะจัดเก็บข้อมูลการติดตามเมื่อพบ ANR ในระบบปฏิบัติการรุ่นเก่า จะมีไฟล์ /data/anr/traces.txt เพียงไฟล์เดียวในอุปกรณ์ ในระบบปฏิบัติการรุ่นใหม่ๆ จะมีไฟล์ /data/anr/anr_* หลายไฟล์ คุณเข้าถึงการติดตาม ANR จากอุปกรณ์หรือโปรแกรมจำลองได้โดยใช้ Android Debug Bridge (adb) เป็นรูท ดังนี้
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
คุณสามารถบันทึกรายงานข้อบกพร่องจากอุปกรณ์จริงได้โดยใช้ตัวเลือกสำหรับนักพัฒนาแอป "ใช้รายงานข้อบกพร่อง" ในอุปกรณ์หรือคำสั่ง adb bugreport ในคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ ดูข้อมูลเพิ่มเติมได้ที่บันทึกและอ่านรายงานข้อบกพร่อง
แก้ไขปัญหา
หลังจากระบุปัญหาแล้ว คุณสามารถใช้เคล็ดลับในส่วนนี้เพื่อแก้ไขปัญหาที่พบได้ทั่วไป
โค้ดที่ทำงานช้าในเทรดหลัก
ระบุตำแหน่งในโค้ดที่เทรดหลักของแอปไม่ว่างนานกว่า 5 วินาที มองหา Use Case ที่น่าสงสัยในแอปและพยายามจำลอง ANR ซ้ำ
ปัญหาที่พบบ่อยคือการทำงานที่ใช้เวลานานใน Composable โดยตรง
@Composable
fun BadList(rawStrings: List<String>) {
// Math or sorting inside the composable runs on EVERY recomposition pass!
val heavilyProcessedList = rawStrings
.filter { it.isNotBlank() }
.map { it.uppercase().reversed() }
.map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
LazyColumn { items(sortedList) { Text(it) } }
}
// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// UI simply renders state; no heavy processing allowed here
LazyColumn { items(uiState.sortedData) { Text(it) } }
}
I/O ในเทรดหลัก
การดําเนินการ I/O ในเทรดหลักเป็นสาเหตุที่พบบ่อยของการทํางานที่ช้าในเทรดหลัก ซึ่งอาจทําให้เกิด ANR ใน Compose นักพัฒนามักจะเรียกให้ระบบอ่านดิสก์โดยไม่ตั้งใจ (เช่น SharedPreferences หรือการเรียกใช้ฐานข้อมูล) ขณะพยายามรับสถานะเริ่มต้น
ดำเนินการ I/O ที่ใช้เวลานานนอกเลเยอร์ UI ใช้ withContext(Dispatchers.IO) ใน ViewModel หรือจะให้ดีกว่านั้น ให้ใช้ Repository ในชั้นข้อมูล
Deadlock
เดดล็อกจะเกิดขึ้นเมื่อเทรดเข้าสู่สถานะรอเนื่องจากเทรดอื่นมีทรัพยากรที่จำเป็น ซึ่งเทรดนั้นก็กำลังรอทรัพยากรที่เทรดแรกมีอยู่เช่นกัน หากเทรดหลักของแอปอยู่ในสถานการณ์นี้ ก็มีแนวโน้มที่จะเกิด ANR
ภาวะติดตายเป็นปรากฏการณ์ที่ได้รับการศึกษาอย่างดีในด้านวิทยาการคอมพิวเตอร์ และมีอัลกอริทึมการป้องกันภาวะติดตายที่คุณสามารถใช้เพื่อหลีกเลี่ยงภาวะติดตายได้
ดูข้อมูลเพิ่มเติมได้ที่ภาวะติดตายและอัลกอริทึมการป้องกันภาวะติดตายใน Wikipedia
เมื่อใช้ Kotlin และ Compose คุณสามารถแทนที่ล็อกพื้นฐานด้วย Mutexes ของโครูทีนแบบไม่บล็อก (Mutex.withLock) เพื่อป้องกันไม่ให้บล็อกเธรดโดยระงับบริบทการดำเนินการแทนที่จะหยุดเธรด UI ไว้ชั่วคราว เช่น
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
// Modern non-blocking concurrency state architecture
class SecureDataRepository {
private val mutex = Mutex()
suspend fun safeUIAccess() {
// If locked, the main thread suspends seamlessly, preventing an ANR
mutex.withLock {
performSafeOperation()
}
}
}
Broadcast Receiver ที่ทำงานช้า
แอปสามารถตอบสนองต่อข้อความประกาศ เช่น การเปิดหรือปิดใช้โหมดบนเครื่องบินหรือการเปลี่ยนแปลงสถานะการเชื่อมต่อ โดยใช้ Broadcast Receiver ANR จะเกิดขึ้นเมื่อแอปใช้เวลานานเกินไปในการประมวลผลข้อความประกาศ
ANR จะเกิดขึ้นในกรณีต่อไปนี้
- Broadcast Receiver ยังดำเนินการตามเมธอด
onReceiveไม่เสร็จภายในระยะเวลาที่เหมาะสม - Broadcast Receiver เรียกใช้
goAsyncและเรียกใช้finishในออบเจ็กต์PendingResultไม่สำเร็จ
แอปควรดำเนินการสั้นๆ ในเมธอด onReceive ของ BroadcastReceiver เท่านั้น อย่างไรก็ตาม หากแอปของคุณต้องมีการประมวลผลที่ซับซ้อนมากขึ้นอันเป็นผลมาจากข้อความประกาศ คุณควรเลื่อนงานออกไปเป็น ViewModel (ใช้ประโยชน์จากประสิทธิภาพของโครูทีน, ขอบเขต และผู้จัดส่งของ Kotlin) หากคาดว่างานจะใช้เวลาไม่เกิน 2-3 วินาที ตัวเก็บสถานะประเภทใดก็ได้ หรือ WorkManager สำหรับงานที่คาดว่าจะใช้เวลานานกว่า 2-3 วินาที
GameActivity
ไลบรารี GameActivity ช่วยลด ANR ในกรณีศึกษาของเกมและแอปที่เขียนด้วย C หรือ C++ หากคุณแทนที่กิจกรรมแบบเนทีฟที่มีอยู่ด้วย GameActivity คุณจะลดการบล็อกเธรด UI และป้องกันไม่ให้เกิด ANR บางรายการได้
ดูข้อมูลเพิ่มเติมเกี่ยวกับ ANR ได้ที่ทำให้แอปตอบสนองอยู่เสมอ ดูข้อมูลเพิ่มเติมเกี่ยวกับชุดข้อความได้ที่ประสิทธิภาพที่ดีขึ้นผ่านชุดข้อความ
แหล่งข้อมูลเพิ่มเติม
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- การปลุกระบบบ่อยเกินไป