StrictMode เป็นเครื่องมือสำหรับนักพัฒนาที่สร้างอยู่ใน Android SDK ซึ่งตรวจจับและรายงานการดำเนินการ I/O และการเรียกเครือข่ายโดยไม่ตั้งใจบนเธรดหลักของแอปพลิเคชันแบบเรียลไทม์ มันไม่แก้ไขข้อผิดพลาดแต่ทำหน้าที่เป็นตัวตรวจจับ — จะโยนข้อยกเว้นหรือเขียนไปยัง LogCat เมื่อมีการละเมิดนโยบายที่กำหนดค่าไว้ ตามข้อมูลจาก Google, 2024 การกำหนดค่า StrictMode ที่เหมาะสมสามารถตรวจจับปัญหาประสิทธิภาพได้มากถึง 80% ก่อนการเผยแพร่แอป
ประเด็นสำคัญ
StrictMode เป็น API ที่รวมอยู่ใน Android SDK ตั้งแต่ API Level 9 (Android 2.3 Gingerbread) หน้าที่ของมันคือตรวจจับในรันไทม์การดำเนินการโดยไม่ตั้งใจของงานหนักบนเธรดหลัก (UI) ที่อาจบล็อกการเรนเดอร์อินเทอร์เฟซ เธรดหลักจัดการกับการประมวลผลอินพุตของผู้ใช้ การคำนวณเลย์เอาต์ และการเรนเดอร์ — การบล็อกใดๆ ที่นานกว่า 16 มิลลิวินาทีจะทำให้เฟรมดรอป
StrictMode ปฏิบัติตามหลักการ “ล้มเหลวเร็ว” — ตรวจหาปัญหาให้เร็วที่สุดเท่าที่จะเป็นไปได้ ควรจะเป็นในขณะที่มันปรากฏครั้งแรก แทนที่จะรอข้อร้องเรียนจากผู้ใช้เกี่ยวกับความช้า นักพัฒนาจะได้รับสัญญาณ (บันทึก กล่องโต้ตอบ หรือการคราช) โดยตรงในขั้นตอนการพัฒนา เครื่องมือไม่ต้องการไลบรารีเพิ่มเติมหรือการกำหนดค่า Gradle — แค่โค้ดสองสามบรรทัดใน Application.onCreate และมันทำงานอัตโนมัติบนทุกอุปกรณ์
StrictMode ออกแบบมาสำหรับ นักพัฒนา Android ทุกคน โดยไม่คำนึงถึงประสบการณ์ สำหรับผู้เริ่มต้น มันช่วยสร้างนิสัยที่ดี (ไม่ทำคำขอเครือข่ายในเธรด UI) สำหรับนักพัฒนาที่มีประสบการณ์ มันทำให้การควบคุมคุณภาพในไปป์ไลน์ CI/CD เป็นอัตโนมัติ โครงการขนาดใหญ่ (Google, Uber, Spotify) รวม StrictMode ในบิลด์ดีบักด้วย penaltyDeath และปิดใช้งานในบิลด์เผยแพร่ผ่านการตรวจสอบ BuildConfig.DEBUG
StrictMode สกัดกั้นการเรียกระบบที่อาจบล็อกเธรดและเปรียบเทียบกับชุดนโยบายที่ทำงานอยู่ หากการเรียกตรงกับนโยบายและดำเนินการบนเธรดหลัก StrictMode จะใช้บทลงโทษที่กำหนด กลไกการสกัดกั้นถูกนำไปใช้ผ่านฮุกภายในกระบวนการ — มันไม่ใช้รีเฟลกชันและทำงานด้วยโอเวอร์เฮดที่น้อยที่สุด
เมื่อเปิดใช้งานนโยบาย StrictMode มันจะแทรกตัวจัดการของมันเข้าไปในจุดเริ่มต้นของการเรียกระบบ (FileInputStream, FileOutputStream, Socket, URLConnection) เมื่อแอปพลิเคชันเรียก ตัวอย่างเช่น URLConnection.openStream บนเธรดหลัก StrictMode จะตรวจสอบเธรดปัจจุบัน — ถ้าเป็นเธรดหลัก เครื่องมือจะทำงาน ใน Android 6.0+ กลไกได้รับการปรับปรุง: การเรียกเครือข่ายบนเธรดหลักจะสร้าง NetworkOnMainThreadException แม้ไม่มี StrictMode แต่ StrictMode ยังอนุญาตให้ควบคุม I/O ของดิสก์ได้
แต่ละนโยบายสามารถมีประเภทบทลงโทษหรือการรวมกันของมันเอง: penaltyLog — เขียนไปยัง LogCat พร้อมร่องรอยสแต็ก penaltyDialog — แสดงกล่องโต้ตอบให้ผู้ใช้ (ดีบักเท่านั้น) penaltyDeath — โยนข้อยกเว้นและทำให้แอปคราช penaltyDropBox — บันทึกข้อมูลไปยัง DropBoxManager เพื่อวิเคราะห์ภายหลัง สำหรับไปป์ไลน์ CI/CD แนะนำให้ใช้ penaltyDeath — มันรับประกันว่าไม่มีการรวมโค้ดที่มีการละเมิดจะผ่านไปโดยไม่มีใครสังเกต
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
StrictMode แบ่งนโยบายออกเป็นสองระดับ: ThreadPolicy (ระดับเธรด — สิ่งที่ไม่สามารถทำได้บนเธรดหลัก) และ VmPolicy (เครื่องเสมือน — การรั่วไหลของหน่วยความจำและทรัพยากร) ทั้งสองระดับถูกกำหนดค่าอย่างอิสระและทำงานแบบขนาน
ในระดับเธรด StrictMode ควบคุมการละเมิดสี่ประเภท: การอ่านดิสก์ (detectDiskReads) การเขียนดิสก์ (detectDiskWrites) การดำเนินการเครือข่าย (detectNetwork) และการเรียกช้าแบบกำหนดเอง (detectCustomSlowCalls) disk_read ทำงานเมื่อมีการอ่าน SharedPreferences, SQLite หรือไฟล์บนเธรดหลัก network ทำงานเมื่อมีคำขอ HTTP, WebSocket และการเชื่อมต่อ Socket ใน Android 11+ มีการเพิ่ม detectUnbufferedIO สำหรับตรวจจับ I/O ที่ไม่มีบัฟเฟอร์
VmPolicy ควบคุมการรั่วไหลในระดับเครื่องเสมือน ART: detectActivityLeaks (กิจกรรมที่ไม่ได้ถูกทำลาย) detectLeakedClosableObjects (Cursor, Stream, Socket ที่ไม่ได้ปิด) detectLeakedRegistrationObjects (BroadcastReceiver, ServiceConnection ที่ไม่ได้ยกเลิกการลงทะเบียน) หาก VmPolicy ตรวจพบว่า Activity ถูกสร้างขึ้นแต่ไม่ได้ถูกทำลายหลังจากเรียก onDestroy มันจะแสดงร่องรอยสแต็กทั้งหมด — ช่วยประหยัดเวลาในการดีบักการรั่วไหลของหน่วยความจำหลายชั่วโมง
| นโยบาย | ระดับ | สิ่งที่ตรวจจับ |
|---|---|---|
| detectDiskReads | Thread | การอ่าน SharedPrefs, SQLite, ไฟล์ในเธรด UI |
| detectDiskWrites | Thread | การเขียนไปยัง SharedPrefs, SQLite, ไฟล์ในเธรด UI |
| detectNetwork | Thread | การดำเนินการเครือข่ายใดๆ ในเธรด UI |
| detectActivityLeaks | VM | กิจกรรมที่รอดจาก onDestroy |
| detectLeakedClosableObjects | VM | Cursor, Stream, Socket ที่ไม่ได้ปิด |
ผ่าน detectCustomSlowCalls คุณสามารถทำเครื่องหมายเมธอดของคุณเองว่า “น่าสงสัย” และรับคำเตือนเมื่อเกินเกณฑ์ที่กำหนด ตัวอย่างเช่น หากเมธอด loadUserProfile() ของคุณปกติใช้เวลา 5 มิลลิวินาที แต่บางครั้งใช้เวลา 200 มิลลิวินาที — ห่อมันใน StrictMode.noteSlowCall(“loadUserProfile”) หากระยะเวลาเกินเกณฑ์ (ค่าเริ่มต้น 2000 มิลลิวินาที) StrictMode จะสร้างบทลงโทษ เกณฑ์ถูกกำหนดค่าผ่าน setSlowCallDurationThreshold
การกำหนดค่าพื้นฐานของ StrictMode ใช้โค้ด 10 บรรทัดและทำในเมธอด onCreate ของคลาส Application ที่กำหนดเอง กฎหลัก: StrictMode เปิดใช้งานเฉพาะในบิลด์ดีบัก — ในบิลด์เผยแพร่มันจะทำให้แอปช้าลงและสามารถสร้างผลบวกลวงได้
สร้างคลาสที่สืบทอด Application ลงทะเบียนใน AndroidManifest.xml ผ่านแอตทริบิวต์ android:name และเพิ่มการกำหนดค่า StrictMode ThreadPolicy.Builder รวมถึงตัวตรวจจับทั้งหมดและบทลงโทษทุกประเภท (ยกเว้น dialog — ทำงานเมื่อมีดีบักเกอร์เชื่อมต่อเท่านั้น) VmPolicy.Builder เพิ่มตัวตรวจจับสำหรับการรั่วไหลของ Activity และออบเจ็กต์ Closable สำหรับโครงการขนาดใหญ่ (100+ หน้าจอ) แนะนำให้กำหนดค่า VmPolicy ด้วย penaltyDeath บนตัวตรวจจับ Activity Leaks — มันเข้มงวดแต่มีประสิทธิภาพ
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.penaltyLog()
.penaltyDeath()
.build()
)
}
}
}
สำหรับการควบคุมอัตโนมัติใน CI/CD ให้ใช้ penaltyDeath — หากการทดสอบใดละเมิดนโยบาย แอปจะคราชด้วยข้อยกเว้น รวมกับ Android Test Orchestrator เพื่อให้แต่ละการทดสอบทำงานในกระบวนการที่สะอาด สำหรับการทดสอบ UI (Espresso, Compose Test) ให้เขียน TestRule ที่กำหนดเองซึ่งสกัดกั้นการละเมิด StrictMode และเปลี่ยนเป็นความล้มเหลวของการยืนยัน ตัวอย่าง: ใน @Before เปิดใช้งาน StrictMode และใน @After ตรวจสอบว่าไม่มีการละเมิด
ค่าเริ่มต้น เกณฑ์สำหรับ customSlowCall คือ 2000 มิลลิวินาที สำหรับ disk_read และ disk_write — ไม่มีเกณฑ์ (การดำเนินการใดๆ ก็ทำงาน) ผ่าน setSlowCallDurationThreshold และ setSlowIoDurationThreshold คุณสามารถตั้งค่าของคุณเองในหน่วยมิลลิวินาที หากแอปของคุณอ่าน SharedPreferences บนเธรดหลักอย่างถูกต้อง (การกำหนดค่าเล็กน้อย) ให้เพิ่มเกณฑ์เป็น 10–20 มิลลิวินาที — ซึ่งจะกรองการอ่านที่เร็วออกไปในขณะที่คงการอ่านที่ช้าไว้
StrictMode เป็นเครื่องมือที่ทรงพลังแต่ละเอียดอ่อน การกำหนดค่าที่ไม่ถูกต้องนำไปสู่ผลบวกลวงนับล้าน ทำให้นักพัฒนาหยุดสนใจพวกเขา ด้านล่างนี้เป็นแนวทางปฏิบัติที่พิสูจน์แล้วซึ่งรวบรวมจากประสบการณ์ของทีม Android ขนาดใหญ่
นี่คือกฎที่เข้มงวด: StrictMode ไม่ควรเปิดใช้งานในบิลด์เผยแพร่ ใช้แฟล็ก BuildConfig.DEBUG หรือ buildConfigField ที่กำหนดเอง ในบิลด์เผยแพร่ ไลบรารีของบุคคลที่สามหลายแห่งดำเนินการบนเธรดหลักอย่างถูกต้อง (การเริ่มต้น SDK, การเขียนแคช) และ StrictMode จะสร้างผลบวกลวง ยิ่งไปกว่านั้น penaltyDialog ในบิลด์เผยแพร่จะแสดงกล่องโต้ตอบให้ผู้ใช้ปลายทาง — ซึ่งไม่สามารถยอมรับได้
สำหรับโครงการขนาดเล็ก (1–10 หน้าจอ) กำหนดค่า penaltyLog — บันทึกเพียงพอสำหรับการวิเคราะห์ด้วยตนเอง สำหรับโครงการขนาดกลาง (10–50 หน้าจอ) เพิ่ม penaltyDeath บนเครือข่ายและ customSlowCalls สำหรับโครงการขนาดใหญ่ (50+ หน้าจอ) เปิดใช้งานชุดนโยบายเต็มรูปแบบด้วย penaltyDeath ใน CI/CD และ penaltyLog สำหรับการพัฒนาในเครื่อง การไล่ระดับนี้ป้องกันไม่ให้นักพัฒนาทำงานหนักเกินไปกับการคราชปลอมในขณะที่ควบคุมคุณภาพในไปป์ไลน์อย่างเข้มงวด
ไลบรารีบางตัว (Firebase, Crashlytics, Adjust) ดำเนินการในพื้นหลังอย่างถูกต้องซึ่ง StrictMode อาจตรวจพบอย่างไม่ถูกต้อง วิธีแก้ไข: เพิ่มไลบรารีใน รายการอนุญาต ผ่าน penaltyListener อัปเดตไลบรารีเป็นเวอร์ชันที่มีการสลับไปยังเธรดพื้นหลังอย่างชัดเจน หรือใช้ StrictMode.vmPolicy ใน Android 11+ มีการนำ StrictMode.OnVmViolationListener มาใช้สำหรับการกรองการละเมิดโดยทางโปรแกรมตามร่องรอยสแต็ก
// การกรองผลบวกลวงผ่าน penaltyListener
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyListener { violation ->
val stack = violation.stackTraceToString()
if ("com.google.firebase" !in stack) {
logViolation(violation)
}
}
.build()
)
StrictMode ไม่ใช่เครื่องมือควบคุมคุณภาพเพียงอย่างเดียวในระบบนิเวศ Android เพื่อเข้าใจตำแหน่งของมัน เรามาเปรียบเทียบกับ Android Lint, Android Profiler และ Perfetto ตามเกณฑ์สำคัญ: เวลาตรวจสอบ ความลึกของการวิเคราะห์ และระบบอัตโนมัติ
| เกณฑ์ | StrictMode | Android Lint | Profiler / Perfetto |
|---|---|---|---|
| เวลาตรวจสอบ | รันไทม์ (ขณะแอปทำงาน) | เวลาคอมไพล์ (ก่อนเริ่ม) | รันไทม์ (หลังการตาย) |
| สิ่งที่ตรวจสอบ | ดิสก์, เครือข่าย, การรั่วไหล | XML, โค้ด, ทรัพยากร | CPU, หน่วยความจำ, เครือข่าย, พลังงาน |
| ระบบอัตโนมัติ | CI/CD ผ่าน penaltyDeath | งาน Gradle + lint-baseline | ต้องวิเคราะห์ด้วยตนเอง |
| ความลึก | เฉพาะเธรด UI และการรั่วไหล | การวิเคราะห์โค้ดแบบคงที่ | ภาพประสิทธิภาพเต็มรูปแบบ |
| ผลบวกลวง | ปานกลาง (ขึ้นอยู่กับไลบรารี) | ต่ำ (กฎที่กำหนดค่าไว้) | ไม่มี (การวัดจริง) |
กลยุทธ์ที่ดีที่สุดคือการรวมทั้งสามแนวทาง: Android Lint จับข้อผิดพลาดที่ชัดเจนในเวลาคอมไพล์ (เช่น IdleHandler ที่ถูกลืม) StrictMode ตรวจจับปัญหาในรันไทม์ และ Android Profiler / Perfetto ใช้สำหรับการวิเคราะห์เชิงลึกเมื่อเครื่องมือสองตัวแรกไม่ได้ให้คำตอบ ในโครงการจริง (Google Maps, Instagram) StrictMode ถูกนำมาใช้ในสัปดาห์ที่สองของการพัฒนา — ทันทีหลังจากตั้งค่าสถาปัตยกรรมพื้นฐาน
มาดูสองสถานการณ์จริงที่ StrictMode ช่วยตรวจจับและแก้ไขปัญหาประสิทธิภาพ: การอ่าน SharedPreferences บนเธรดหลักและการรั่วไหลของ Activity ผ่านการเรียกกลับที่ไม่ได้ยกเลิกการลงทะเบียน
เมื่อเริ่มต้นแอป StrictMode ที่มีนโยบาย detectDiskReads จะตรวจจับการอ่าน SharedPreferences บนเธรดหลัก วิธีแก้ไข: โหลดการกำหนดค่าแบบอะซิงโครนัสผ่าน CoroutineScope หรือแคชในหน่วยความจำเมื่อเริ่มต้น SharedPreferences อ่านไฟล์ XML จากดิสก์แบบซิงโครนัส — แม้กับไฟล์ขนาดเล็ก (1–2 KB) การดำเนินการใช้เวลา 1–5 มิลลิวินาที และสูงถึง 20 มิลลิวินาทีบนอุปกรณ์ราคาถูก ซึ่งอาจทำให้เฟรมดรอป
// ❌ โค้ดที่มีปัญหา — การอ่าน SharedPrefs ในเธรด UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// StrictMode detectDiskReads → การละเมิด!
prefs = getSharedPreferences("config", MODE_PRIVATE)
}
}
// ✅ โค้ดที่แก้ไขแล้ว — การอ่านผ่าน Coroutine
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loadConfigAsync()
}
}
StrictMode ที่มี VmPolicy.detectActivityLeaks จะตรวจจับ Activity ที่ออกจากสแต็กแล้ว (เรียก finish) แต่ออบเจ็กต์ Activity ยังคงอยู่ในหน่วยความจำเนื่องจากการอ้างอิงแบบคงที่หรือการเรียกกลับที่ไม่ได้ยกเลิกการลงทะเบียน สถานการณ์ทั่วไป: การลงทะเบียน EventBus หรือ LocationListener ใน onResume โดยไม่เรียก unregister ใน onPause VmPolicy จะแสดงร่องรอยสแต็กที่ระบุบรรทัดที่สร้างการอ้างอิง
// ❌ การรั่วไหล — การเรียกกลับไม่ได้ถูกยกเลิก
private var locationCallback: LocationCallback? = null
override fun onResume() {
super.onResume()
locationCallback = LocationCallback(this::onLocationUpdated)
locationManager.register(locationCallback) // StrictMode → การรั่วไหล!
}
override fun onPause() {
super.onPause()
// ลืม: locationManager.unregister(locationCallback)
}
คำถามที่พบบ่อย
StrictMode เพิ่มโอเวอร์เฮดเล็กน้อย — ทุกการเรียกระบบจะถูกตรวจสอบกับนโยบาย ผลกระทบต่อประสิทธิภาพคือ 1–3% ในบิลด์ดีบักและไม่มีในบิลด์เผยแพร่ (ที่ StrictMode ถูกปิดใช้งาน) เมื่อเปิดใช้งาน detectAll บนอุปกรณ์เก่า (Android 6–8) โอเวอร์เฮดอาจสูงถึง 5% ดังนั้นแนะนำให้กำหนดค่าเฉพาะนโยบายที่จำเป็น
ใช่ StrictMode เข้ากันได้อย่างสมบูรณ์กับ Jetpack Compose นโยบายดิสก์และเครือข่ายทำงานในระดับเฟรมเวิร์ก โดยอิสระจากเฟรมเวิร์ก UI ยิ่งไปกว่านั้น ใน Compose ความสำคัญของการบล็อก UI สูงกว่า — Compose วาดเฟรมใหม่ที่ 120 FPS บนอุปกรณ์ที่มีอัตรารีเฟรชสูง ดังนั้น 5 มิลลิวินาทีพิเศษในการอ่านไฟล์จึงสังเกตเห็นได้ชัดเจนขึ้น
ตั้งแต่ Android 8.1 (API 27) SharedPreferences อาจใช้ แคชในหน่วยความจำ — หากไฟล์ถูกอ่านแล้ว การอ่านซ้ำจะไม่ทำให้ StrictMode ทำงาน ตรวจสอบว่าคุณเรียก getSharedPreferences เป็นครั้งแรก (การอ่านเย็น) และนโยบาย detectDiskReads ทำงานอยู่ ตรวจสอบด้วยว่า StrictMode ไม่ได้ถูกแทนที่ใน Fragment ที่ไม่มีพาเรนต์
ในการทดสอบ JUnit ให้ใช้ StrictMode.allowThreadDiskReads() และ StrictMode.allowThreadDiskWrites() ใน @Before และคืนค่าการตั้งค่าใน @After ผ่าน StrictMode.enableDefaults() สำหรับการทดสอบ Instrumentation ให้ใช้ TestRunner ที่กำหนดเองพร้อมการรักษานโยบายเดิมชั่วคราว ในการทดสอบ Espresso สะดวกที่จะห่อโค้ดที่ไวต่อ StrictMode ใน IdlingResource
StrictMode ทำงานบนแพลตฟอร์ม Android ผ่าน Android SDK เท่านั้น ใน Kotlin Multiplatform (KMP) โค้ด commonMain ไม่สามารถใช้ StrictMode ได้ แต่สำหรับ androidMain คุณสามารถเพิ่มได้ตามปกติ สำหรับส่วน iOS ให้ใช้อะนาล็อก — การยืนยัน DispatchQueue.main.async สำหรับเธรดหลัก
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม