@@ -5,7 +5,7 @@ import android.os.Looper
55import android.os.SystemClock
66import java.util.concurrent.TimeUnit
77import java.util.concurrent.atomic.AtomicBoolean
8- import java.util.concurrent.locks.ReentrantLock
8+ import java.util.concurrent.locks.LockSupport
99
1010internal class LooperMonitorThread (
1111 watchedLooper : Looper ,
@@ -14,15 +14,13 @@ internal class LooperMonitorThread(
1414) : Thread(" Bugsnag AppHang Monitor: ${watchedLooper.thread.name} " ) {
1515 private val handler: Handler = Handler (watchedLooper)
1616
17+ @Volatile
1718 private var lastHeartbeatTimestamp = 0L
1819
1920 private val isRunning = AtomicBoolean (false )
2021
2122 private var isAppHangDetected = false
2223
23- private val heartbeatLock = ReentrantLock (false )
24- private val heartbeatCondition = heartbeatLock.newCondition()
25-
2624 private val heartbeat: Runnable = Heartbeat ()
2725
2826 private fun calculateTimeToAppHang (now : Long ): Long =
@@ -36,22 +34,17 @@ internal class LooperMonitorThread(
3634
3735 fun stopMonitoring () {
3836 if (isRunning.compareAndSet(true , false )) {
39- interrupt()
37+ handler.removeCallbacks(heartbeat)
38+ LockSupport .unpark(this )
4039 }
4140 }
4241
4342 internal fun resetHeartbeatTimer () {
44- heartbeatLock.lock()
45- try {
46- heartbeatCondition.signalAll()
47- } finally {
48- heartbeatLock.unlock()
49- }
43+ LockSupport .unpark(this )
5044 }
5145
5246 private fun reportAppHang (timeSinceLastHeartbeat : Long ) {
5347 if (isAppHangDetected) {
54- // avoid reporting duplicate AppHangs
5548 return
5649 }
5750
@@ -63,37 +56,34 @@ internal class LooperMonitorThread(
6356 handler.post(heartbeat)
6457
6558 while (isRunning.get()) {
66- heartbeatLock.lock()
67- try {
68- val waitThreshold =
69- if (lastHeartbeatTimestamp <= 0L ) appHangThresholdMillis
70- else calculateTimeToAppHang(SystemClock .elapsedRealtime())
71- heartbeatCondition.await(waitThreshold, TimeUnit .MILLISECONDS )
72-
73- val timeSinceLastHeartbeat = SystemClock .elapsedRealtime() - lastHeartbeatTimestamp
74-
75- if (timeSinceLastHeartbeat >= appHangThresholdMillis) {
76- reportAppHang(timeSinceLastHeartbeat)
77- }
78- } catch (_: InterruptedException ) {
79- // continue loop and check isRunning
80- } finally {
81- heartbeatLock.unlock()
59+ val waitThreshold =
60+ if (lastHeartbeatTimestamp <= 0L ) appHangThresholdMillis
61+ else calculateTimeToAppHang(SystemClock .uptimeMillis())
62+
63+ val waitThresholdNanos = TimeUnit .MILLISECONDS .toNanos(waitThreshold)
64+ LockSupport .parkNanos(waitThresholdNanos)
65+
66+ if (! isRunning.get()) break
67+
68+ val timeSinceLastHeartbeat = SystemClock .uptimeMillis() - lastHeartbeatTimestamp
69+
70+ if (timeSinceLastHeartbeat >= appHangThresholdMillis) {
71+ reportAppHang(timeSinceLastHeartbeat)
72+ }
73+
74+ if (! handler.post(heartbeat)) {
75+ // handler.post returning false means the Looper has likely quit
76+ isRunning.set(false )
8277 }
8378 }
8479 }
8580
8681 private inner class Heartbeat : Runnable {
8782 override fun run () {
88- lastHeartbeatTimestamp = SystemClock .elapsedRealtime()
89- // mark the hang as "recovered" and start the detection again
83+ lastHeartbeatTimestamp = SystemClock .uptimeMillis()
9084 isAppHangDetected = false
91- resetHeartbeatTimer()
9285
93- // only post the Heartbeat messages if the monitor is still running
94- if (isRunning.get()) {
95- handler.post(this )
96- }
86+ resetHeartbeatTimer()
9787 }
9888
9989 override fun toString (): String {
0 commit comments