1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.os.Build; 8 import android.os.Handler; 9 import android.os.HandlerThread; 10 import android.os.Looper; 11 12 import org.chromium.base.annotations.CalledByNative; 13 import org.chromium.base.annotations.JNINamespace; 14 import org.chromium.base.annotations.MainDex; 15 16 import java.lang.Thread.UncaughtExceptionHandler; 17 18 /** 19 * Thread in Java with an Android Handler. This class is not thread safe. 20 */ 21 @JNINamespace("base::android") 22 @MainDex 23 public class JavaHandlerThread { 24 private final HandlerThread mThread; 25 26 private Throwable mUnhandledException; 27 28 /** 29 * Construct a java-only instance. Can be connected with native side later. 30 * Useful for cases where a java thread is needed before native library is loaded. 31 */ JavaHandlerThread(String name, int priority)32 public JavaHandlerThread(String name, int priority) { 33 mThread = new HandlerThread(name, priority); 34 } 35 36 @CalledByNative create(String name, int priority)37 private static JavaHandlerThread create(String name, int priority) { 38 return new JavaHandlerThread(name, priority); 39 } 40 getLooper()41 public Looper getLooper() { 42 assert hasStarted(); 43 return mThread.getLooper(); 44 } 45 maybeStart()46 public void maybeStart() { 47 if (hasStarted()) return; 48 mThread.start(); 49 } 50 51 @CalledByNative startAndInitialize(final long nativeThread, final long nativeEvent)52 private void startAndInitialize(final long nativeThread, final long nativeEvent) { 53 maybeStart(); 54 new Handler(mThread.getLooper()).post(new Runnable() { 55 @Override 56 public void run() { 57 nativeInitializeThread(nativeThread, nativeEvent); 58 } 59 }); 60 } 61 62 @CalledByNative quitThreadSafely(final long nativeThread)63 private void quitThreadSafely(final long nativeThread) { 64 // Allow pending java tasks to run, but don't run any delayed or newly queued up tasks. 65 new Handler(mThread.getLooper()).post(new Runnable() { 66 @Override 67 public void run() { 68 mThread.quit(); 69 nativeOnLooperStopped(nativeThread); 70 } 71 }); 72 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 73 // When we can, signal that new tasks queued up won't be run. 74 mThread.getLooper().quitSafely(); 75 } 76 } 77 78 @CalledByNative joinThread()79 private void joinThread() { 80 boolean joined = false; 81 while (!joined) { 82 try { 83 mThread.join(); 84 joined = true; 85 } catch (InterruptedException e) { 86 } 87 } 88 } 89 hasStarted()90 private boolean hasStarted() { 91 return mThread.getState() != Thread.State.NEW; 92 } 93 94 @CalledByNative isAlive()95 private boolean isAlive() { 96 return mThread.isAlive(); 97 } 98 99 // This should *only* be used for tests. In production we always need to call the original 100 // uncaught exception handler (the framework's) after any uncaught exception handling we do, as 101 // it generates crash dumps and kills the process. 102 @CalledByNative listenForUncaughtExceptionsForTesting()103 private void listenForUncaughtExceptionsForTesting() { 104 mThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 105 @Override 106 public void uncaughtException(Thread t, Throwable e) { 107 mUnhandledException = e; 108 } 109 }); 110 } 111 112 @CalledByNative getUncaughtExceptionIfAny()113 private Throwable getUncaughtExceptionIfAny() { 114 return mUnhandledException; 115 } 116 nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent)117 private native void nativeInitializeThread(long nativeJavaHandlerThread, long nativeEvent); nativeOnLooperStopped(long nativeJavaHandlerThread)118 private native void nativeOnLooperStopped(long nativeJavaHandlerThread); 119 } 120