blob: 9a7431ee38e6e15e162071b73c5c909d6ba840b3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.MessageQueue;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import java.lang.Thread.UncaughtExceptionHandler;
/**
* Thread in Java with an Android Handler. This class is not thread safe.
*/
@JNINamespace("base::android")
public class JavaHandlerThread {
private final HandlerThread mThread;
private Throwable mUnhandledException;
/**
* Construct a java-only instance. Can be connected with native side later.
* Useful for cases where a java thread is needed before native library is loaded.
*/
public JavaHandlerThread(String name, int priority) {
mThread = new HandlerThread(name, priority);
}
@CalledByNative
private static JavaHandlerThread create(String name, int priority) {
return new JavaHandlerThread(name, priority);
}
public Looper getLooper() {
assert hasStarted();
return mThread.getLooper();
}
public void maybeStart() {
if (hasStarted()) return;
mThread.start();
}
@CalledByNative
private void startAndInitialize(final long nativeThread, final long nativeEvent) {
maybeStart();
new Handler(mThread.getLooper()).post(new Runnable() {
@Override
public void run() {
nativeInitializeThread(nativeThread, nativeEvent);
}
});
}
@CalledByNative
private void stopOnThread(final long nativeThread) {
nativeStopThread(nativeThread);
MessageQueue queue = Looper.myQueue();
// Add an idle handler so that the thread cleanup code can run after the message loop has
// detected an idle state and quit properly.
// This matches the behavior of base::Thread in that it will keep running non-delayed posted
// tasks indefinitely (until an idle state is reached). HandlerThread#quit() and
// HandlerThread#quitSafely() aren't sufficient because they prevent new tasks from being
// added to the queue, and don't allow us to wait for the Runloop to quit properly before
// stopping the thread.
queue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// The MessageQueue may not be empty, but only delayed tasks remain. To
// match the behavior of other platforms, we should quit now. Calling quit
// here is equivalent to calling quitSafely(), but doesn't require target
// API guards.
mThread.getLooper().quit();
nativeOnLooperStopped(nativeThread);
return false;
}
});
}
@CalledByNative
private void joinThread() {
boolean joined = false;
while (!joined) {
try {
mThread.join();
joined = true;
} catch (InterruptedException e) {
}
}
}
@CalledByNative
private void stop(final long nativeThread) {
assert hasStarted();
// Looper may be null if the thread crashed.
Looper looper = mThread.getLooper();
if (!isAlive() || looper == null) return;
new Handler(looper).post(new Runnable() {
@Override
public void run() {
stopOnThread(nativeThread);
}
});
joinThread();
}
private boolean hasStarted() {
return mThread.getState() != Thread.State.NEW;
}
@CalledByNative
private boolean isAlive() {
return mThread.isAlive();
}
// This should *only* be used for tests. In production we always need to call the original
// uncaught exception handler (the framework's) after any uncaught exception handling we do, as
// it generates crash dumps and kills the process.
@CalledByNative
private void listenForUncaughtExceptionsForTesting() {
mThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
mUnhandledException = e;
}
});
}
@CalledByNative
private Throwable getUncaughtExceptionIfAny() {
return mUnhandledException;
}
private native void nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent);
private native void nativeStopThread(long nativeJavaHandlerThread);
private native void nativeOnLooperStopped(long nativeJavaHandlerThread);
}