-
Notifications
You must be signed in to change notification settings - Fork 205
/
BlockedThreadDetector.java
121 lines (103 loc) · 3.83 KB
/
BlockedThreadDetector.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.bugsnag.android;
import android.app.ActivityManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.SystemClock;
/**
* Detects whether a given thread is blocked by continuously posting a {@link Runnable} to it
* from a watcher thread, invoking a delegate if the message is not processed within
* a configured interval.
*/
final class BlockedThreadDetector {
static final int MIN_CHECK_INTERVAL_MS = 1000;
interface Delegate {
/**
* Invoked when a given thread has been unable to execute a {@link Runnable} within
* the {@link #blockedThresholdMs}
*
* @param thread the thread being monitored
*/
void onThreadBlocked(Thread thread);
}
final Looper looper;
final long checkIntervalMs;
final long blockedThresholdMs;
final Handler uiHandler;
final Handler watchdogHandler;
private final HandlerThread watchdogHandlerThread;
final Delegate delegate;
volatile long lastUpdateMs;
volatile boolean isAlreadyBlocked = false;
BlockedThreadDetector(long blockedThresholdMs,
Looper looper,
Delegate delegate) {
this(blockedThresholdMs, MIN_CHECK_INTERVAL_MS, looper, delegate);
}
BlockedThreadDetector(long blockedThresholdMs,
long checkIntervalMs,
Looper looper,
Delegate delegate) {
if ((blockedThresholdMs <= 0 || checkIntervalMs <= 0
|| looper == null || delegate == null)) {
throw new IllegalArgumentException();
}
this.blockedThresholdMs = blockedThresholdMs;
this.checkIntervalMs = checkIntervalMs;
this.looper = looper;
this.delegate = delegate;
this.uiHandler = new Handler(looper);
watchdogHandlerThread = new HandlerThread("bugsnag-anr-watchdog");
watchdogHandlerThread.start();
watchdogHandler = new Handler(watchdogHandlerThread.getLooper());
}
void start() {
updateLivenessTimestamp();
uiHandler.post(livenessCheck);
watchdogHandler.postDelayed(watchdogCheck, calculateNextCheckIn());
}
void updateLivenessTimestamp() {
lastUpdateMs = SystemClock.uptimeMillis();
}
final Runnable livenessCheck = new Runnable() {
@Override
public void run() {
updateLivenessTimestamp();
uiHandler.postDelayed(this, checkIntervalMs);
}
};
final Runnable watchdogCheck = new Runnable() {
@Override
public void run() {
checkIfThreadBlocked();
watchdogHandler.postDelayed(this, calculateNextCheckIn());
}
};
long calculateNextCheckIn() {
long currentUptimeMs = SystemClock.uptimeMillis();
return Math.max(lastUpdateMs + blockedThresholdMs - currentUptimeMs, 0);
}
void checkIfThreadBlocked() {
long delta = SystemClock.uptimeMillis() - lastUpdateMs;
boolean inForeground = isInForeground();
if (inForeground && delta > blockedThresholdMs) {
if (!isAlreadyBlocked) {
delegate.onThreadBlocked(looper.getThread());
}
isAlreadyBlocked = true; // prevents duplicate reports for the same ANR
} else {
isAlreadyBlocked = false;
}
}
private boolean isInForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityManager.RunningAppProcessInfo info
= new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(info);
return info.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
} else {
return true;
}
}
}