├── .gitignore
├── LICENSE
├── README.md
├── anr-watchdog
├── .gitignore
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── github
│ └── anrwatchdog
│ ├── ANRError.java
│ └── ANRWatchDog.java
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── testapp
├── .gitignore
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── github
│ └── anrtestapp
│ ├── ANRWatchdogTestApplication.java
│ └── MainActivity.java
└── res
├── layout
└── activity_main.xml
├── mipmap-hdpi
└── ic_launcher.png
├── mipmap-mdpi
└── ic_launcher.png
├── mipmap-xhdpi
└── ic_launcher.png
├── mipmap-xxhdpi
└── ic_launcher.png
├── mipmap-xxxhdpi
└── ic_launcher.png
└── values
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # ECLIPSE
3 | .classpath
4 | .project
5 | .settings/
6 |
7 | # INTELLIJ
8 | .idea/
9 | *.ipr
10 | *.iml
11 |
12 | # SYSTEM FILES
13 | *~
14 | .DS_STORE
15 |
16 | # GRADLE
17 | .gradle
18 | /build/
19 | gradle.properties
20 | local.properties
21 | .gradletasknamecache
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Salomon BRYS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.anrwatchdog%22)
2 | [](https://github.com/SalomonBrys/ANR-WatchDog/blob/master/LICENSE)
3 | [](https://github.com/SalomonBrys/ANR-WatchDog/issues)
4 | [](https://donorbox.org/donation-salomonbrys/)
5 |
6 |
7 | ANR-WatchDog
8 | ============
9 |
10 | A simple watchdog that detects Android ANRs (Application Not Responding).
11 |
12 |
13 | Table of contents
14 | -----------------
15 |
16 | * [ANR-WatchDog](#anr-watchdog)
17 | * [Table of contents](#table-of-contents)
18 | * [Why it exists](#why-it-exists)
19 | * [What it does](#what-it-does)
20 | * [Can it work with crash reporters?](#can-it-work-with-crash-reporters)
21 | * [How it works](#how-it-works)
22 | * [Usage](#usage)
23 | * [Install](#install)
24 | * [With Gradle / Android Studio](#with-gradle--android-studio)
25 | * [With Eclipse](#with-eclipse)
26 | * [Reading the ANRError exception report](#reading-the-anrerror-exception-report)
27 | * [Configuration](#configuration)
28 | * [Timeout (minimum hanging time for an ANR)](#timeout-minimum-hanging-time-for-an-anr)
29 | * [Debugger](#debugger)
30 | * [On ANR callback](#on-anr-callback)
31 | * [Filtering reports](#filtering-reports)
32 | * [Watchdog thread](#watchdog-thread)
33 | * [Donate](#donate)
34 |
35 |
36 | Why it exists
37 | -------------
38 |
39 | There is currently no way for an android application to catch and report ANR errors.
40 | If your application is not in the play store (either because you are still developing it or because you are distributing it differently), the only way to investigate an ANR is to pull the file /data/anr/traces.txt.
41 | Additionally, we found that using the Play Store was not as effective as being able to choose our own bug tracking service.
42 |
43 | There is an [issue entry](https://code.google.com/p/android/issues/detail?id=35380) in the android bug tracker describing this lack, feel free to star it ;)
44 |
45 |
46 | What it does
47 | ------------
48 |
49 | It sets up a "watchdog" timer that will detect when the UI thread stops responding. When it does, it raises an error with all threads stack traces (main first).
50 |
51 |
52 | Can it work with crash reporters?
53 | ---------------------------------
54 |
55 | Yes! I'm glad you asked: That's the reason why it was developed in the first place!
56 | As this throws an error, a crash handler can intercept it and handle it the way it needs.
57 |
58 | Known working crash reporters include:
59 |
60 | * [ACRA](https://github.com/ACRA/acra)
61 | * [Crashlytics](https://get.fabric.io/crashlytics) ([Only with `setReportMainThreadOnly()`](https://github.com/SalomonBrys/ANR-WatchDog/issues/29))
62 | * [HockeyApp](https://hockeyapp.net/)
63 | * [Bugsnag](https://www.bugsnag.com/)
64 |
65 | And there is no reason why it should not work with *[insert your favourite crash reporting system here]*.
66 |
67 |
68 | How it works
69 | ------------
70 |
71 | The watchdog is a simple thread that does the following in a loop:
72 |
73 | 1. Schedules a runnable to be run on the UI thread as soon as possible.
74 | 2. Wait for 5 seconds. (5 seconds is the default, but it can be configured).
75 | 3. See if the runnable has been run. If it has, go back to 1.
76 | 4. If the runnable has not been run, which means that the UI thread has been blocked for at least 5 seconds, it raises an error with all running threads stack traces.
77 |
78 |
79 | Usage
80 | =====
81 |
82 | Install
83 | -------
84 |
85 | ### With Gradle / Android Studio
86 |
87 | 1. In the `app/build.gradle` file, add:
88 |
89 | ```
90 | implementation 'com.github.anrwatchdog:anrwatchdog:1.4.0'
91 | ```
92 |
93 | 2. In your application class, in `onCreate`, add:
94 |
95 | ```java
96 | new ANRWatchDog().start();
97 | ```
98 |
99 |
100 | ### With Eclipse
101 |
102 | 1. [Download the latest jar](https://search.maven.org/remote_content?g=com.github.anrwatchdog&a=anrwatchdog&v=LATEST)
103 |
104 | 2. Put the jar in the `libs/` directory of your project
105 |
106 |
107 | Reading the ANRError exception report
108 | -------------------------------------
109 |
110 | The `ANRError` stack trace is a bit particular, it has the stack traces of all the threads running in your application. So, in the report, **each `caused by` section is not the cause of the precedent exception**, but the stack trace of a different thread.
111 |
112 | Here is a dead lock example:
113 |
114 | ```
115 | FATAL EXCEPTION: |ANR-WatchDog|
116 | Process: anrwatchdog.github.com.testapp, PID: 26737
117 | com.github.anrwatchdog.ANRError: Application Not Responding
118 | Caused by: com.github.anrwatchdog.ANRError$_$_Thread: main (state = WAITING)
119 | at testapp.MainActivity$1.run(MainActivity.java:46)
120 | at android.os.Handler.handleCallback(Handler.java:739)
121 | at android.os.Handler.dispatchMessage(Handler.java:95)
122 | at android.os.Looper.loop(Looper.java:135)
123 | at android.app.ActivityThread.main(ActivityThread.java:5221)
124 | Caused by: com.github.anrwatchdog.ANRError$_$_Thread: APP: Locker (state = TIMED_WAITING)
125 | at java.lang.Thread.sleep(Native Method)
126 | at java.lang.Thread.sleep(Thread.java:1031)
127 | at java.lang.Thread.sleep(Thread.java:985)
128 | at testapp.MainActivity.SleepAMinute(MainActivity.java:18)
129 | at testapp.MainActivity.access$100(MainActivity.java:12)
130 | at testapp.MainActivity$LockerThread.run(MainActivity.java:36)
131 | ```
132 |
133 | From this report, we can see that the stack traces of two threads. The first (the "main" thread) is stuck at `MainActivity.java:46` while the second thread (named "App: Locker") is locked in a Sleep at `MainActivity.java:18`.
134 | From there, if we looked at those two lines, we would surely understand the cause of the dead lock!
135 |
136 | Note that some crash reporting library (such as Crashlytics) report all thread stack traces at the time of an uncaught exception. In that case, having all threads in the same exception can be cumbersome. In such cases, simply use `setReportMainThreadOnly()`.
137 |
138 |
139 | Configuration
140 | -------------
141 |
142 | ### Timeout (minimum hanging time for an ANR)
143 |
144 | To set a different timeout (5000 millis is the default):
145 |
146 | ```java
147 | if (BuildConfig.DEBUG == false) {
148 | new ANRWatchDog(10000 /*timeout*/).start();
149 | }
150 | ```
151 |
152 |
153 | ### Debugger
154 |
155 | By default, the watchdog will ignore ANRs if the debugger is attached or if the app is waiting for the debugger to attach. This is because it detects execution pauses and breakpoints as ANRs.
156 | To disable this and throw an `ANRError` even if the debugger is connected, you can add `setIgnoreDebugger(true)`:
157 |
158 | ```java
159 | new ANRWatchDog().setIgnoreDebugger(true).start();
160 | ```
161 |
162 |
163 | ### On ANR callback
164 |
165 | If you would prefer not to crash the application when an ANR is detected, you can enable a callback instead:
166 |
167 | ```java
168 | new ANRWatchDog().setANRListener(new ANRWatchDog.ANRListener() {
169 | @Override
170 | public void onAppNotResponding(ANRError error) {
171 | // Handle the error. For example, log it to HockeyApp:
172 | ExceptionHandler.saveException(error, new CrashManager());
173 | }
174 | }).start();
175 | ```
176 |
177 | **This is very important when delivering your app in production.**
178 | When in the hand of the final user, it's *probably better* not to crash after 5 seconds, but simply report the ANR to whatever reporting system you use.
179 | Maybe, after some more seconds, the app will "de-freeze".
180 |
181 |
182 | ### Filtering reports
183 |
184 | If you would like to have only your own threads to be reported in the ANRError, and not all threads (including system threads such as the `FinalizerDaemon` thread), you can set a prefix: only the threads whose name starts with this prefix will be reported.
185 |
186 | ```java
187 | new ANRWatchDog().setReportThreadNamePrefix("APP:").start();
188 | ```
189 |
190 | Then, when you start a thread, don't forget to set its name to something that starts with this prefix (if you want it to be reported):
191 |
192 | ```java
193 | public class MyAmazingThread extends Thread {
194 | @Override
195 | public void run() {
196 | setName("APP: Amazing!");
197 | /* ... do amazing things ... */
198 | }
199 | }
200 | ```
201 |
202 | If you want to have only the main thread stack trace and not all the other threads, you can:
203 |
204 | ```java
205 | new ANRWatchDog().setReportMainThreadOnly().start();
206 | ```
207 |
208 |
209 | ### ANR Interceptor
210 |
211 | Sometimes, you want to know that the application has froze for a certain duration, but not report the ANR error just yet.
212 | You can define an interceptor that will be called before reporting an error.
213 | The role of the interceptor is to define whether or not, given the given freeze duration, an ANR error should be raised or postponed.
214 |
215 | ```java
216 | new ANRWatchDog(2000).setANRInterceptor(new ANRWatchDog.ANRInterceptor() {
217 | @Override
218 | public long intercept(long duration) {
219 | long ret = 5000 - duration;
220 | if (ret > 0) {
221 | Log.w(TAG, "Intercepted ANR that is too short (" + duration + " ms), postponing for " + ret + " ms.");
222 | }
223 | return ret;
224 | }
225 | })
226 | ```
227 |
228 | In this example, the ANRWatchDog starts with a timeout of 2000 ms, but the interceptor will postpone the error until at least 5000 ms of freeze has been reached.
229 |
230 |
231 | ### Watchdog thread
232 |
233 | ANRWatchDog is a thread, so you can interrupt it at any time.
234 |
235 | If you are programming with Android's multi process capability (like starting an activity in a new process), remember that you will need an ANRWatchDog thread per process.
236 |
237 |
238 | Donate
239 | ======
240 |
241 | ANR-Watchdog is free to use for both non-profit and commercial use and always will be.
242 |
243 | If you wish to show some support or appreciation to my work, you are free to **[donate](https://donorbox.org/donation-salomonbrys)**!
244 |
245 | This would be (of course) greatly appreciated but is by no means necessary to receive help or support, which I'll be happy to provide for free :)
246 |
--------------------------------------------------------------------------------
/anr-watchdog/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/anr-watchdog/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | apply plugin: 'com.android.library'
3 | apply plugin: 'maven'
4 | apply plugin: 'signing'
5 |
6 | group = "com.github.anrwatchdog"
7 | version = "1.4.0"
8 |
9 | sourceCompatibility = 1.6
10 |
11 | task javadoc(type: Javadoc) {
12 | failOnError false
13 | source = android.sourceSets.main.java.sourceFiles
14 | }
15 |
16 | task javadocJar(type: Jar, dependsOn: javadoc) {
17 | classifier 'javadoc'
18 | from javadoc.destinationDir
19 | }
20 |
21 | task sourcesJar(type: Jar) {
22 | from android.sourceSets.main.java.srcDirs
23 | classifier = 'sources'
24 | }
25 |
26 | artifacts {
27 | archives javadocJar, sourcesJar
28 | }
29 |
30 | signing {
31 | sign configurations.archives
32 | }
33 |
34 | configurations {
35 | provided
36 | compile.extendsFrom provided
37 | }
38 |
39 | android {
40 | compileSdkVersion 29
41 | defaultConfig {
42 | minSdkVersion 14
43 | targetSdkVersion 28
44 | }
45 | }
46 |
47 | dependencies {
48 | implementation 'androidx.annotation:annotation:1.1.0'
49 | }
50 |
51 | uploadArchives {
52 | repositories {
53 | mavenDeployer {
54 | beforeDeployment { deployment -> signing.signPom(deployment) }
55 |
56 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
57 | authentication(userName: ossrhUsername, password: ossrhPassword)
58 | }
59 |
60 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
61 | authentication(userName: ossrhUsername, password: ossrhPassword)
62 | }
63 |
64 | pom.project {
65 | name 'ANR-Watchdog'
66 | packaging 'jar'
67 | artifactId 'anrwatchdog'
68 | description 'A simple watchdog that detects Android ANR (Application Not Responding) error and throws a meaningful exception'
69 | url 'https://github.com/SalomonBrys/ANR-WatchDog'
70 |
71 | licenses {
72 | license {
73 | name 'MIT'
74 | url 'http://opensource.org/licenses/MIT'
75 | }
76 | }
77 |
78 | developers {
79 | developer {
80 | id 'salomonbrys'
81 | name 'Salomon BRYS'
82 | email 'salomon.brys@gmail.com'
83 | }
84 | }
85 |
86 | scm {
87 | url 'https://github.com/SalomonBrys/ANR-WatchDog'
88 | connection 'scm:git:https://github.com/SalomonBrys/ANR-WatchDog.git'
89 | developerConnection 'scm:git:git@github.com:SalomonBrys/ANR-WatchDog.git'
90 | tag 'HEAD'
91 | }
92 |
93 | issueManagement {
94 | system 'GitHub Issues'
95 | url 'https://github.com/SalomonBrys/ANR-WatchDog/issues'
96 | }
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/anr-watchdog/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRError.java:
--------------------------------------------------------------------------------
1 | package com.github.anrwatchdog;
2 |
3 | import android.os.Looper;
4 |
5 | import java.io.Serializable;
6 | import java.util.Comparator;
7 | import java.util.Map;
8 | import java.util.TreeMap;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 |
13 | /**
14 | * Error thrown by {@link com.github.anrwatchdog.ANRWatchDog} when an ANR is detected.
15 | * Contains the stack trace of the frozen UI thread.
16 | *
17 | * It is important to notice that, in an ANRError, all the "Caused by" are not really the cause
18 | * of the exception. Each "Caused by" is the stack trace of a running thread. Note that the main
19 | * thread always comes first.
20 | */
21 | public class ANRError extends Error {
22 |
23 | private static class $ implements Serializable {
24 | private final String _name;
25 | private final StackTraceElement[] _stackTrace;
26 |
27 | private class _Thread extends Throwable {
28 | private _Thread(_Thread other) {
29 | super(_name, other);
30 | }
31 |
32 | @Override
33 | @NonNull
34 | public Throwable fillInStackTrace() {
35 | setStackTrace(_stackTrace);
36 | return this;
37 | }
38 | }
39 |
40 | private $(String name, StackTraceElement[] stackTrace) {
41 | _name = name;
42 | _stackTrace = stackTrace;
43 | }
44 | }
45 |
46 | private static final long serialVersionUID = 1L;
47 |
48 | /**
49 | * The minimum duration, in ms, for which the main thread has been blocked. May be more.
50 | */
51 | @SuppressWarnings("WeakerAccess")
52 | public final long duration;
53 |
54 | private ANRError($._Thread st, long duration) {
55 | super("Application Not Responding for at least " + duration + " ms.", st);
56 | this.duration = duration;
57 | }
58 |
59 | @Override
60 | @NonNull
61 | public Throwable fillInStackTrace() {
62 | setStackTrace(new StackTraceElement[] {});
63 | return this;
64 | }
65 |
66 | @NonNull
67 | static ANRError New(long duration, @Nullable String prefix, boolean logThreadsWithoutStackTrace) {
68 | final Thread mainThread = Looper.getMainLooper().getThread();
69 |
70 | final Map stackTraces = new TreeMap(new Comparator() {
71 | @Override
72 | public int compare(Thread lhs, Thread rhs) {
73 | if (lhs == rhs)
74 | return 0;
75 | if (lhs == mainThread)
76 | return 1;
77 | if (rhs == mainThread)
78 | return -1;
79 | return rhs.getName().compareTo(lhs.getName());
80 | }
81 | });
82 |
83 | for (Map.Entry entry : Thread.getAllStackTraces().entrySet())
84 | if (
85 | entry.getKey() == mainThread
86 | || (
87 | entry.getKey().getName().startsWith(prefix)
88 | && (
89 | logThreadsWithoutStackTrace
90 | ||
91 | entry.getValue().length > 0
92 | )
93 | )
94 | )
95 | stackTraces.put(entry.getKey(), entry.getValue());
96 |
97 | // Sometimes main is not returned in getAllStackTraces() - ensure that we list it
98 | if (!stackTraces.containsKey(mainThread)) {
99 | stackTraces.put(mainThread, mainThread.getStackTrace());
100 | }
101 |
102 | $._Thread tst = null;
103 | for (Map.Entry entry : stackTraces.entrySet())
104 | tst = new $(getThreadTitle(entry.getKey()), entry.getValue()).new _Thread(tst);
105 |
106 | return new ANRError(tst, duration);
107 | }
108 |
109 | @NonNull
110 | static ANRError NewMainOnly(long duration) {
111 | final Thread mainThread = Looper.getMainLooper().getThread();
112 | final StackTraceElement[] mainStackTrace = mainThread.getStackTrace();
113 |
114 | return new ANRError(new $(getThreadTitle(mainThread), mainStackTrace).new _Thread(null), duration);
115 | }
116 |
117 | private static String getThreadTitle(Thread thread) {
118 | return thread.getName() + " (state = " + thread.getState() + ")";
119 | }
120 | }
--------------------------------------------------------------------------------
/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRWatchDog.java:
--------------------------------------------------------------------------------
1 | package com.github.anrwatchdog;
2 |
3 | /*
4 | * The MIT License (MIT)
5 | *
6 | * Copyright (c) 2016 Salomon BRYS
7 | *
8 | * Permission is hereby granted, free of charge, to any person obtaining a copy of
9 | * this software and associated documentation files (the "Software"), to deal in
10 | * the Software without restriction, including without limitation the rights to
11 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12 | * the Software, and to permit persons to whom the Software is furnished to do so,
13 | * subject to the following conditions:
14 | *
15 | * The above copyright notice and this permission notice shall be included in all
16 | * copies or substantial portions of the Software.
17 | *
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | */
25 |
26 | import android.os.Debug;
27 | import android.os.Handler;
28 | import android.os.Looper;
29 | import android.util.Log;
30 |
31 | import androidx.annotation.NonNull;
32 | import androidx.annotation.Nullable;
33 |
34 | /**
35 | * A watchdog timer thread that detects when the UI thread has frozen.
36 | */
37 | @SuppressWarnings("UnusedReturnValue")
38 | public class ANRWatchDog extends Thread {
39 |
40 | public interface ANRListener {
41 | /**
42 | * Called when an ANR is detected.
43 | *
44 | * @param error The error describing the ANR.
45 | */
46 | void onAppNotResponding(@NonNull ANRError error);
47 | }
48 |
49 | public interface ANRInterceptor {
50 | /**
51 | * Called when main thread has froze more time than defined by the timeout.
52 | *
53 | * @param duration The minimum time (in ms) the main thread has been frozen (may be more).
54 | * @return 0 or negative if the ANR should be reported immediately. A positive number of ms to postpone the reporting.
55 | */
56 | long intercept(long duration);
57 | }
58 |
59 | public interface InterruptionListener {
60 | void onInterrupted(@NonNull InterruptedException exception);
61 | }
62 |
63 | private static final int DEFAULT_ANR_TIMEOUT = 5000;
64 |
65 | private static final ANRListener DEFAULT_ANR_LISTENER = new ANRListener() {
66 | @Override public void onAppNotResponding(@NonNull ANRError error) {
67 | throw error;
68 | }
69 | };
70 |
71 | private static final ANRInterceptor DEFAULT_ANR_INTERCEPTOR = new ANRInterceptor() {
72 | @Override public long intercept(long duration) {
73 | return 0;
74 | }
75 | };
76 |
77 | private static final InterruptionListener DEFAULT_INTERRUPTION_LISTENER = new InterruptionListener() {
78 | @Override public void onInterrupted(@NonNull InterruptedException exception) {
79 | Log.w("ANRWatchdog", "Interrupted: " + exception.getMessage());
80 | }
81 | };
82 |
83 | private ANRListener _anrListener = DEFAULT_ANR_LISTENER;
84 | private ANRInterceptor _anrInterceptor = DEFAULT_ANR_INTERCEPTOR;
85 | private InterruptionListener _interruptionListener = DEFAULT_INTERRUPTION_LISTENER;
86 |
87 | private final Handler _uiHandler = new Handler(Looper.getMainLooper());
88 | private final int _timeoutInterval;
89 |
90 | private String _namePrefix = "";
91 | private boolean _logThreadsWithoutStackTrace = false;
92 | private boolean _ignoreDebugger = false;
93 |
94 | private volatile long _tick = 0;
95 | private volatile boolean _reported = false;
96 |
97 | private final Runnable _ticker = new Runnable() {
98 | @Override public void run() {
99 | _tick = 0;
100 | _reported = false;
101 | }
102 | };
103 |
104 | /**
105 | * Constructs a watchdog that checks the ui thread every {@value #DEFAULT_ANR_TIMEOUT} milliseconds
106 | */
107 | public ANRWatchDog() {
108 | this(DEFAULT_ANR_TIMEOUT);
109 | }
110 |
111 | /**
112 | * Constructs a watchdog that checks the ui thread every given interval
113 | *
114 | * @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
115 | * It is therefore the maximum time the UI may freeze before being reported as ANR.
116 | */
117 | public ANRWatchDog(int timeoutInterval) {
118 | super();
119 | _timeoutInterval = timeoutInterval;
120 | }
121 |
122 | /**
123 | * @return The interval the WatchDog
124 | */
125 | public int getTimeoutInterval() {
126 | return _timeoutInterval;
127 | }
128 |
129 | /**
130 | * Sets an interface for when an ANR is detected.
131 | * If not set, the default behavior is to throw an error and crash the application.
132 | *
133 | * @param listener The new listener or null
134 | * @return itself for chaining.
135 | */
136 | @NonNull
137 | public ANRWatchDog setANRListener(@Nullable ANRListener listener) {
138 | if (listener == null) {
139 | _anrListener = DEFAULT_ANR_LISTENER;
140 | } else {
141 | _anrListener = listener;
142 | }
143 | return this;
144 | }
145 |
146 | /**
147 | * Sets an interface to intercept ANRs before they are reported.
148 | * If set, you can define if, given the current duration of the detected ANR and external context, it is necessary to report the ANR.
149 | *
150 | * @param interceptor The new interceptor or null
151 | * @return itself for chaining.
152 | */
153 | @NonNull
154 | public ANRWatchDog setANRInterceptor(@Nullable ANRInterceptor interceptor) {
155 | if (interceptor == null) {
156 | _anrInterceptor = DEFAULT_ANR_INTERCEPTOR;
157 | } else {
158 | _anrInterceptor = interceptor;
159 | }
160 | return this;
161 | }
162 |
163 | /**
164 | * Sets an interface for when the watchdog thread is interrupted.
165 | * If not set, the default behavior is to just log the interruption message.
166 | *
167 | * @param listener The new listener or null.
168 | * @return itself for chaining.
169 | */
170 | @NonNull
171 | public ANRWatchDog setInterruptionListener(@Nullable InterruptionListener listener) {
172 | if (listener == null) {
173 | _interruptionListener = DEFAULT_INTERRUPTION_LISTENER;
174 | } else {
175 | _interruptionListener = listener;
176 | }
177 | return this;
178 | }
179 |
180 | /**
181 | * Set the prefix that a thread's name must have for the thread to be reported.
182 | * Note that the main thread is always reported.
183 | * Default "".
184 | *
185 | * @param prefix The thread name's prefix for a thread to be reported.
186 | * @return itself for chaining.
187 | */
188 | @NonNull
189 | public ANRWatchDog setReportThreadNamePrefix(@Nullable String prefix) {
190 | if (prefix == null) {
191 | prefix = "";
192 | }
193 | _namePrefix = prefix;
194 | return this;
195 | }
196 |
197 | /**
198 | * Set that only the main thread will be reported.
199 | *
200 | * @return itself for chaining.
201 | */
202 | @NonNull
203 | public ANRWatchDog setReportMainThreadOnly() {
204 | _namePrefix = null;
205 | return this;
206 | }
207 |
208 | /**
209 | * Set that all threads will be reported (default behaviour).
210 | *
211 | * @return itself for chaining.
212 | */
213 | @NonNull
214 | public ANRWatchDog setReportAllThreads() {
215 | _namePrefix = "";
216 | return this;
217 | }
218 |
219 | /**
220 | * Set that all running threads will be reported,
221 | * even those from which no stack trace could be extracted.
222 | * Default false.
223 | *
224 | * @param logThreadsWithoutStackTrace Whether or not all running threads should be reported
225 | * @return itself for chaining.
226 | */
227 | @NonNull
228 | public ANRWatchDog setLogThreadsWithoutStackTrace(boolean logThreadsWithoutStackTrace) {
229 | _logThreadsWithoutStackTrace = logThreadsWithoutStackTrace;
230 | return this;
231 | }
232 |
233 | /**
234 | * Set whether to ignore the debugger when detecting ANRs.
235 | * When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected.
236 | * By default, it does not, to avoid interpreting debugging pauses as ANRs.
237 | * Default false.
238 | *
239 | * @param ignoreDebugger Whether to ignore the debugger.
240 | * @return itself for chaining.
241 | */
242 | @NonNull
243 | public ANRWatchDog setIgnoreDebugger(boolean ignoreDebugger) {
244 | _ignoreDebugger = ignoreDebugger;
245 | return this;
246 | }
247 |
248 | @SuppressWarnings("NonAtomicOperationOnVolatileField")
249 | @Override
250 | public void run() {
251 | setName("|ANR-WatchDog|");
252 |
253 | long interval = _timeoutInterval;
254 | while (!isInterrupted()) {
255 | boolean needPost = _tick == 0;
256 | _tick += interval;
257 | if (needPost) {
258 | _uiHandler.post(_ticker);
259 | }
260 |
261 | try {
262 | Thread.sleep(interval);
263 | } catch (InterruptedException e) {
264 | _interruptionListener.onInterrupted(e);
265 | return ;
266 | }
267 |
268 | // If the main thread has not handled _ticker, it is blocked. ANR.
269 | if (_tick != 0 && !_reported) {
270 | //noinspection ConstantConditions
271 | if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
272 | Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
273 | _reported = true;
274 | continue ;
275 | }
276 |
277 | interval = _anrInterceptor.intercept(_tick);
278 | if (interval > 0) {
279 | continue;
280 | }
281 |
282 | final ANRError error;
283 | if (_namePrefix != null) {
284 | error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
285 | } else {
286 | error = ANRError.NewMainOnly(_tick);
287 | }
288 | _anrListener.onAppNotResponding(error);
289 | interval = _timeoutInterval;
290 | _reported = true;
291 | }
292 | }
293 | }
294 |
295 | }
296 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | google()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.2'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 06 12:43:28 CET 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':anr-watchdog', ':testapp'
2 |
--------------------------------------------------------------------------------
/testapp/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/testapp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 | defaultConfig {
7 | applicationId "anrwatchdog.github.com.testapp"
8 | minSdkVersion 14
9 | targetSdkVersion 28
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | }
14 |
15 | dependencies {
16 | implementation project(':anr-watchdog')
17 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
18 | implementation 'androidx.annotation:annotation:1.1.0'
19 | }
20 |
--------------------------------------------------------------------------------
/testapp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/testapp/src/main/java/com/github/anrtestapp/ANRWatchdogTestApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.anrtestapp;
2 |
3 | import android.app.Application;
4 | import android.util.Log;
5 |
6 | import com.github.anrwatchdog.ANRError;
7 | import com.github.anrwatchdog.ANRWatchDog;
8 |
9 | import java.io.ByteArrayOutputStream;
10 | import java.io.IOException;
11 | import java.io.ObjectOutputStream;
12 |
13 | import androidx.annotation.NonNull;
14 |
15 | public class ANRWatchdogTestApplication extends Application {
16 |
17 | ANRWatchDog anrWatchDog = new ANRWatchDog(2000);
18 |
19 | int duration = 4;
20 |
21 | final ANRWatchDog.ANRListener silentListener = new ANRWatchDog.ANRListener() {
22 | @Override
23 | public void onAppNotResponding(@NonNull ANRError error) {
24 | Log.e("ANR-Watchdog-Demo", "", error);
25 | }
26 | };
27 |
28 | @Override
29 | public void onCreate() {
30 | super.onCreate();
31 |
32 | anrWatchDog
33 | .setANRListener(new ANRWatchDog.ANRListener() {
34 | @Override
35 | public void onAppNotResponding(@NonNull ANRError error) {
36 | Log.e("ANR-Watchdog-Demo", "Detected Application Not Responding!");
37 |
38 | // Some tools like ACRA are serializing the exception, so we must make sure the exception serializes correctly
39 | try {
40 | new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(error);
41 | }
42 | catch (IOException ex) {
43 | throw new RuntimeException(ex);
44 | }
45 |
46 | Log.i("ANR-Watchdog-Demo", "Error was successfully serialized");
47 |
48 | throw error;
49 | }
50 | })
51 | .setANRInterceptor(new ANRWatchDog.ANRInterceptor() {
52 | @Override
53 | public long intercept(long duration) {
54 | long ret = ANRWatchdogTestApplication.this.duration * 1000 - duration;
55 | if (ret > 0)
56 | Log.w("ANR-Watchdog-Demo", "Intercepted ANR that is too short (" + duration + " ms), postponing for " + ret + " ms.");
57 | return ret;
58 | }
59 | })
60 | ;
61 |
62 | anrWatchDog.start();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/testapp/src/main/java/com/github/anrtestapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.anrtestapp;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.widget.Button;
10 |
11 |
12 | public class MainActivity extends Activity {
13 |
14 | private final Object _mutex = new Object();
15 |
16 | private static void Sleep() {
17 | try {
18 | Thread.sleep(8 * 1000);
19 | }
20 | catch (InterruptedException e) {
21 | e.printStackTrace();
22 | }
23 | }
24 |
25 | private static void InfiniteLoop() {
26 | int i = 0;
27 | //noinspection InfiniteLoopStatement
28 | while (true) {
29 | i++;
30 | }
31 | }
32 |
33 | public class LockerThread extends Thread {
34 |
35 | LockerThread() {
36 | setName("APP: Locker");
37 | }
38 |
39 | @Override
40 | public void run() {
41 | synchronized (_mutex) {
42 | //noinspection InfiniteLoopStatement
43 | while (true)
44 | Sleep();
45 | }
46 | }
47 | }
48 |
49 | private void _deadLock() {
50 | new LockerThread().start();
51 |
52 | new Handler().postDelayed(new Runnable() {
53 | @Override public void run() {
54 | synchronized (_mutex) {
55 | Log.e("ANR-Failed", "There should be a dead lock before this message");
56 | }
57 | }
58 | }, 1000);
59 | }
60 |
61 | private int mode = 0;
62 | private boolean crash = true;
63 |
64 | @SuppressLint("SetTextI18n")
65 | @Override
66 | protected void onCreate(Bundle savedInstanceState) {
67 | super.onCreate(savedInstanceState);
68 | setContentView(R.layout.activity_main);
69 |
70 | final ANRWatchdogTestApplication application = (ANRWatchdogTestApplication) getApplication();
71 |
72 | final Button minAnrDurationButton = (Button) findViewById(R.id.minAnrDuration);
73 | minAnrDurationButton.setText(application.duration + " seconds");
74 | minAnrDurationButton.setOnClickListener(new View.OnClickListener() {
75 | @Override
76 | public void onClick(View v) {
77 | application.duration = application.duration % 6 + 2;
78 | minAnrDurationButton.setText(application.duration + " seconds");
79 | }
80 | });
81 |
82 | final Button reportModeButton = (Button) findViewById(R.id.reportMode);
83 | reportModeButton.setText("All threads");
84 | reportModeButton.setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | mode = (mode + 1) % 3;
88 | switch (mode) {
89 | case 0:
90 | reportModeButton.setText("All threads");
91 | application.anrWatchDog.setReportAllThreads();
92 | break ;
93 | case 1:
94 | reportModeButton.setText("Main thread only");
95 | application.anrWatchDog.setReportMainThreadOnly();
96 | break ;
97 | case 2:
98 | reportModeButton.setText("Filtered");
99 | application.anrWatchDog.setReportThreadNamePrefix("APP:");
100 | break ;
101 | }
102 | }
103 | });
104 |
105 | final Button behaviourButton = (Button) findViewById(R.id.behaviour);
106 | behaviourButton.setText("Crash");
107 | behaviourButton.setOnClickListener(new View.OnClickListener() {
108 | @Override
109 | public void onClick(View v) {
110 | crash = !crash;
111 | if (crash) {
112 | behaviourButton.setText("Crash");
113 | application.anrWatchDog.setANRListener(null);
114 | } else {
115 | behaviourButton.setText("Silent");
116 | application.anrWatchDog.setANRListener(application.silentListener);
117 | }
118 | }
119 | });
120 |
121 | findViewById(R.id.threadSleep).setOnClickListener(new View.OnClickListener() {
122 | @Override public void onClick(View v) {
123 | Sleep();
124 | }
125 | });
126 |
127 | findViewById(R.id.infiniteLoop).setOnClickListener(new View.OnClickListener() {
128 | @Override public void onClick(View v) {
129 | InfiniteLoop();
130 | }
131 | });
132 |
133 | findViewById(R.id.deadlock).setOnClickListener(new View.OnClickListener() {
134 | @Override public void onClick(View v) {
135 | _deadLock();
136 | }
137 | });
138 |
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/testapp/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
20 |
21 |
22 |
37 |
38 |
50 |
51 |
60 |
61 |
70 |
71 |
81 |
82 |
91 |
92 |
107 |
108 |
109 |
119 |
120 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/testapp/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/testapp/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SalomonBrys/ANR-WatchDog/4d7b4bb288d34a604be80c3c1b13ec85c0c5b06a/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ANR-Watchdog Demo
4 | Thread sleep
5 | Infinite loop
6 | Dead lock
7 | Min ANR duration:
8 | Report mode:
9 | Filter report:
10 | Behaviour:
11 |
--------------------------------------------------------------------------------