package com.basic.security.utils;/*
|
* The MIT License (MIT)
|
*
|
* Copyright (c) 2016 Salomon BRYS
|
*
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* this software and associated documentation files (the "Software"), to deal in
|
* the Software without restriction, including without limitation the rights to
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
* subject to the following conditions:
|
*
|
* The above copyright notice and this permission notice shall be included in all
|
* copies or substantial portions of the Software.
|
*
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
*/
|
|
import android.os.Debug;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.util.Log;
|
|
/**
|
* A watchdog timer thread that detects when the UI thread has frozen.
|
*/
|
@SuppressWarnings("UnusedReturnValue")
|
public class ANRWatchDog extends Thread {
|
private static final int DEFAULT_ANR_TIMEOUT = 10 * 1000;
|
private static final ANRListener DEFAULT_ANR_LISTENER = new ANRListener() {
|
@Override
|
public void onAppNotResponding(ANRError error) {
|
throw error;
|
}
|
};
|
private static final ANRInterceptor DEFAULT_ANR_INTERCEPTOR = new ANRInterceptor() {
|
@Override
|
public long intercept(long duration) {
|
return 0;
|
}
|
};
|
private static final InterruptionListener DEFAULT_INTERRUPTION_LISTENER = new InterruptionListener() {
|
@Override
|
public void onInterrupted(InterruptedException exception) {
|
Log.w("ANRWatchdog", "Interrupted: " + exception.getMessage());
|
}
|
};
|
private final Handler _uiHandler = new Handler(Looper.getMainLooper());
|
private final int _timeoutInterval;
|
private ANRListener _anrListener = DEFAULT_ANR_LISTENER;
|
private ANRInterceptor _anrInterceptor = DEFAULT_ANR_INTERCEPTOR;
|
private InterruptionListener _interruptionListener = DEFAULT_INTERRUPTION_LISTENER;
|
private String _namePrefix = "";
|
private boolean _logThreadsWithoutStackTrace = false;
|
private boolean _ignoreDebugger = false;
|
private volatile long _tick = 0;
|
private volatile boolean _reported = false;
|
private final Runnable _ticker = new Runnable() {
|
@Override
|
public void run() {
|
_tick = 0;
|
_reported = false;
|
}
|
};
|
|
/**
|
* Constructs a watchdog that checks the ui thread every {@value #DEFAULT_ANR_TIMEOUT} milliseconds
|
*/
|
public ANRWatchDog() {
|
this(DEFAULT_ANR_TIMEOUT);
|
}
|
|
/**
|
* Constructs a watchdog that checks the ui thread every given interval
|
*
|
* @param timeoutInterval The interval, in milliseconds, between to checks of the UI thread.
|
* It is therefore the maximum time the UI may freeze before being reported as ANR.
|
*/
|
public ANRWatchDog(int timeoutInterval) {
|
super();
|
_timeoutInterval = timeoutInterval;
|
}
|
|
/**
|
* @return The interval the WatchDog
|
*/
|
public int getTimeoutInterval() {
|
return _timeoutInterval;
|
}
|
|
/**
|
* Sets an interface for when an ANR is detected.
|
* If not set, the default behavior is to throw an error and crash the application.
|
*
|
* @param listener The new listener or null
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setANRListener(ANRListener listener) {
|
if (listener == null) {
|
_anrListener = DEFAULT_ANR_LISTENER;
|
} else {
|
_anrListener = listener;
|
}
|
return this;
|
}
|
|
/**
|
* Sets an interface to intercept ANRs before they are reported.
|
* If set, you can define if, given the current duration of the detected ANR and external context, it is necessary to report the ANR.
|
*
|
* @param interceptor The new interceptor or null
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setANRInterceptor(ANRInterceptor interceptor) {
|
if (interceptor == null) {
|
_anrInterceptor = DEFAULT_ANR_INTERCEPTOR;
|
} else {
|
_anrInterceptor = interceptor;
|
}
|
return this;
|
}
|
|
/**
|
* Sets an interface for when the watchdog thread is interrupted.
|
* If not set, the default behavior is to just log the interruption message.
|
*
|
* @param listener The new listener or null.
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setInterruptionListener(InterruptionListener listener) {
|
if (listener == null) {
|
_interruptionListener = DEFAULT_INTERRUPTION_LISTENER;
|
} else {
|
_interruptionListener = listener;
|
}
|
return this;
|
}
|
|
/**
|
* Set the prefix that a thread's name must have for the thread to be reported.
|
* Note that the main thread is always reported.
|
* Default "".
|
*
|
* @param prefix The thread name's prefix for a thread to be reported.
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setReportThreadNamePrefix(String prefix) {
|
if (prefix == null) {
|
prefix = "";
|
}
|
_namePrefix = prefix;
|
return this;
|
}
|
|
/**
|
* Set that only the main thread will be reported.
|
*
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setReportMainThreadOnly() {
|
_namePrefix = null;
|
return this;
|
}
|
|
/**
|
* Set that all threads will be reported (default behaviour).
|
*
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setReportAllThreads() {
|
_namePrefix = "";
|
return this;
|
}
|
|
/**
|
* Set that all running threads will be reported,
|
* even those from which no stack trace could be extracted.
|
* Default false.
|
*
|
* @param logThreadsWithoutStackTrace Whether or not all running threads should be reported
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setLogThreadsWithoutStackTrace(boolean logThreadsWithoutStackTrace) {
|
_logThreadsWithoutStackTrace = logThreadsWithoutStackTrace;
|
return this;
|
}
|
|
/**
|
* Set whether to ignore the debugger when detecting ANRs.
|
* When ignoring the debugger, ANRWatchdog will detect ANRs even if the debugger is connected.
|
* By default, it does not, to avoid interpreting debugging pauses as ANRs.
|
* Default false.
|
*
|
* @param ignoreDebugger Whether to ignore the debugger.
|
* @return itself for chaining.
|
*/
|
public ANRWatchDog setIgnoreDebugger(boolean ignoreDebugger) {
|
_ignoreDebugger = ignoreDebugger;
|
return this;
|
}
|
|
@SuppressWarnings("NonAtomicOperationOnVolatileField")
|
@Override
|
public void run() {
|
setName("|ANR-WatchDog|");
|
long interval = _timeoutInterval;
|
while (!isInterrupted()) {
|
boolean needPost = _tick == 0;
|
_tick += interval;
|
if (needPost) {
|
_uiHandler.post(_ticker);
|
}
|
try {
|
Thread.sleep(interval);
|
} catch (InterruptedException e) {
|
_interruptionListener.onInterrupted(e);
|
return;
|
}
|
// If the main thread has not handled _ticker, it is blocked. ANR.
|
if (_tick != 0 && !_reported) {
|
//noinspection ConstantConditions
|
if (!_ignoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
|
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
|
_reported = true;
|
continue;
|
}
|
interval = _anrInterceptor.intercept(_tick);
|
if (interval > 0) {
|
continue;
|
}
|
final ANRError error;
|
if (_namePrefix != null) {
|
System.out.println("ANRWatchDog.run 1");
|
error = ANRError.New(_tick, _namePrefix, _logThreadsWithoutStackTrace);
|
} else {
|
System.out.println("ANRWatchDog.run 2");
|
error = ANRError.NewMainOnly(_tick);
|
}
|
_anrListener.onAppNotResponding(error);
|
interval = _timeoutInterval;
|
_reported = true;
|
}
|
}
|
}
|
|
public interface ANRListener {
|
/**
|
* Called when an ANR is detected.
|
*
|
* @param error The error describing the ANR.
|
*/
|
void onAppNotResponding(ANRError error);
|
}
|
|
public interface ANRInterceptor {
|
/**
|
* Called when main thread has froze more time than defined by the timeout.
|
*
|
* @param duration The minimum time (in ms) the main thread has been frozen (may be more).
|
* @return 0 or negative if the ANR should be reported immediately. A positive number of ms to postpone the reporting.
|
*/
|
long intercept(long duration);
|
}
|
|
public interface InterruptionListener {
|
void onInterrupted(InterruptedException exception);
|
}
|
}
|