├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── android │ ├── app │ └── ActivityThread.java │ └── os │ ├── Handler.java │ ├── HandlerThread.java │ ├── Looper.java │ ├── Message.java │ ├── MessageQueue.java │ ├── internal │ ├── DelayQueue.java │ ├── Delayed.java │ └── PriorityQueue.java │ └── utils │ └── Logger.java └── test ├── java └── android │ └── os │ ├── BaseTestHandler.kt │ └── HandlerTest.kt └── resources └── logger └── debug /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .idea 26 | build 27 | *.iml 28 | out 29 | .gradle 30 | 31 | local.properties 32 | jcenter.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bennyhuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PortableAndroidHandler 2 | Pure Java implementation of Android Handler. This is helpful to test Android Handler based logic in a pure Java project. 3 | 4 | Use `DelayQueue` internal to implement the `MessageQuue`. 5 | 6 | The project is still working, and most of the Android Handler related apis will be completed later on. 7 | 8 | ## Usage 9 | 10 | It has been deployed to mavenCentral: 11 | 12 | ``` 13 | implementation("com.bennyhuo:portable-android-handler:1.0") 14 | ``` 15 | 16 | First you should setup your own looper thread like what Android does in `ActivityThread`: 17 | 18 | ``` java 19 | Looper.prepare(); 20 | // Create a new Thread to use your handler here. 21 | Looper.loop(); 22 | //unreachable unless you quit the looper. 23 | ``` 24 | 25 | ## Reference 26 | 27 | This library is mentioned in my Android Interview Tutorials: [破解Android高级面试](https://s.imooc.com/SBS30PR) 28 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } 4 | } 5 | dependencies { 6 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2' 7 | // For Kotlin projects, you need to add Dokka. 8 | classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.10.1' 9 | } 10 | } 11 | 12 | plugins { 13 | id 'java' 14 | id 'com.vanniktech.maven.publish' version("0.14.2") 15 | } 16 | 17 | group = GROUP 18 | version = VERSION_NAME 19 | 20 | sourceCompatibility = 1.8 21 | 22 | repositories { 23 | maven { url 'https://mirrors.tencent.com/nexus/repository/maven-public/' } 24 | } 25 | 26 | dependencies { 27 | testCompile group: 'junit', name: 'junit', version: '4.12' 28 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.bennyhuo 2 | VERSION_NAME=1.0 3 | POM_ARTIFACT_ID=portable-android-handler 4 | 5 | POM_NAME=PortableAndroidHandler 6 | 7 | POM_URL=https://github.com/bennyhuo/PortableAndroidHandler 8 | POM_DESCRIPTION=Pure Java implementation of Android Handler. 9 | 10 | POM_SCM_URL=https://github.com/bennyhuo/PortableAndroidHandler 11 | POM_SCM_CONNECTION=scm:git:git://github.com/bennyhuo/PortableAndroidHandler.git 12 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com:bennyhuo/PortableAndroidHandler.git 13 | 14 | POM_LICENCE_NAME=MIT License 15 | POM_LICENCE_URL=https://github.com/bennyhuo/PortableAndroidHandler/blob/master/LICENSE 16 | POM_LICENCE_DIST=repo 17 | 18 | POM_DEVELOPER_ID=bennyhuo 19 | POM_DEVELOPER_NAME=Benny Huo 20 | POM_DEVELOPER_URL=https://github.com/bennyhuo/ 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'portable-android-handler' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.os.Message; 6 | 7 | public class ActivityThread { 8 | 9 | private Handler handler = new Handler() { 10 | @Override 11 | public void handleMessage(Message msg) { 12 | System.out.println(msg); 13 | } 14 | }; 15 | 16 | private void attach(){ 17 | Thread otherThread = new Thread() { 18 | @Override 19 | public void run() { 20 | super.run(); 21 | 22 | for (int i = 0; i < 5; i++) { 23 | try { 24 | sleep(2000); 25 | } catch (InterruptedException e) { 26 | e.printStackTrace(); 27 | } 28 | for (int j = 0; j < 5; j++) { 29 | Message msg = new Message(); 30 | msg.what = i * 5 + j; 31 | msg.obj = "[" + i + ", " + j + "]"; 32 | handler.sendMessageDelayed(msg, (long) (Math.random() * 10000)); 33 | } 34 | } 35 | } 36 | }; 37 | otherThread.start(); 38 | } 39 | 40 | public static void main(String... args) { 41 | Looper.prepareMainLooper(); 42 | ActivityThread activityThread = new ActivityThread(); 43 | activityThread.attach(); 44 | Looper.loop(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/android/os/Handler.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class Handler { 4 | private MessageQueue mQueue; 5 | private Callback mCallback; 6 | 7 | public Handler() { 8 | this(Looper.myLooper()); 9 | } 10 | 11 | public Handler(Looper looper) { 12 | this(looper, null); 13 | } 14 | 15 | public Handler(Looper looper, Callback callback) { 16 | this.mQueue = looper.getQueue(); 17 | this.mCallback = callback; 18 | } 19 | 20 | public final void sendMessage(Message msg) { 21 | sendMessageDelayed(msg, 0L); 22 | } 23 | 24 | public final void sendMessageDelayed(Message msg, long delay) { 25 | msg.target = this; 26 | delay = Math.max(delay, 0L); 27 | msg.when = System.currentTimeMillis() + delay; 28 | mQueue.enqueueMessage(msg); 29 | } 30 | 31 | public final void post(Runnable r) { 32 | postDelayed(r, 0L); 33 | } 34 | 35 | public final void postDelayed(Runnable r, long delay) { 36 | Message msg = new Message(); 37 | msg.callback = r; 38 | sendMessageDelayed(msg, delay); 39 | } 40 | 41 | public void handleMessage(Message msg) { 42 | 43 | } 44 | 45 | private void handleCallback(Message msg) { 46 | msg.callback.run(); 47 | } 48 | 49 | public final void dispatchMessage(Message msg) { 50 | if (msg.callback != null) { 51 | handleCallback(msg); 52 | } else { 53 | if (mCallback != null) { 54 | if (mCallback.handleMessage(msg)) { 55 | return; 56 | } 57 | } 58 | handleMessage(msg); 59 | } 60 | } 61 | 62 | /** 63 | * Remove any pending posts of messages with code 'what' that are in the 64 | * message queue. 65 | */ 66 | public final void removeMessages(int what) { 67 | mQueue.removeMessages(this, what, null); 68 | } 69 | 70 | /** 71 | * Remove any pending posts of messages with code 'what' and whose obj is 72 | * 'object' that are in the message queue. If object is null, 73 | * all messages will be removed. 74 | */ 75 | public final void removeMessages(int what, Object object) { 76 | mQueue.removeMessages(this, what, object); 77 | } 78 | 79 | /** 80 | * Remove any pending posts of callbacks and sent messages whose 81 | * obj is token. If token is null, 82 | * all callbacks and messages will be removed. 83 | */ 84 | public final void removeCallbacksAndMessages(Object token) { 85 | mQueue.removeCallbacksAndMessages(this, token); 86 | } 87 | 88 | /** 89 | * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than 90 | * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this). 91 | * If you don't want that facility, just call Message.obtain() instead. 92 | */ 93 | public final Message obtainMessage() 94 | { 95 | return Message.obtain(this); 96 | } 97 | 98 | /** 99 | * Same as {@link #obtainMessage()}, except that it also sets the what member of the returned Message. 100 | * 101 | * @param what Value to assign to the returned Message.what field. 102 | * @return A Message from the global message pool. 103 | */ 104 | public final Message obtainMessage(int what) 105 | { 106 | return Message.obtain(this, what); 107 | } 108 | 109 | /** 110 | * 111 | * Same as {@link #obtainMessage()}, except that it also sets the what and obj members 112 | * of the returned Message. 113 | * 114 | * @param what Value to assign to the returned Message.what field. 115 | * @param obj Value to assign to the returned Message.obj field. 116 | * @return A Message from the global message pool. 117 | */ 118 | public final Message obtainMessage(int what, Object obj) 119 | { 120 | return Message.obtain(this, what, obj); 121 | } 122 | 123 | /** 124 | * 125 | * Same as {@link #obtainMessage()}, except that it also sets the what, arg1 and arg2 members of the returned 126 | * Message. 127 | * @param what Value to assign to the returned Message.what field. 128 | * @param arg1 Value to assign to the returned Message.arg1 field. 129 | * @param arg2 Value to assign to the returned Message.arg2 field. 130 | * @return A Message from the global message pool. 131 | */ 132 | public final Message obtainMessage(int what, int arg1, int arg2) 133 | { 134 | return Message.obtain(this, what, arg1, arg2); 135 | } 136 | 137 | /** 138 | * 139 | * Same as {@link #obtainMessage()}, except that it also sets the what, obj, arg1,and arg2 values on the 140 | * returned Message. 141 | * @param what Value to assign to the returned Message.what field. 142 | * @param arg1 Value to assign to the returned Message.arg1 field. 143 | * @param arg2 Value to assign to the returned Message.arg2 field. 144 | * @param obj Value to assign to the returned Message.obj field. 145 | * @return A Message from the global message pool. 146 | */ 147 | public final Message obtainMessage(int what, int arg1, int arg2, Object obj) 148 | { 149 | return Message.obtain(this, what, arg1, arg2, obj); 150 | } 151 | 152 | /** 153 | * Callback interface you can use when instantiating a Handler to avoid 154 | * having to implement your own subclass of Handler. 155 | */ 156 | public interface Callback { 157 | /** 158 | * @param msg A {@link android.os.Message Message} object 159 | * @return True if no further handling is desired 160 | */ 161 | public boolean handleMessage(Message msg); 162 | } 163 | } -------------------------------------------------------------------------------- /src/main/java/android/os/HandlerThread.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class HandlerThread extends Thread { 4 | Looper mLooper; 5 | private Handler mHandler; 6 | 7 | public HandlerThread(String name) { super(name); } 8 | protected void onLooperPrepared() { } 9 | 10 | @Override 11 | public void run() { 12 | Looper.prepare(); 13 | synchronized (this) { 14 | mLooper = Looper.myLooper(); 15 | notifyAll(); 16 | } 17 | onLooperPrepared(); 18 | Looper.loop(); 19 | } 20 | 21 | public Looper getLooper() { 22 | if (!isAlive()) return null; 23 | synchronized (this) { 24 | while (isAlive() && mLooper == null) 25 | try { wait(); } catch (InterruptedException e) { } 26 | } 27 | return mLooper; 28 | } 29 | 30 | public Handler getThreadHandler() { 31 | if (mHandler == null) mHandler = new Handler(getLooper()); 32 | return mHandler; 33 | } 34 | 35 | public boolean quit() { 36 | Looper looper = getLooper(); 37 | if (looper != null) { 38 | looper.quit(); 39 | return true; 40 | } 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/android/os/Looper.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public final class Looper { 4 | 5 | private static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); 6 | 7 | private static Looper sMainLooper; 8 | 9 | private MessageQueue messageQueue; 10 | 11 | public static void prepare() { 12 | if (THREAD_LOCAL.get() != null) { 13 | throw new RuntimeException("Only one Looper may be created per thread"); 14 | } 15 | THREAD_LOCAL.set(new Looper()); 16 | } 17 | 18 | public static void prepareMainLooper() { 19 | prepare(); 20 | synchronized (Looper.class) { 21 | if (sMainLooper != null) { 22 | throw new IllegalStateException("The main Looper has already been prepared."); 23 | } 24 | sMainLooper = myLooper(); 25 | } 26 | } 27 | 28 | public static Looper getMainLooper() { 29 | synchronized (Looper.class) { 30 | return sMainLooper; 31 | } 32 | } 33 | 34 | public static Looper myLooper() { 35 | return THREAD_LOCAL.get(); 36 | } 37 | 38 | private Looper() { 39 | messageQueue = new MessageQueue(); 40 | } 41 | 42 | public MessageQueue getQueue() { 43 | return messageQueue; 44 | } 45 | 46 | public static void loop() { 47 | Looper me = myLooper(); 48 | if (me == null) { 49 | throw new RuntimeException("No looper"); 50 | } 51 | MessageQueue queue = me.messageQueue; 52 | while(true) { 53 | Message msg = queue.next(); 54 | if (msg == null) { 55 | return; 56 | } 57 | msg.target.dispatchMessage(msg); 58 | msg.recycleUnchecked(); 59 | } 60 | } 61 | 62 | public void quit(){ 63 | messageQueue.quit(false); 64 | } 65 | 66 | public void quitSafely() { 67 | messageQueue.quit(true); 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/android/os/Message.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import android.os.internal.Delayed; 4 | 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.LinkedList; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public final class Message implements Delayed { 12 | 13 | private static final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); 14 | 15 | private static final Object sPoolSync = new Object(); 16 | 17 | /*package*/ static final int FLAG_IN_USE = 1 << 0; 18 | 19 | /** Flags to clear in the copyFrom method */ 20 | /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; 21 | 22 | /*package*/ int flags; 23 | 24 | private static LinkedList sPool = new LinkedList<>(); 25 | private static final int MAX_POOL_SIZE = 50; 26 | 27 | Handler target; 28 | public int what; 29 | public int arg1; 30 | public int arg2; 31 | public Object obj; 32 | 33 | long when; 34 | 35 | /** 36 | * for TEST 37 | */ 38 | String who; 39 | 40 | Runnable callback; 41 | 42 | /** 43 | * Return the targeted delivery time of this message, in milliseconds. 44 | */ 45 | public long getWhen() { 46 | return when; 47 | } 48 | 49 | public void setTarget(Handler target) { 50 | this.target = target; 51 | } 52 | 53 | /** 54 | * Retrieve the a {@link android.os.Handler Handler} implementation that 55 | * will receive this message. The object must implement 56 | * {@link android.os.Handler#handleMessage(android.os.Message) 57 | * Handler.handleMessage()}. Each Handler has its own name-space for 58 | * message codes, so you do not need to 59 | * worry about yours conflicting with other handlers. 60 | */ 61 | public Handler getTarget() { 62 | return target; 63 | } 64 | 65 | /** 66 | * Retrieve callback object that will execute when this message is handled. 67 | * This object must implement Runnable. This is called by 68 | * the target {@link Handler} that is receiving this Message to 69 | * dispatch it. If 70 | * not set, the message will be dispatched to the receiving Handler's 71 | * {@link Handler#handleMessage(Message)}. 72 | */ 73 | public Runnable getCallback() { 74 | return callback; 75 | } 76 | 77 | /** 78 | * Sends this Message to the Handler specified by {@link #getTarget}. 79 | * Throws a null pointer exception if this field has not been set. 80 | */ 81 | public void sendToTarget() { 82 | target.sendMessage(this); 83 | } 84 | 85 | /*package*/ void markInUse() { 86 | flags |= FLAG_IN_USE; 87 | } 88 | 89 | /*package*/ boolean isInUse() { 90 | return ((flags & FLAG_IN_USE) == FLAG_IN_USE); 91 | } 92 | 93 | /** 94 | * Make this message like o. Performs a shallow copy of the data field. 95 | * Does not copy the linked list fields, nor the timestamp or 96 | * target/callback of the original message. 97 | */ 98 | public void copyFrom(Message o) { 99 | this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM; 100 | this.what = o.what; 101 | this.arg1 = o.arg1; 102 | this.arg2 = o.arg2; 103 | this.obj = o.obj; 104 | } 105 | 106 | /** 107 | * Return a Message instance to the global pool. 108 | *

109 | * You MUST NOT touch the Message after calling this function because it has 110 | * effectively been freed. It is an error to recycle a message that is currently 111 | * enqueued or that is in the process of being delivered to a Handler. 112 | *

113 | */ 114 | public void recycle() { 115 | if (isInUse()) { 116 | throw new IllegalStateException("This message cannot be recycled because it " 117 | + "is still in use."); 118 | } 119 | recycleUnchecked(); 120 | } 121 | 122 | /** 123 | * Recycles a Message that may be in-use. 124 | * Used internally by the MessageQueue and Looper when disposing of queued Messages. 125 | */ 126 | void recycleUnchecked() { 127 | // Mark the message as in use while it remains in the recycled object pool. 128 | // Clear out all other details. 129 | flags = FLAG_IN_USE; 130 | what = 0; 131 | arg1 = 0; 132 | arg2 = 0; 133 | obj = null; 134 | when = 0; 135 | target = null; 136 | callback = null; 137 | 138 | synchronized (sPoolSync) { 139 | if (sPool.size() < MAX_POOL_SIZE) { 140 | sPool.addLast(this); 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Return a new Message instance from the global pool. Allows us to 147 | * avoid allocating new objects in many cases. 148 | */ 149 | public static Message obtain() { 150 | synchronized (sPoolSync) { 151 | if (!sPool.isEmpty()) { 152 | Message m = sPool.removeFirst(); 153 | m.flags = 0; // clear in-use flag 154 | return m; 155 | } 156 | } 157 | return new Message(); 158 | } 159 | 160 | /** 161 | * Same as {@link #obtain()}, but copies the values of an existing 162 | * message (including its target) into the new one. 163 | * @param orig Original message to copy. 164 | * @return A Message object from the global pool. 165 | */ 166 | public static Message obtain(Message orig) { 167 | Message m = obtain(); 168 | m.what = orig.what; 169 | m.arg1 = orig.arg1; 170 | m.arg2 = orig.arg2; 171 | m.obj = orig.obj; 172 | m.target = orig.target; 173 | m.callback = orig.callback; 174 | 175 | return m; 176 | } 177 | 178 | /** 179 | * Same as {@link #obtain()}, but sets the value for the target member on the Message returned. 180 | * @param h Handler to assign to the returned Message object's target member. 181 | * @return A Message object from the global pool. 182 | */ 183 | public static Message obtain(Handler h) { 184 | Message m = obtain(); 185 | m.target = h; 186 | 187 | return m; 188 | } 189 | 190 | /** 191 | * Same as {@link #obtain(Handler)}, but assigns a callback Runnable on 192 | * the Message that is returned. 193 | * @param h Handler to assign to the returned Message object's target member. 194 | * @param callback Runnable that will execute when the message is handled. 195 | * @return A Message object from the global pool. 196 | */ 197 | public static Message obtain(Handler h, Runnable callback) { 198 | Message m = obtain(); 199 | m.target = h; 200 | m.callback = callback; 201 | 202 | return m; 203 | } 204 | 205 | /** 206 | * Same as {@link #obtain()}, but sets the values for both target and 207 | * what members on the Message. 208 | * @param h Value to assign to the target member. 209 | * @param what Value to assign to the what member. 210 | * @return A Message object from the global pool. 211 | */ 212 | public static Message obtain(Handler h, int what) { 213 | Message m = obtain(); 214 | m.target = h; 215 | m.what = what; 216 | 217 | return m; 218 | } 219 | 220 | /** 221 | * Same as {@link #obtain()}, but sets the values of the target, what, and obj 222 | * members. 223 | * @param h The target value to set. 224 | * @param what The what value to set. 225 | * @param obj The object method to set. 226 | * @return A Message object from the global pool. 227 | */ 228 | public static Message obtain(Handler h, int what, Object obj) { 229 | Message m = obtain(); 230 | m.target = h; 231 | m.what = what; 232 | m.obj = obj; 233 | 234 | return m; 235 | } 236 | 237 | /** 238 | * Same as {@link #obtain()}, but sets the values of the target, what, 239 | * arg1, and arg2 members. 240 | * 241 | * @param h The target value to set. 242 | * @param what The what value to set. 243 | * @param arg1 The arg1 value to set. 244 | * @param arg2 The arg2 value to set. 245 | * @return A Message object from the global pool. 246 | */ 247 | public static Message obtain(Handler h, int what, int arg1, int arg2) { 248 | Message m = obtain(); 249 | m.target = h; 250 | m.what = what; 251 | m.arg1 = arg1; 252 | m.arg2 = arg2; 253 | 254 | return m; 255 | } 256 | 257 | /** 258 | * Same as {@link #obtain()}, but sets the values of the target, what, 259 | * arg1, arg2, and obj members. 260 | * 261 | * @param h The target value to set. 262 | * @param what The what value to set. 263 | * @param arg1 The arg1 value to set. 264 | * @param arg2 The arg2 value to set. 265 | * @param obj The obj value to set. 266 | * @return A Message object from the global pool. 267 | */ 268 | public static Message obtain(Handler h, int what, 269 | int arg1, int arg2, Object obj) { 270 | Message m = obtain(); 271 | m.target = h; 272 | m.what = what; 273 | m.arg1 = arg1; 274 | m.arg2 = arg2; 275 | m.obj = obj; 276 | 277 | return m; 278 | } 279 | 280 | @Override 281 | public long getDelay(TimeUnit unit) { 282 | return unit.convert(when - System.currentTimeMillis(), TimeUnit.MILLISECONDS); 283 | } 284 | 285 | @Override 286 | public int compareTo(Delayed o) { 287 | if (o instanceof Message) { 288 | return (int) (when - ((Message) o).when); 289 | } else { 290 | return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); 291 | } 292 | } 293 | 294 | @Override 295 | public String toString() { 296 | return "Message[" + who + "]{" + 297 | "what=" + what + 298 | ", obj=" + obj + 299 | ", when=" + dateFormat.format(new Date(when)) + 300 | '}'; 301 | } 302 | } -------------------------------------------------------------------------------- /src/main/java/android/os/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import android.os.internal.DelayQueue; 4 | import android.os.utils.Logger; 5 | 6 | import java.util.function.Predicate; 7 | 8 | public class MessageQueue { 9 | private static final Message POISON = new Message(); 10 | 11 | private volatile boolean isQuited = false; 12 | 13 | private DelayQueue queue = new DelayQueue<>(); 14 | 15 | public void enqueueMessage(Message msg) { 16 | if(isQuited){ 17 | msg.recycle(); 18 | return; 19 | } 20 | queue.add(msg); 21 | msg.markInUse(); 22 | } 23 | 24 | public Message next() { 25 | try { 26 | Message next = queue.take(); 27 | if (next == POISON) { 28 | return null; 29 | } 30 | return next; 31 | } catch (InterruptedException e) { 32 | e.printStackTrace(); 33 | } 34 | return null; 35 | } 36 | 37 | void removeMessages(Handler h, int what, Object object) { 38 | if (h == null) { 39 | return; 40 | } 41 | queue.removeIf(new Predicate() { 42 | @Override 43 | public boolean test(Message message) { 44 | boolean shouldRemove = message.target == h && message.what == what && (object == null || message.obj == object); 45 | if(shouldRemove){ 46 | message.recycleUnchecked(); 47 | } 48 | return shouldRemove; 49 | } 50 | }); 51 | } 52 | 53 | void removeMessages(Handler h, Runnable r, Object object) { 54 | if (h == null || r == null) { 55 | return; 56 | } 57 | queue.removeIf(new Predicate() { 58 | @Override 59 | public boolean test(Message message) { 60 | boolean shouldRemove = message.target == h && message.callback == r && (object == null || message.obj == object); 61 | if(shouldRemove){ 62 | message.recycleUnchecked(); 63 | } 64 | return shouldRemove; 65 | } 66 | }); 67 | } 68 | 69 | void removeCallbacksAndMessages(Handler h, Object object) { 70 | if (h == null) { 71 | return; 72 | } 73 | 74 | queue.removeIf(new Predicate() { 75 | @Override 76 | public boolean test(Message message) { 77 | boolean shouldRemove = message.target == h && (object == null || message.obj == object); 78 | if(shouldRemove){ 79 | message.recycleUnchecked(); 80 | } 81 | return shouldRemove; 82 | } 83 | }); 84 | } 85 | 86 | void quit(boolean safe) { 87 | if (isQuited) return; 88 | isQuited = true; 89 | Logger.debug("Quit, messages in queue: " + queue.size()); 90 | if (!safe) { 91 | queue.clear(); 92 | } 93 | // Tell looper to quit. 94 | POISON.when = System.currentTimeMillis(); 95 | POISON.who = "KILLER"; 96 | Logger.debug("Feed poison: %s", POISON); 97 | queue.offer(POISON); 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/android/os/internal/DelayQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 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 | * Written by Doug Lea with assistance from members of JCP JSR-166 32 | * Expert Group and released to the public domain, as explained at 33 | * http://creativecommons.org/publicdomain/zero/1.0/ 34 | */ 35 | 36 | package android.os.internal; 37 | 38 | import java.util.AbstractQueue; 39 | import java.util.Collection; 40 | import java.util.Iterator; 41 | import java.util.NoSuchElementException; 42 | import java.util.Objects; 43 | import java.util.concurrent.BlockingQueue; 44 | import java.util.concurrent.TimeUnit; 45 | import java.util.concurrent.locks.Condition; 46 | import java.util.concurrent.locks.ReentrantLock; 47 | import java.util.function.Predicate; 48 | 49 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 50 | 51 | /** 52 | * An unbounded {@linkplain BlockingQueue blocking queue} of 53 | * {@code Delayed} elements, in which an element can only be taken 54 | * when its delay has expired. The head of the queue is that 55 | * {@code Delayed} element whose delay expired furthest in the 56 | * past. If no delay has expired there is no head and {@code poll} 57 | * will return {@code null}. Expiration occurs when an element's 58 | * {@code getDelay(TimeUnit.NANOSECONDS)} method returns a value less 59 | * than or equal to zero. Even though unexpired elements cannot be 60 | * removed using {@code take} or {@code poll}, they are otherwise 61 | * treated as normal elements. For example, the {@code size} method 62 | * returns the count of both expired and unexpired elements. 63 | * This queue does not permit null elements. 64 | * 65 | *

This class and its iterator implement all of the 66 | * optional methods of the {@link Collection} and {@link 67 | * Iterator} interfaces. The Iterator provided in method {@link 68 | * #iterator()} is not guaranteed to traverse the elements of 69 | * the DelayQueue in any particular order. 70 | * 71 | *

This class is a member of the 72 | * 73 | * Java Collections Framework. 74 | * 75 | * @since 1.5 76 | * @author Doug Lea 77 | * @param the type of elements held in this collection 78 | */ 79 | public class DelayQueue extends AbstractQueue 80 | implements BlockingQueue { 81 | 82 | private final transient ReentrantLock lock = new ReentrantLock(); 83 | private final PriorityQueue q = new PriorityQueue(); 84 | 85 | /** 86 | * Thread designated to wait for the element at the head of 87 | * the queue. This variant of the Leader-Follower pattern 88 | * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to 89 | * minimize unnecessary timed waiting. When a thread becomes 90 | * the leader, it waits only for the next delay to elapse, but 91 | * other threads await indefinitely. The leader thread must 92 | * signal some other thread before returning from take() or 93 | * poll(...), unless some other thread becomes leader in the 94 | * interim. Whenever the head of the queue is replaced with 95 | * an element with an earlier expiration time, the leader 96 | * field is invalidated by being reset to null, and some 97 | * waiting thread, but not necessarily the current leader, is 98 | * signalled. So waiting threads must be prepared to acquire 99 | * and lose leadership while waiting. 100 | */ 101 | private Thread leader = null; 102 | 103 | /** 104 | * Condition signalled when a newer element becomes available 105 | * at the head of the queue or a new thread may need to 106 | * become leader. 107 | */ 108 | private final Condition available = lock.newCondition(); 109 | 110 | /** 111 | * Creates a new {@code DelayQueue} that is initially empty. 112 | */ 113 | public DelayQueue() {} 114 | 115 | /** 116 | * Creates a {@code DelayQueue} initially containing the elements of the 117 | * given collection of {@link Delayed} instances. 118 | * 119 | * @param c the collection of elements to initially contain 120 | * @throws NullPointerException if the specified collection or any 121 | * of its elements are null 122 | */ 123 | public DelayQueue(Collection c) { 124 | this.addAll(c); 125 | } 126 | 127 | /** 128 | * Inserts the specified element into this delay queue. 129 | * 130 | * @param e the element to add 131 | * @return {@code true} (as specified by {@link Collection#add}) 132 | * @throws NullPointerException if the specified element is null 133 | */ 134 | public boolean add(E e) { 135 | return offer(e); 136 | } 137 | 138 | /** 139 | * Inserts the specified element into this delay queue. 140 | * 141 | * @param e the element to add 142 | * @return {@code true} 143 | * @throws NullPointerException if the specified element is null 144 | */ 145 | public boolean offer(E e) { 146 | final ReentrantLock lock = this.lock; 147 | lock.lock(); 148 | try { 149 | q.offer(e); 150 | if (q.peek() == e) { 151 | leader = null; 152 | available.signal(); 153 | } 154 | return true; 155 | } finally { 156 | lock.unlock(); 157 | } 158 | } 159 | 160 | /** 161 | * Inserts the specified element into this delay queue. As the queue is 162 | * unbounded this method will never block. 163 | * 164 | * @param e the element to add 165 | * @throws NullPointerException {@inheritDoc} 166 | */ 167 | public void put(E e) { 168 | offer(e); 169 | } 170 | 171 | /** 172 | * Inserts the specified element into this delay queue. As the queue is 173 | * unbounded this method will never block. 174 | * 175 | * @param e the element to add 176 | * @param timeout This parameter is ignored as the method never blocks 177 | * @param unit This parameter is ignored as the method never blocks 178 | * @return {@code true} 179 | * @throws NullPointerException {@inheritDoc} 180 | */ 181 | public boolean offer(E e, long timeout, TimeUnit unit) { 182 | return offer(e); 183 | } 184 | 185 | /** 186 | * Retrieves and removes the head of this queue, or returns {@code null} 187 | * if this queue has no elements with an expired delay. 188 | * 189 | * @return the head of this queue, or {@code null} if this 190 | * queue has no elements with an expired delay 191 | */ 192 | public E poll() { 193 | final ReentrantLock lock = this.lock; 194 | lock.lock(); 195 | try { 196 | E first = q.peek(); 197 | if (first == null || first.getDelay(NANOSECONDS) > 0) 198 | return null; 199 | else 200 | return q.poll(); 201 | } finally { 202 | lock.unlock(); 203 | } 204 | } 205 | 206 | /** 207 | * Retrieves and removes the head of this queue, waiting if necessary 208 | * until an element with an expired delay is available on this queue. 209 | * 210 | * @return the head of this queue 211 | * @throws InterruptedException {@inheritDoc} 212 | */ 213 | public E take() throws InterruptedException { 214 | final ReentrantLock lock = this.lock; 215 | lock.lockInterruptibly(); 216 | try { 217 | for (;;) { 218 | E first = q.peek(); 219 | if (first == null) 220 | available.await(); 221 | else { 222 | long delay = first.getDelay(NANOSECONDS); 223 | if (delay <= 0) 224 | return q.poll(); 225 | first = null; // don't retain ref while waiting 226 | if (leader != null) 227 | available.await(); 228 | else { 229 | Thread thisThread = Thread.currentThread(); 230 | leader = thisThread; 231 | try { 232 | available.awaitNanos(delay); 233 | } finally { 234 | if (leader == thisThread) 235 | leader = null; 236 | } 237 | } 238 | } 239 | } 240 | } finally { 241 | if (leader == null && q.peek() != null) 242 | available.signal(); 243 | lock.unlock(); 244 | } 245 | } 246 | 247 | /** 248 | * Retrieves and removes the head of this queue, waiting if necessary 249 | * until an element with an expired delay is available on this queue, 250 | * or the specified wait time expires. 251 | * 252 | * @return the head of this queue, or {@code null} if the 253 | * specified waiting time elapses before an element with 254 | * an expired delay becomes available 255 | * @throws InterruptedException {@inheritDoc} 256 | */ 257 | public E poll(long timeout, TimeUnit unit) throws InterruptedException { 258 | long nanos = unit.toNanos(timeout); 259 | final ReentrantLock lock = this.lock; 260 | lock.lockInterruptibly(); 261 | try { 262 | for (;;) { 263 | E first = q.peek(); 264 | if (first == null) { 265 | if (nanos <= 0) 266 | return null; 267 | else 268 | nanos = available.awaitNanos(nanos); 269 | } else { 270 | long delay = first.getDelay(NANOSECONDS); 271 | if (delay <= 0) 272 | return q.poll(); 273 | if (nanos <= 0) 274 | return null; 275 | first = null; // don't retain ref while waiting 276 | if (nanos < delay || leader != null) 277 | nanos = available.awaitNanos(nanos); 278 | else { 279 | Thread thisThread = Thread.currentThread(); 280 | leader = thisThread; 281 | try { 282 | long timeLeft = available.awaitNanos(delay); 283 | nanos -= delay - timeLeft; 284 | } finally { 285 | if (leader == thisThread) 286 | leader = null; 287 | } 288 | } 289 | } 290 | } 291 | } finally { 292 | if (leader == null && q.peek() != null) 293 | available.signal(); 294 | lock.unlock(); 295 | } 296 | } 297 | 298 | /** 299 | * Retrieves, but does not remove, the head of this queue, or 300 | * returns {@code null} if this queue is empty. Unlike 301 | * {@code poll}, if no expired elements are available in the queue, 302 | * this method returns the element that will expire next, 303 | * if one exists. 304 | * 305 | * @return the head of this queue, or {@code null} if this 306 | * queue is empty 307 | */ 308 | public E peek() { 309 | final ReentrantLock lock = this.lock; 310 | lock.lock(); 311 | try { 312 | return q.peek(); 313 | } finally { 314 | lock.unlock(); 315 | } 316 | } 317 | 318 | public int size() { 319 | final ReentrantLock lock = this.lock; 320 | lock.lock(); 321 | try { 322 | return q.size(); 323 | } finally { 324 | lock.unlock(); 325 | } 326 | } 327 | 328 | /** 329 | * Returns first element only if it is expired. 330 | * Used only by drainTo. Call only when holding lock. 331 | */ 332 | private E peekExpired() { 333 | // assert lock.isHeldByCurrentThread(); 334 | E first = q.peek(); 335 | return (first == null || first.getDelay(NANOSECONDS) > 0) ? 336 | null : first; 337 | } 338 | 339 | /** 340 | * @throws UnsupportedOperationException {@inheritDoc} 341 | * @throws ClassCastException {@inheritDoc} 342 | * @throws NullPointerException {@inheritDoc} 343 | * @throws IllegalArgumentException {@inheritDoc} 344 | */ 345 | public int drainTo(Collection c) { 346 | if (c == null) 347 | throw new NullPointerException(); 348 | if (c == this) 349 | throw new IllegalArgumentException(); 350 | final ReentrantLock lock = this.lock; 351 | lock.lock(); 352 | try { 353 | int n = 0; 354 | for (E e; (e = peekExpired()) != null;) { 355 | c.add(e); // In this order, in case add() throws. 356 | q.poll(); 357 | ++n; 358 | } 359 | return n; 360 | } finally { 361 | lock.unlock(); 362 | } 363 | } 364 | 365 | /** 366 | * @throws UnsupportedOperationException {@inheritDoc} 367 | * @throws ClassCastException {@inheritDoc} 368 | * @throws NullPointerException {@inheritDoc} 369 | * @throws IllegalArgumentException {@inheritDoc} 370 | */ 371 | public int drainTo(Collection c, int maxElements) { 372 | if (c == null) 373 | throw new NullPointerException(); 374 | if (c == this) 375 | throw new IllegalArgumentException(); 376 | if (maxElements <= 0) 377 | return 0; 378 | final ReentrantLock lock = this.lock; 379 | lock.lock(); 380 | try { 381 | int n = 0; 382 | for (E e; n < maxElements && (e = peekExpired()) != null;) { 383 | c.add(e); // In this order, in case add() throws. 384 | q.poll(); 385 | ++n; 386 | } 387 | return n; 388 | } finally { 389 | lock.unlock(); 390 | } 391 | } 392 | 393 | /** 394 | * Atomically removes all of the elements from this delay queue. 395 | * The queue will be empty after this call returns. 396 | * Elements with an unexpired delay are not waited for; they are 397 | * simply discarded from the queue. 398 | */ 399 | public void clear() { 400 | final ReentrantLock lock = this.lock; 401 | lock.lock(); 402 | try { 403 | q.clear(); 404 | } finally { 405 | lock.unlock(); 406 | } 407 | } 408 | 409 | /** 410 | * Always returns {@code Integer.MAX_VALUE} because 411 | * a {@code DelayQueue} is not capacity constrained. 412 | * 413 | * @return {@code Integer.MAX_VALUE} 414 | */ 415 | public int remainingCapacity() { 416 | return Integer.MAX_VALUE; 417 | } 418 | 419 | /** 420 | * Returns an array containing all of the elements in this queue. 421 | * The returned array elements are in no particular order. 422 | * 423 | *

The returned array will be "safe" in that no references to it are 424 | * maintained by this queue. (In other words, this method must allocate 425 | * a new array). The caller is thus free to modify the returned array. 426 | * 427 | *

This method acts as bridge between array-based and collection-based 428 | * APIs. 429 | * 430 | * @return an array containing all of the elements in this queue 431 | */ 432 | public Object[] toArray() { 433 | final ReentrantLock lock = this.lock; 434 | lock.lock(); 435 | try { 436 | return q.toArray(); 437 | } finally { 438 | lock.unlock(); 439 | } 440 | } 441 | 442 | /** 443 | * Returns an array containing all of the elements in this queue; the 444 | * runtime type of the returned array is that of the specified array. 445 | * The returned array elements are in no particular order. 446 | * If the queue fits in the specified array, it is returned therein. 447 | * Otherwise, a new array is allocated with the runtime type of the 448 | * specified array and the size of this queue. 449 | * 450 | *

If this queue fits in the specified array with room to spare 451 | * (i.e., the array has more elements than this queue), the element in 452 | * the array immediately following the end of the queue is set to 453 | * {@code null}. 454 | * 455 | *

Like the {@link #toArray()} method, this method acts as bridge between 456 | * array-based and collection-based APIs. Further, this method allows 457 | * precise control over the runtime type of the output array, and may, 458 | * under certain circumstances, be used to save allocation costs. 459 | * 460 | *

The following code can be used to dump a delay queue into a newly 461 | * allocated array of {@code Delayed}: 462 | * 463 | *

 {@code Delayed[] a = q.toArray(new Delayed[0]);}
464 | * 465 | * Note that {@code toArray(new Object[0])} is identical in function to 466 | * {@code toArray()}. 467 | * 468 | * @param a the array into which the elements of the queue are to 469 | * be stored, if it is big enough; otherwise, a new array of the 470 | * same runtime type is allocated for this purpose 471 | * @return an array containing all of the elements in this queue 472 | * @throws ArrayStoreException if the runtime type of the specified array 473 | * is not a supertype of the runtime type of every element in 474 | * this queue 475 | * @throws NullPointerException if the specified array is null 476 | */ 477 | public T[] toArray(T[] a) { 478 | final ReentrantLock lock = this.lock; 479 | lock.lock(); 480 | try { 481 | return q.toArray(a); 482 | } finally { 483 | lock.unlock(); 484 | } 485 | } 486 | 487 | /** 488 | * Removes a single instance of the specified element from this 489 | * queue, if it is present, whether or not it has expired. 490 | */ 491 | public boolean remove(Object o) { 492 | final ReentrantLock lock = this.lock; 493 | lock.lock(); 494 | try { 495 | return q.remove(o); 496 | } finally { 497 | lock.unlock(); 498 | } 499 | } 500 | 501 | @Override 502 | public boolean removeIf(Predicate filter) { 503 | Objects.requireNonNull(filter); 504 | final ReentrantLock lock = this.lock; 505 | lock.lock(); 506 | try { 507 | boolean removed = false; 508 | final Iterator each = q.iterator(); 509 | while (each.hasNext()) { 510 | if (filter.test(each.next())) { 511 | each.remove(); 512 | removed = true; 513 | } 514 | } 515 | return removed; 516 | } finally { 517 | lock.unlock(); 518 | } 519 | } 520 | 521 | /** 522 | * Identity-based version for use in Itr.remove 523 | */ 524 | void removeEQ(Object o) { 525 | final ReentrantLock lock = this.lock; 526 | lock.lock(); 527 | try { 528 | for (Iterator it = q.iterator(); it.hasNext(); ) { 529 | if (o == it.next()) { 530 | it.remove(); 531 | break; 532 | } 533 | } 534 | } finally { 535 | lock.unlock(); 536 | } 537 | } 538 | 539 | /** 540 | * Returns an iterator over all the elements (both expired and 541 | * unexpired) in this queue. The iterator does not return the 542 | * elements in any particular order. 543 | * 544 | *

The returned iterator is 545 | * weakly consistent. 546 | * 547 | * @return an iterator over the elements in this queue 548 | */ 549 | public Iterator iterator() { 550 | return new Itr(toArray()); 551 | } 552 | 553 | /** 554 | * Snapshot iterator that works off copy of underlying q array. 555 | */ 556 | private class Itr implements Iterator { 557 | final Object[] array; // Array of all elements 558 | int cursor; // index of next element to return 559 | int lastRet; // index of last element, or -1 if no such 560 | 561 | Itr(Object[] array) { 562 | lastRet = -1; 563 | this.array = array; 564 | } 565 | 566 | public boolean hasNext() { 567 | return cursor < array.length; 568 | } 569 | 570 | @SuppressWarnings("unchecked") 571 | public E next() { 572 | if (cursor >= array.length) 573 | throw new NoSuchElementException(); 574 | lastRet = cursor; 575 | return (E)array[cursor++]; 576 | } 577 | 578 | public void remove() { 579 | if (lastRet < 0) 580 | throw new IllegalStateException(); 581 | removeEQ(array[lastRet]); 582 | lastRet = -1; 583 | } 584 | } 585 | 586 | } 587 | -------------------------------------------------------------------------------- /src/main/java/android/os/internal/Delayed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 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 | * Written by Doug Lea with assistance from members of JCP JSR-166 32 | * Expert Group and released to the public domain, as explained at 33 | * http://creativecommons.org/publicdomain/zero/1.0/ 34 | */ 35 | 36 | package android.os.internal; 37 | 38 | import java.util.concurrent.TimeUnit; 39 | 40 | /** 41 | * A mix-in style interface for marking objects that should be 42 | * acted upon after a given delay. 43 | * 44 | *

An implementation of this interface must define a 45 | * {@code compareTo} method that provides an ordering consistent with 46 | * its {@code getDelay} method. 47 | * 48 | * @since 1.5 49 | * @author Doug Lea 50 | */ 51 | public interface Delayed extends Comparable { 52 | 53 | /** 54 | * Returns the remaining delay associated with this object, in the 55 | * given time unit. 56 | * 57 | * @param unit the time unit 58 | * @return the remaining delay; zero or negative values indicate 59 | * that the delay has already elapsed 60 | */ 61 | long getDelay(TimeUnit unit); 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/android/os/internal/PriorityQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 3 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 4 | * 5 | * 6 | * 7 | * 8 | * 9 | * 10 | * 11 | * 12 | * 13 | * 14 | * 15 | * 16 | * 17 | * 18 | * 19 | * 20 | * 21 | * 22 | * 23 | * 24 | */ 25 | 26 | package android.os.internal; 27 | 28 | import java.util.AbstractQueue; 29 | import java.util.ArrayDeque; 30 | import java.util.Arrays; 31 | import java.util.Collection; 32 | import java.util.Comparator; 33 | import java.util.ConcurrentModificationException; 34 | import java.util.Iterator; 35 | import java.util.NoSuchElementException; 36 | import java.util.Queue; 37 | import java.util.SortedSet; 38 | import java.util.Spliterator; 39 | import java.util.function.Consumer; 40 | 41 | /** 42 | * An unbounded priority {@linkplain Queue queue} based on a priority heap. 43 | * The elements of the priority queue are ordered according to their 44 | * {@linkplain Comparable natural ordering}, or by a {@link Comparator} 45 | * provided at queue construction time, depending on which constructor is 46 | * used. A priority queue does not permit {@code null} elements. 47 | * A priority queue relying on natural ordering also does not permit 48 | * insertion of non-comparable objects (doing so may result in 49 | * {@code ClassCastException}). 50 | * 51 | *

The head of this queue is the least element 52 | * with respect to the specified ordering. If multiple elements are 53 | * tied for least value, the head is one of those elements -- ties are 54 | * broken arbitrarily. The queue retrieval operations {@code poll}, 55 | * {@code remove}, {@code peek}, and {@code element} access the 56 | * element at the head of the queue. 57 | * 58 | *

A priority queue is unbounded, but has an internal 59 | * capacity governing the size of an array used to store the 60 | * elements on the queue. It is always at least as large as the queue 61 | * size. As elements are added to a priority queue, its capacity 62 | * grows automatically. The details of the growth policy are not 63 | * specified. 64 | * 65 | *

This class and its iterator implement all of the 66 | * optional methods of the {@link Collection} and {@link 67 | * Iterator} interfaces. The Iterator provided in method {@link 68 | * #iterator()} is not guaranteed to traverse the elements of 69 | * the priority queue in any particular order. If you need ordered 70 | * traversal, consider using {@code Arrays.sort(pq.toArray())}. 71 | * 72 | *

Note that this implementation is not synchronized. 73 | * Multiple threads should not access a {@code PriorityQueue} 74 | * instance concurrently if any of the threads modifies the queue. 75 | * Instead, use the thread-safe {@link 76 | * java.util.concurrent.PriorityBlockingQueue} class. 77 | * 78 | *

Implementation note: this implementation provides 79 | * O(log(n)) time for the enqueuing and dequeuing methods 80 | * ({@code offer}, {@code poll}, {@code remove()} and {@code add}); 81 | * linear time for the {@code remove(Object)} and {@code contains(Object)} 82 | * methods; and constant time for the retrieval methods 83 | * ({@code peek}, {@code element}, and {@code size}). 84 | * 85 | *

This class is a member of the 86 | * 87 | * Java Collections Framework. 88 | * 89 | * @since 1.5 90 | * @author Josh Bloch, Doug Lea 91 | * @param the type of elements held in this collection 92 | */ 93 | public class PriorityQueue extends AbstractQueue 94 | implements java.io.Serializable { 95 | 96 | private static final long serialVersionUID = -7720805057305804111L; 97 | 98 | private static final int DEFAULT_INITIAL_CAPACITY = 11; 99 | 100 | /** 101 | * Priority queue represented as a balanced binary heap: the two 102 | * children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The 103 | * priority queue is ordered by comparator, or by the elements' 104 | * natural ordering, if comparator is null: For each node n in the 105 | * heap and each descendant d of n, n <= d. The element with the 106 | * lowest value is in queue[0], assuming the queue is nonempty. 107 | */ 108 | transient Object[] queue; // non-private to simplify nested class access 109 | 110 | /** 111 | * The number of elements in the priority queue. 112 | */ 113 | private int size = 0; 114 | 115 | /** 116 | * The comparator, or null if priority queue uses elements' 117 | * natural ordering. 118 | */ 119 | private final Comparator comparator; 120 | 121 | /** 122 | * The number of times this priority queue has been 123 | * structurally modified. See AbstractList for gory details. 124 | */ 125 | transient int modCount = 0; // non-private to simplify nested class access 126 | 127 | /** 128 | * Creates a {@code PriorityQueue} with the default initial 129 | * capacity (11) that orders its elements according to their 130 | * {@linkplain Comparable natural ordering}. 131 | */ 132 | public PriorityQueue() { 133 | this(DEFAULT_INITIAL_CAPACITY, null); 134 | } 135 | 136 | /** 137 | * Creates a {@code PriorityQueue} with the specified initial 138 | * capacity that orders its elements according to their 139 | * {@linkplain Comparable natural ordering}. 140 | * 141 | * @param initialCapacity the initial capacity for this priority queue 142 | * @throws IllegalArgumentException if {@code initialCapacity} is less 143 | * than 1 144 | */ 145 | public PriorityQueue(int initialCapacity) { 146 | this(initialCapacity, null); 147 | } 148 | 149 | /** 150 | * Creates a {@code PriorityQueue} with the default initial capacity and 151 | * whose elements are ordered according to the specified comparator. 152 | * 153 | * @param comparator the comparator that will be used to order this 154 | * priority queue. If {@code null}, the {@linkplain Comparable 155 | * natural ordering} of the elements will be used. 156 | * @since 1.8 157 | */ 158 | public PriorityQueue(Comparator comparator) { 159 | this(DEFAULT_INITIAL_CAPACITY, comparator); 160 | } 161 | 162 | /** 163 | * Creates a {@code PriorityQueue} with the specified initial capacity 164 | * that orders its elements according to the specified comparator. 165 | * 166 | * @param initialCapacity the initial capacity for this priority queue 167 | * @param comparator the comparator that will be used to order this 168 | * priority queue. If {@code null}, the {@linkplain Comparable 169 | * natural ordering} of the elements will be used. 170 | * @throws IllegalArgumentException if {@code initialCapacity} is 171 | * less than 1 172 | */ 173 | public PriorityQueue(int initialCapacity, 174 | Comparator comparator) { 175 | // Note: This restriction of at least one is not actually needed, 176 | // but continues for 1.5 compatibility 177 | if (initialCapacity < 1) 178 | throw new IllegalArgumentException(); 179 | this.queue = new Object[initialCapacity]; 180 | this.comparator = comparator; 181 | } 182 | 183 | /** 184 | * Creates a {@code PriorityQueue} containing the elements in the 185 | * specified collection. If the specified collection is an instance of 186 | * a {@link SortedSet} or is another {@code PriorityQueue}, this 187 | * priority queue will be ordered according to the same ordering. 188 | * Otherwise, this priority queue will be ordered according to the 189 | * {@linkplain Comparable natural ordering} of its elements. 190 | * 191 | * @param c the collection whose elements are to be placed 192 | * into this priority queue 193 | * @throws ClassCastException if elements of the specified collection 194 | * cannot be compared to one another according to the priority 195 | * queue's ordering 196 | * @throws NullPointerException if the specified collection or any 197 | * of its elements are null 198 | */ 199 | @SuppressWarnings("unchecked") 200 | public PriorityQueue(Collection c) { 201 | if (c instanceof SortedSet) { 202 | SortedSet ss = (SortedSet) c; 203 | this.comparator = (Comparator) ss.comparator(); 204 | initElementsFromCollection(ss); 205 | } 206 | else if (c instanceof PriorityQueue) { 207 | PriorityQueue pq = (PriorityQueue) c; 208 | this.comparator = (Comparator) pq.comparator(); 209 | initFromPriorityQueue(pq); 210 | } 211 | else { 212 | this.comparator = null; 213 | initFromCollection(c); 214 | } 215 | } 216 | 217 | /** 218 | * Creates a {@code PriorityQueue} containing the elements in the 219 | * specified priority queue. This priority queue will be 220 | * ordered according to the same ordering as the given priority 221 | * queue. 222 | * 223 | * @param c the priority queue whose elements are to be placed 224 | * into this priority queue 225 | * @throws ClassCastException if elements of {@code c} cannot be 226 | * compared to one another according to {@code c}'s 227 | * ordering 228 | * @throws NullPointerException if the specified priority queue or any 229 | * of its elements are null 230 | */ 231 | @SuppressWarnings("unchecked") 232 | public PriorityQueue(PriorityQueue c) { 233 | this.comparator = (Comparator) c.comparator(); 234 | initFromPriorityQueue(c); 235 | } 236 | 237 | /** 238 | * Creates a {@code PriorityQueue} containing the elements in the 239 | * specified sorted set. This priority queue will be ordered 240 | * according to the same ordering as the given sorted set. 241 | * 242 | * @param c the sorted set whose elements are to be placed 243 | * into this priority queue 244 | * @throws ClassCastException if elements of the specified sorted 245 | * set cannot be compared to one another according to the 246 | * sorted set's ordering 247 | * @throws NullPointerException if the specified sorted set or any 248 | * of its elements are null 249 | */ 250 | @SuppressWarnings("unchecked") 251 | public PriorityQueue(SortedSet c) { 252 | this.comparator = (Comparator) c.comparator(); 253 | initElementsFromCollection(c); 254 | } 255 | 256 | private void initFromPriorityQueue(PriorityQueue c) { 257 | if (c.getClass() == PriorityQueue.class) { 258 | this.queue = c.toArray(); 259 | this.size = c.size(); 260 | } else { 261 | initFromCollection(c); 262 | } 263 | } 264 | 265 | private void initElementsFromCollection(Collection c) { 266 | Object[] a = c.toArray(); 267 | // If c.toArray incorrectly doesn't return Object[], copy it. 268 | if (a.getClass() != Object[].class) 269 | a = Arrays.copyOf(a, a.length, Object[].class); 270 | int len = a.length; 271 | if (len == 1 || this.comparator != null) 272 | for (int i = 0; i < len; i++) 273 | if (a[i] == null) 274 | throw new NullPointerException(); 275 | this.queue = a; 276 | this.size = a.length; 277 | } 278 | 279 | /** 280 | * Initializes queue array with elements from the given Collection. 281 | * 282 | * @param c the collection 283 | */ 284 | private void initFromCollection(Collection c) { 285 | initElementsFromCollection(c); 286 | heapify(); 287 | } 288 | 289 | /** 290 | * The maximum size of array to allocate. 291 | * Some VMs reserve some header words in an array. 292 | * Attempts to allocate larger arrays may result in 293 | * OutOfMemoryError: Requested array size exceeds VM limit 294 | */ 295 | private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 296 | 297 | /** 298 | * Increases the capacity of the array. 299 | * 300 | * @param minCapacity the desired minimum capacity 301 | */ 302 | private void grow(int minCapacity) { 303 | int oldCapacity = queue.length; 304 | // Double size if small; else grow by 50% 305 | int newCapacity = oldCapacity + ((oldCapacity < 64) ? 306 | (oldCapacity + 2) : 307 | (oldCapacity >> 1)); 308 | // overflow-conscious code 309 | if (newCapacity - MAX_ARRAY_SIZE > 0) 310 | newCapacity = hugeCapacity(minCapacity); 311 | queue = Arrays.copyOf(queue, newCapacity); 312 | } 313 | 314 | private static int hugeCapacity(int minCapacity) { 315 | if (minCapacity < 0) // overflow 316 | throw new OutOfMemoryError(); 317 | return (minCapacity > MAX_ARRAY_SIZE) ? 318 | Integer.MAX_VALUE : 319 | MAX_ARRAY_SIZE; 320 | } 321 | 322 | /** 323 | * Inserts the specified element into this priority queue. 324 | * 325 | * @return {@code true} (as specified by {@link Collection#add}) 326 | * @throws ClassCastException if the specified element cannot be 327 | * compared with elements currently in this priority queue 328 | * according to the priority queue's ordering 329 | * @throws NullPointerException if the specified element is null 330 | */ 331 | public boolean add(E e) { 332 | return offer(e); 333 | } 334 | 335 | /** 336 | * Inserts the specified element into this priority queue. 337 | * 338 | * @return {@code true} (as specified by {@link Queue#offer}) 339 | * @throws ClassCastException if the specified element cannot be 340 | * compared with elements currently in this priority queue 341 | * according to the priority queue's ordering 342 | * @throws NullPointerException if the specified element is null 343 | */ 344 | public boolean offer(E e) { 345 | if (e == null) 346 | throw new NullPointerException(); 347 | modCount++; 348 | int i = size; 349 | if (i >= queue.length) 350 | grow(i + 1); 351 | size = i + 1; 352 | if (i == 0) 353 | queue[0] = e; 354 | else 355 | siftUp(i, e); 356 | return true; 357 | } 358 | 359 | @SuppressWarnings("unchecked") 360 | public E peek() { 361 | return (size == 0) ? null : (E) queue[0]; 362 | } 363 | 364 | private int indexOf(Object o) { 365 | if (o != null) { 366 | for (int i = 0; i < size; i++) 367 | if (o.equals(queue[i])) 368 | return i; 369 | } 370 | return -1; 371 | } 372 | 373 | /** 374 | * Removes a single instance of the specified element from this queue, 375 | * if it is present. More formally, removes an element {@code e} such 376 | * that {@code o.equals(e)}, if this queue contains one or more such 377 | * elements. Returns {@code true} if and only if this queue contained 378 | * the specified element (or equivalently, if this queue changed as a 379 | * result of the call). 380 | * 381 | * @param o element to be removed from this queue, if present 382 | * @return {@code true} if this queue changed as a result of the call 383 | */ 384 | public boolean remove(Object o) { 385 | int i = indexOf(o); 386 | if (i == -1) 387 | return false; 388 | else { 389 | removeAt(i); 390 | return true; 391 | } 392 | } 393 | 394 | /** 395 | * Version of remove using reference equality, not equals. 396 | * Needed by iterator.remove. 397 | * 398 | * @param o element to be removed from this queue, if present 399 | * @return {@code true} if removed 400 | */ 401 | boolean removeEq(Object o) { 402 | for (int i = 0; i < size; i++) { 403 | if (o == queue[i]) { 404 | removeAt(i); 405 | return true; 406 | } 407 | } 408 | return false; 409 | } 410 | 411 | /** 412 | * Returns {@code true} if this queue contains the specified element. 413 | * More formally, returns {@code true} if and only if this queue contains 414 | * at least one element {@code e} such that {@code o.equals(e)}. 415 | * 416 | * @param o object to be checked for containment in this queue 417 | * @return {@code true} if this queue contains the specified element 418 | */ 419 | public boolean contains(Object o) { 420 | return indexOf(o) != -1; 421 | } 422 | 423 | /** 424 | * Returns an array containing all of the elements in this queue. 425 | * The elements are in no particular order. 426 | * 427 | *

The returned array will be "safe" in that no references to it are 428 | * maintained by this queue. (In other words, this method must allocate 429 | * a new array). The caller is thus free to modify the returned array. 430 | * 431 | *

This method acts as bridge between array-based and collection-based 432 | * APIs. 433 | * 434 | * @return an array containing all of the elements in this queue 435 | */ 436 | public Object[] toArray() { 437 | return Arrays.copyOf(queue, size); 438 | } 439 | 440 | /** 441 | * Returns an array containing all of the elements in this queue; the 442 | * runtime type of the returned array is that of the specified array. 443 | * The returned array elements are in no particular order. 444 | * If the queue fits in the specified array, it is returned therein. 445 | * Otherwise, a new array is allocated with the runtime type of the 446 | * specified array and the size of this queue. 447 | * 448 | *

If the queue fits in the specified array with room to spare 449 | * (i.e., the array has more elements than the queue), the element in 450 | * the array immediately following the end of the collection is set to 451 | * {@code null}. 452 | * 453 | *

Like the {@link #toArray()} method, this method acts as bridge between 454 | * array-based and collection-based APIs. Further, this method allows 455 | * precise control over the runtime type of the output array, and may, 456 | * under certain circumstances, be used to save allocation costs. 457 | * 458 | *

Suppose {@code x} is a queue known to contain only strings. 459 | * The following code can be used to dump the queue into a newly 460 | * allocated array of {@code String}: 461 | * 462 | *

 {@code String[] y = x.toArray(new String[0]);}
463 | * 464 | * Note that {@code toArray(new Object[0])} is identical in function to 465 | * {@code toArray()}. 466 | * 467 | * @param a the array into which the elements of the queue are to 468 | * be stored, if it is big enough; otherwise, a new array of the 469 | * same runtime type is allocated for this purpose. 470 | * @return an array containing all of the elements in this queue 471 | * @throws ArrayStoreException if the runtime type of the specified array 472 | * is not a supertype of the runtime type of every element in 473 | * this queue 474 | * @throws NullPointerException if the specified array is null 475 | */ 476 | @SuppressWarnings("unchecked") 477 | public T[] toArray(T[] a) { 478 | final int size = this.size; 479 | if (a.length < size) 480 | // Make a new array of a's runtime type, but my contents: 481 | return (T[]) Arrays.copyOf(queue, size, a.getClass()); 482 | System.arraycopy(queue, 0, a, 0, size); 483 | if (a.length > size) 484 | a[size] = null; 485 | return a; 486 | } 487 | 488 | /** 489 | * Returns an iterator over the elements in this queue. The iterator 490 | * does not return the elements in any particular order. 491 | * 492 | * @return an iterator over the elements in this queue 493 | */ 494 | public Iterator iterator() { 495 | return new Itr(); 496 | } 497 | 498 | private final class Itr implements Iterator { 499 | /** 500 | * Index (into queue array) of element to be returned by 501 | * subsequent call to next. 502 | */ 503 | private int cursor = 0; 504 | 505 | /** 506 | * Index of element returned by most recent call to next, 507 | * unless that element came from the forgetMeNot list. 508 | * Set to -1 if element is deleted by a call to remove. 509 | */ 510 | private int lastRet = -1; 511 | 512 | /** 513 | * A queue of elements that were moved from the unvisited portion of 514 | * the heap into the visited portion as a result of "unlucky" element 515 | * removals during the iteration. (Unlucky element removals are those 516 | * that require a siftup instead of a siftdown.) We must visit all of 517 | * the elements in this list to complete the iteration. We do this 518 | * after we've completed the "normal" iteration. 519 | * 520 | * We expect that most iterations, even those involving removals, 521 | * will not need to store elements in this field. 522 | */ 523 | private ArrayDeque forgetMeNot = null; 524 | 525 | /** 526 | * Element returned by the most recent call to next iff that 527 | * element was drawn from the forgetMeNot list. 528 | */ 529 | private E lastRetElt = null; 530 | 531 | /** 532 | * The modCount value that the iterator believes that the backing 533 | * Queue should have. If this expectation is violated, the iterator 534 | * has detected concurrent modification. 535 | */ 536 | private int expectedModCount = modCount; 537 | 538 | public boolean hasNext() { 539 | return cursor < size || 540 | (forgetMeNot != null && !forgetMeNot.isEmpty()); 541 | } 542 | 543 | @SuppressWarnings("unchecked") 544 | public E next() { 545 | if (expectedModCount != modCount) 546 | throw new ConcurrentModificationException(); 547 | if (cursor < size) 548 | return (E) queue[lastRet = cursor++]; 549 | if (forgetMeNot != null) { 550 | lastRet = -1; 551 | lastRetElt = forgetMeNot.poll(); 552 | if (lastRetElt != null) 553 | return lastRetElt; 554 | } 555 | throw new NoSuchElementException(); 556 | } 557 | 558 | public void remove() { 559 | if (expectedModCount != modCount) 560 | throw new ConcurrentModificationException(); 561 | if (lastRet != -1) { 562 | E moved = PriorityQueue.this.removeAt(lastRet); 563 | lastRet = -1; 564 | if (moved == null) 565 | cursor--; 566 | else { 567 | if (forgetMeNot == null) 568 | forgetMeNot = new ArrayDeque<>(); 569 | forgetMeNot.add(moved); 570 | } 571 | } else if (lastRetElt != null) { 572 | PriorityQueue.this.removeEq(lastRetElt); 573 | lastRetElt = null; 574 | } else { 575 | throw new IllegalStateException(); 576 | } 577 | expectedModCount = modCount; 578 | } 579 | } 580 | 581 | public int size() { 582 | return size; 583 | } 584 | 585 | /** 586 | * Removes all of the elements from this priority queue. 587 | * The queue will be empty after this call returns. 588 | */ 589 | public void clear() { 590 | modCount++; 591 | for (int i = 0; i < size; i++) 592 | queue[i] = null; 593 | size = 0; 594 | } 595 | 596 | @SuppressWarnings("unchecked") 597 | public E poll() { 598 | if (size == 0) 599 | return null; 600 | int s = --size; 601 | modCount++; 602 | E result = (E) queue[0]; 603 | E x = (E) queue[s]; 604 | queue[s] = null; 605 | if (s != 0) 606 | siftDown(0, x); 607 | return result; 608 | } 609 | 610 | /** 611 | * Removes the ith element from queue. 612 | * 613 | * Normally this method leaves the elements at up to i-1, 614 | * inclusive, untouched. Under these circumstances, it returns 615 | * null. Occasionally, in order to maintain the heap invariant, 616 | * it must swap a later element of the list with one earlier than 617 | * i. Under these circumstances, this method returns the element 618 | * that was previously at the end of the list and is now at some 619 | * position before i. This fact is used by iterator.remove so as to 620 | * avoid missing traversing elements. 621 | */ 622 | @SuppressWarnings("unchecked") 623 | private E removeAt(int i) { 624 | // assert i >= 0 && i < size; 625 | modCount++; 626 | int s = --size; 627 | if (s == i) // removed last element 628 | queue[i] = null; 629 | else { 630 | E moved = (E) queue[s]; 631 | queue[s] = null; 632 | siftDown(i, moved); 633 | if (queue[i] == moved) { 634 | siftUp(i, moved); 635 | if (queue[i] != moved) 636 | return moved; 637 | } 638 | } 639 | return null; 640 | } 641 | 642 | /** 643 | * Inserts item x at position k, maintaining heap invariant by 644 | * promoting x up the tree until it is greater than or equal to 645 | * its parent, or is the root. 646 | * 647 | * To simplify and speed up coercions and comparisons. the 648 | * Comparable and Comparator versions are separated into different 649 | * methods that are otherwise identical. (Similarly for siftDown.) 650 | * 651 | * @param k the position to fill 652 | * @param x the item to insert 653 | */ 654 | private void siftUp(int k, E x) { 655 | if (comparator != null) 656 | siftUpUsingComparator(k, x); 657 | else 658 | siftUpComparable(k, x); 659 | } 660 | 661 | @SuppressWarnings("unchecked") 662 | private void siftUpComparable(int k, E x) { 663 | Comparable key = (Comparable) x; 664 | while (k > 0) { 665 | int parent = (k - 1) >>> 1; 666 | Object e = queue[parent]; 667 | if (key.compareTo((E) e) >= 0) 668 | break; 669 | queue[k] = e; 670 | k = parent; 671 | } 672 | queue[k] = key; 673 | } 674 | 675 | @SuppressWarnings("unchecked") 676 | private void siftUpUsingComparator(int k, E x) { 677 | while (k > 0) { 678 | int parent = (k - 1) >>> 1; 679 | Object e = queue[parent]; 680 | if (comparator.compare(x, (E) e) >= 0) 681 | break; 682 | queue[k] = e; 683 | k = parent; 684 | } 685 | queue[k] = x; 686 | } 687 | 688 | /** 689 | * Inserts item x at position k, maintaining heap invariant by 690 | * demoting x down the tree repeatedly until it is less than or 691 | * equal to its children or is a leaf. 692 | * 693 | * @param k the position to fill 694 | * @param x the item to insert 695 | */ 696 | private void siftDown(int k, E x) { 697 | if (comparator != null) 698 | siftDownUsingComparator(k, x); 699 | else 700 | siftDownComparable(k, x); 701 | } 702 | 703 | @SuppressWarnings("unchecked") 704 | private void siftDownComparable(int k, E x) { 705 | Comparable key = (Comparable)x; 706 | int half = size >>> 1; // loop while a non-leaf 707 | while (k < half) { 708 | int child = (k << 1) + 1; // assume left child is least 709 | Object c = queue[child]; 710 | int right = child + 1; 711 | if (right < size && 712 | ((Comparable) c).compareTo((E) queue[right]) > 0) 713 | c = queue[child = right]; 714 | if (key.compareTo((E) c) <= 0) 715 | break; 716 | queue[k] = c; 717 | k = child; 718 | } 719 | queue[k] = key; 720 | } 721 | 722 | @SuppressWarnings("unchecked") 723 | private void siftDownUsingComparator(int k, E x) { 724 | int half = size >>> 1; 725 | while (k < half) { 726 | int child = (k << 1) + 1; 727 | Object c = queue[child]; 728 | int right = child + 1; 729 | if (right < size && 730 | comparator.compare((E) c, (E) queue[right]) > 0) 731 | c = queue[child = right]; 732 | if (comparator.compare(x, (E) c) <= 0) 733 | break; 734 | queue[k] = c; 735 | k = child; 736 | } 737 | queue[k] = x; 738 | } 739 | 740 | /** 741 | * Establishes the heap invariant (described above) in the entire tree, 742 | * assuming nothing about the order of the elements prior to the call. 743 | */ 744 | @SuppressWarnings("unchecked") 745 | private void heapify() { 746 | for (int i = (size >>> 1) - 1; i >= 0; i--) 747 | siftDown(i, (E) queue[i]); 748 | } 749 | 750 | /** 751 | * Returns the comparator used to order the elements in this 752 | * queue, or {@code null} if this queue is sorted according to 753 | * the {@linkplain Comparable natural ordering} of its elements. 754 | * 755 | * @return the comparator used to order this queue, or 756 | * {@code null} if this queue is sorted according to the 757 | * natural ordering of its elements 758 | */ 759 | public Comparator comparator() { 760 | return comparator; 761 | } 762 | 763 | /** 764 | * Saves this queue to a stream (that is, serializes it). 765 | * 766 | * @serialData The length of the array backing the instance is 767 | * emitted (int), followed by all of its elements 768 | * (each an {@code Object}) in the proper order. 769 | * @param s the stream 770 | */ 771 | private void writeObject(java.io.ObjectOutputStream s) 772 | throws java.io.IOException { 773 | // Write out element count, and any hidden stuff 774 | s.defaultWriteObject(); 775 | 776 | // Write out array length, for compatibility with 1.5 version 777 | s.writeInt(Math.max(2, size + 1)); 778 | 779 | // Write out all elements in the "proper order". 780 | for (int i = 0; i < size; i++) 781 | s.writeObject(queue[i]); 782 | } 783 | 784 | /** 785 | * Reconstitutes the {@code PriorityQueue} instance from a stream 786 | * (that is, deserializes it). 787 | * 788 | * @param s the stream 789 | */ 790 | private void readObject(java.io.ObjectInputStream s) 791 | throws java.io.IOException, ClassNotFoundException { 792 | // Read in size, and any hidden stuff 793 | s.defaultReadObject(); 794 | 795 | // Read in (and discard) array length 796 | s.readInt(); 797 | 798 | queue = new Object[size]; 799 | 800 | // Read in all elements. 801 | for (int i = 0; i < size; i++) 802 | queue[i] = s.readObject(); 803 | 804 | // Elements are guaranteed to be in "proper order", but the 805 | // spec has never explained what that might be. 806 | heapify(); 807 | } 808 | 809 | /** 810 | * Creates a late-binding 811 | * and fail-fast {@link Spliterator} over the elements in this 812 | * queue. 813 | * 814 | *

The {@code Spliterator} reports {@link Spliterator#SIZED}, 815 | * {@link Spliterator#SUBSIZED}, and {@link Spliterator#NONNULL}. 816 | * Overriding implementations should document the reporting of additional 817 | * characteristic values. 818 | * 819 | * @return a {@code Spliterator} over the elements in this queue 820 | * @since 1.8 821 | */ 822 | public final Spliterator spliterator() { 823 | return new PriorityQueueSpliterator(this, 0, -1, 0); 824 | } 825 | 826 | static final class PriorityQueueSpliterator implements Spliterator { 827 | /* 828 | * This is very similar to ArrayList Spliterator, except for 829 | * extra null checks. 830 | */ 831 | private final PriorityQueue pq; 832 | private int index; // current index, modified on advance/split 833 | private int fence; // -1 until first use 834 | private int expectedModCount; // initialized when fence set 835 | 836 | /** Creates new spliterator covering the given range */ 837 | PriorityQueueSpliterator(PriorityQueue pq, int origin, int fence, 838 | int expectedModCount) { 839 | this.pq = pq; 840 | this.index = origin; 841 | this.fence = fence; 842 | this.expectedModCount = expectedModCount; 843 | } 844 | 845 | private int getFence() { // initialize fence to size on first use 846 | int hi; 847 | if ((hi = fence) < 0) { 848 | expectedModCount = pq.modCount; 849 | hi = fence = pq.size; 850 | } 851 | return hi; 852 | } 853 | 854 | public PriorityQueueSpliterator trySplit() { 855 | int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; 856 | return (lo >= mid) ? null : 857 | new PriorityQueueSpliterator(pq, lo, index = mid, 858 | expectedModCount); 859 | } 860 | 861 | @SuppressWarnings("unchecked") 862 | public void forEachRemaining(Consumer action) { 863 | int i, hi, mc; // hoist accesses and checks from loop 864 | PriorityQueue q; Object[] a; 865 | if (action == null) 866 | throw new NullPointerException(); 867 | if ((q = pq) != null && (a = q.queue) != null) { 868 | if ((hi = fence) < 0) { 869 | mc = q.modCount; 870 | hi = q.size; 871 | } 872 | else 873 | mc = expectedModCount; 874 | if ((i = index) >= 0 && (index = hi) <= a.length) { 875 | for (E e;; ++i) { 876 | if (i < hi) { 877 | if ((e = (E) a[i]) == null) // must be CME 878 | break; 879 | action.accept(e); 880 | } 881 | else if (q.modCount != mc) 882 | break; 883 | else 884 | return; 885 | } 886 | } 887 | } 888 | throw new ConcurrentModificationException(); 889 | } 890 | 891 | public boolean tryAdvance(Consumer action) { 892 | if (action == null) 893 | throw new NullPointerException(); 894 | int hi = getFence(), lo = index; 895 | if (lo >= 0 && lo < hi) { 896 | index = lo + 1; 897 | @SuppressWarnings("unchecked") E e = (E)pq.queue[lo]; 898 | if (e == null) 899 | throw new ConcurrentModificationException(); 900 | action.accept(e); 901 | if (pq.modCount != expectedModCount) 902 | throw new ConcurrentModificationException(); 903 | return true; 904 | } 905 | return false; 906 | } 907 | 908 | public long estimateSize() { 909 | return (long) (getFence() - index); 910 | } 911 | 912 | public int characteristics() { 913 | return Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL; 914 | } 915 | } 916 | } 917 | -------------------------------------------------------------------------------- /src/main/java/android/os/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package android.os.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | public class Logger { 7 | private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 8 | 9 | private static final boolean DEBUG; 10 | 11 | static { 12 | boolean isDebug = false; 13 | try { 14 | isDebug = Logger.class.getClassLoader().getResource("logger/debug") != null; 15 | } catch (Exception e) { 16 | e.printStackTrace(); 17 | } 18 | DEBUG = isDebug; 19 | } 20 | 21 | public static void debug(Object msg, Object ... args){ 22 | if(DEBUG){ 23 | StackTraceElement ste = new Throwable().getStackTrace()[1]; 24 | StringBuilder sb = new StringBuilder(); 25 | sb.append(SIMPLE_DATE_FORMAT.format(new Date())).append(" [").append(Thread.currentThread().getName()).append("] ") 26 | .append(build(String.format(String.valueOf(msg), args),ste)); 27 | System.out.println(sb); 28 | } 29 | } 30 | 31 | private static String build(String log, StackTraceElement ste) { 32 | StringBuilder buf = new StringBuilder(); 33 | if (ste.isNativeMethod()) { 34 | buf.append("(Native Method)"); 35 | } else { 36 | String fName = ste.getFileName(); 37 | 38 | if (fName == null) { 39 | buf.append("(Unknown Source)"); 40 | } else { 41 | int lineNum = ste.getLineNumber(); 42 | buf.append('('); 43 | buf.append(fName); 44 | if (lineNum >= 0) { 45 | buf.append(':'); 46 | buf.append(lineNum); 47 | } 48 | buf.append("):"); 49 | } 50 | } 51 | buf.append(log); 52 | return buf.toString(); 53 | } 54 | 55 | public static void main(String... args) { 56 | debug("Output nothing."); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/android/os/BaseTestHandler.kt: -------------------------------------------------------------------------------- 1 | package android.os 2 | 3 | import android.os.utils.* 4 | 5 | class BaseTestHandler(looper: Looper = Looper.myLooper()) : Handler(looper) { 6 | override fun handleMessage(msg: Message) { 7 | super.handleMessage(msg) 8 | Logger.debug(msg) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/android/os/HandlerTest.kt: -------------------------------------------------------------------------------- 1 | package android.os 2 | 3 | import android.os.utils.* 4 | import org.junit.* 5 | 6 | class HandlerTest { 7 | private lateinit var looper: Looper 8 | 9 | @Test 10 | fun test() { 11 | Looper.prepare() 12 | looper = Looper.myLooper() 13 | 14 | val workingThread = Thread(Runnable { 15 | testRemove() 16 | looper.quitSafely() 17 | }, "Working-Thread") 18 | workingThread.start() 19 | 20 | Looper.loop() 21 | } 22 | 23 | fun testRemove() { 24 | val token = Any() 25 | val r = Runnable { 26 | Logger.debug("Runnable called!") 27 | } 28 | val handler = BaseTestHandler(looper) 29 | 30 | val msg = Message().also { 31 | it.obj = token 32 | it.who = "msg" 33 | } 34 | 35 | val msg1= Message().also { 36 | it.what = 1 37 | it.who = "msg1" 38 | } 39 | 40 | val msg2 = Message().also { 41 | it.callback = r 42 | it.who = "msg2" 43 | } 44 | 45 | val msg3 = Message().also { 46 | it.callback = r 47 | it.obj = token 48 | it.who = "msg3" 49 | } 50 | 51 | val msg4= Message().also { 52 | it.what = 1 53 | it.obj = token 54 | it.who = "msg4" 55 | } 56 | 57 | handler.sendMessage(msg) 58 | handler.sendMessage(msg1) 59 | handler.sendMessage(msg2) 60 | handler.sendMessageDelayed(msg3, 10) 61 | handler.sendMessageDelayed(msg4, 100) 62 | //handler.removeMessages(1) 63 | //handler.removeCallbacksAndMessages(token) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/resources/logger/debug: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennyhuo/PortableAndroidHandler/5ea4a596e7d51fcf3219e27af0e3dfd58243357e/src/test/resources/logger/debug --------------------------------------------------------------------------------