เมื่อเธรด UI ของแอป Android ถูกบล็อกนานเกินไป ระบบจะทริกเกอร์ข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR) หากแอปอยู่ในเบื้องหน้า ระบบจะแสดงกล่องโต้ตอบต่อผู้ใช้ ดังที่แสดงในรูปที่ 1 กล่องโต้ตอบ ANR จะให้ โอกาสผู้ใช้ในการบังคับปิดแอป
ANR เป็นปัญหาเนื่องจากเธรดหลักของแอปซึ่งมีหน้าที่อัปเดต UI ไม่สามารถประมวลผลข้อมูลจากผู้ใช้หรือวาดภาพได้ ซึ่งทำให้ผู้ใช้รู้สึกหงุดหงิด ดูข้อมูลเพิ่มเติมเกี่ยวกับเทรดหลักของแอปได้ที่ภาพรวมของกระบวนการและเทรด
ระบบจะทริกเกอร์ ANR สำหรับแอปเมื่อเกิดเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้
- การส่งอินพุตหมดเวลา: หากแอปไม่ตอบสนองต่อเหตุการณ์อินพุต (เช่น การกดปุ่มหรือการสัมผัสหน้าจอ) ภายใน 5 วินาที
- การดำเนินการบริการ: หากบริการที่แอปประกาศไม่สามารถดำเนินการ
Service.onCreateและService.onStartCommand/Service.onBindให้เสร็จสมบูรณ์ ภายในไม่กี่วินาที Service.startForegroundไม่ได้เรียกใช้: หากแอปใช้Context.startForegroundServiceเพื่อเริ่มบริการใหม่ในเบื้องหน้า แต่บริการไม่ได้เรียกใช้startForegroundภายใน 5 วินาที- การออกอากาศเจตนา: หาก
BroadcastReceiverยังดำเนินการไม่เสร็จภายในระยะเวลาที่กำหนด หากแอปมีกิจกรรมใดๆ ใน เบื้องหน้า การหมดเวลานี้จะอยู่ที่ 5 วินาที JobSchedulerการโต้ตอบ: หากJobServiceไม่กลับจากJobService.onStartJobหรือJobService.onStopJobภายใน 2-3 วินาที หรือหากงานที่ผู้ใช้เริ่มเริ่มต้นและแอปของคุณไม่เรียกใช้JobService.setNotificationภายใน 2-3 วินาทีหลังจากเรียกใช้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 ไฟล์เดียวในอุปกรณ์ ใน OS เวอร์ชันใหม่กว่า
จะมีไฟล์ /data/anr/anr_* หลายไฟล์ คุณเข้าถึง ANR
traces จากอุปกรณ์หรือโปรแกรมจำลองได้โดยใช้ 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 ใน dataLayer จะดีกว่า
การติดตาย
การติดตายจะเกิดขึ้นเมื่อเทรดเข้าสู่สถานะรอเนื่องจากเทรดอื่นถือครองทรัพยากรที่จำเป็น ซึ่งเทรดนั้นก็รอทรัพยากรที่เทรดแรกถือครองอยู่เช่นกัน หากเทรดหลักของแอปอยู่ในสถานการณ์นี้ มีแนวโน้มที่จะเกิด 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ไม่เสร็จภายในระยะเวลาที่เหมาะสม - BroadcastReceiver เรียกใช้
goAsyncและเรียกใช้finishในออบเจ็กต์PendingResultไม่สำเร็จ
แอปควรดำเนินการสั้นๆ ในเมธอด onReceive
ของ BroadcastReceiver เท่านั้น อย่างไรก็ตาม หากแอปของคุณต้องมีการประมวลผลที่ซับซ้อนมากขึ้นอันเป็นผลมาจากข้อความประกาศ คุณควรเลื่อนงานไปยัง ViewModel (ใช้ประโยชน์จากพลังของโครูทีน ขอบเขต และผู้มอบหมายงานของ Kotlin) หากคาดว่างานจะใช้เวลาไม่กี่วินาที หรือไปยังตัวเก็บสถานะประเภทใดก็ได้ หรือไปยัง WorkManager สำหรับงานที่คาดว่าจะใช้เวลานานกว่าไม่กี่วินาที
GameActivity
ไลบรารี GameActivity ช่วยลด ANR ในกรณีศึกษาของ
เกมและแอปที่เขียนด้วย C หรือ C++ การแทนที่กิจกรรมดั้งเดิมที่มีอยู่ด้วย GameActivity จะช่วยลดการบล็อกเทรด UI และป้องกันไม่ให้เกิด ANR บางรายการได้
ดูข้อมูลเพิ่มเติมเกี่ยวกับ ANR ได้ที่ทำให้แอปตอบสนอง ดูข้อมูลเพิ่มเติมเกี่ยวกับเธรดได้ที่ประสิทธิภาพที่ดีขึ้นผ่านเธรด
แหล่งข้อมูลเพิ่มเติม
ดูเนื้อหา
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- การปลุกระบบบ่อยเกินไป