make Looper, Handler, and MessageQueue work properly

this for example makes Unity apps not steal the main thread,
hanging Gtk.
This commit is contained in:
Mis012
2023-07-25 14:26:29 +02:00
parent 7ac5587fca
commit 08998b0076
15 changed files with 997 additions and 158 deletions

View File

@@ -6,6 +6,8 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -102,6 +104,12 @@ public class Activity extends Context {
protected void onCreate(Bundle savedInstanceState) {
System.out.println("- onCreate - yay!");
/* TODO: this probably belongs elsewhere, but this is our entry point for better or worse */
Looper looper = Looper.myLooper();
if(looper == null) {
Looper.prepareMainLooper();
}
return;
}
@@ -212,7 +220,11 @@ public class Activity extends Context {
}
public final void runOnUiThread(Runnable action) {
action.run(); // FIXME: running synchronously for now
if(Looper.myLooper() == Looper.getMainLooper()) {
action.run();
} else {
new Handler(Looper.getMainLooper()).post(action);
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {}

View File

@@ -33,6 +33,7 @@ import android.view.WindowManager;
import android.view.WindowManagerImpl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Context extends Object {
@@ -138,8 +139,14 @@ public class Context extends Object {
}
public Looper getMainLooper() {
System.out.println("returning the main Looper, most definitely doing just that!");
return new Looper();
/* TODO: this is not what AOSP does, which could be a problem */
Looper looper = Looper.myLooper();
if(looper == null) {
Looper.prepare();
looper = Looper.myLooper();
}
return looper;
}
public String getPackageName() {
@@ -251,9 +258,12 @@ public class Context extends Object {
return new ComponentName("", "");
}
// FIXME - it should be *trivial* to do actually implement this
public FileInputStream openFileInput(String name) {
return null;
// TODO: do these both work? make them look more alike
public FileInputStream openFileInput(String name) throws FileNotFoundException {
System.out.println("openFileInput called for: '" + name + "'");
File file = new File(getFilesDir(), name);
return new FileInputStream(file);
}
public FileOutputStream openFileOutput(String name, int mode) throws java.io.FileNotFoundException {

View File

@@ -738,6 +738,7 @@ public final class Bundle implements Cloneable {
*/
public boolean getBoolean(String key, boolean defaultValue) {
Object o = mMap.get(key);
System.out.println("bundle.getBoolean(" + key + ", " + defaultValue + ") called");
if (o == null) {
return defaultValue;
}

View File

@@ -225,7 +225,7 @@ public class Handler {
*/
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = null /*looper.mQueue*/;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
@@ -316,9 +316,7 @@ public class Handler {
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r) {
// return sendMessageDelayed(getPostMessage(r), 0);
r.run();
return true;
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
@@ -571,29 +569,14 @@ public class Handler {
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
/* MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);*/
if (mCallback != null) {
// System.out.println("Handler.sendMessageAtTime: directly calling mCallback.handleMessage)");
if (msg.callback != null) {
msg.callback.run();
}
return mCallback.handleMessage(msg);
} else {
// System.out.println("Handler.sendMessageAtTime: not directly calling mCallback.handleMessage - mCallback is null)");
/* do this in this case as well?
if(msg.callback != null) {
msg.callback.run();
}
*/
return true; // false?
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
@@ -609,15 +592,14 @@ public class Handler {
* looper processing the message queue is exiting.
*/
public final boolean sendMessageAtFrontOfQueue(Message msg) {
/*MessageQueue queue = mQueue;
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, 0);*/
return true;
return enqueueMessage(queue, msg, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

View File

@@ -1,19 +1,149 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
String name;
public HandlerThread() {}
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
this.name = name;
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public void start() {
// if(name.equals("FlurryAgent")) { return; }
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
return new Looper();
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}

View File

@@ -53,13 +53,6 @@ import android.util.Printer;
public final class Looper {
private static final String TAG = "Looper";
// FIXME
public Looper() {
mQueue = null;
mThread = Thread.currentThread();
System.out.println("making a fake Looper object, let's hope noone tries to actually use it");
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
@@ -84,7 +77,7 @@ public final class Looper {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(/*quitAllowed*/));
sThreadLocal.set(new Looper(quitAllowed));
}
/**
@@ -107,10 +100,9 @@ public final class Looper {
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
return new Looper();
/* synchronized (Looper.class) {
return sMainLooper;
}*/
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
@@ -118,51 +110,46 @@ public final class Looper {
* {@link #quit()} to end the loop.
*/
public static void loop() {
System.out.println("oops, Looper.loop called... and we don't implement that");
/*final Looper me = myLooper();
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
// Binder.clearCallingIdentity();
// final long ident = Binder.clearCallingIdentity();
// Binder.clearCallingIdentity();
// final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
// final long newIdent = Binder.clearCallingIdentity();
/* if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}* /
msg.recycle();
}*/
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
/* final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what);
}
*/
msg.recycle();
}
}
/**
@@ -170,11 +157,7 @@ public final class Looper {
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return new Looper();
/* if(sThreadLocal.get() == null) {
prepare(false);
}
return sThreadLocal.get();*/
return sThreadLocal.get();
}
/**
@@ -293,8 +276,7 @@ public final class Looper {
* Return the Thread associated with this Looper.
*/
public Thread getThread() {
return Thread.currentThread(); // ugly hack
// return mThread;
return mThread;
}
/**

View File

@@ -34,7 +34,7 @@ public final class MessageQueue {
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
private int mPtr; // used by native code
private long mPtr; // used by native code
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
@@ -48,11 +48,11 @@ public final class MessageQueue {
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
private native static int nativeInit();
private native static void nativeDestroy(int ptr);
private native static void nativePollOnce(int ptr, int timeoutMillis);
private native static void nativeWake(int ptr);
private native static boolean nativeIsIdling(int ptr);
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native static void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsIdling(long ptr);
/**
* Callback interface for discovering when a thread is going to block

View File

@@ -105,28 +105,25 @@ public final class SystemClock {
* @param ms to sleep before returning, in milliseconds of uptime.
*/
public static void sleep(long ms) {
System.out.println("!!! sleep(...) doesn't work, need to implement uptimeMillis");
/*
long start = uptimeMillis();
long duration = ms;
boolean interrupted = false;
do {
try {
Thread.sleep(duration);
}
catch (InterruptedException e) {
interrupted = true;
}
duration = start + ms - uptimeMillis();
} while (duration > 0);
if (interrupted) {
// Important: we don't want to quietly eat an interrupt() event,
// so we make sure to re-interrupt the thread so that the next
// call to Thread.sleep() or Object.wait() will be interrupted.
Thread.currentThread().interrupt();
}
*/}
long start = uptimeMillis();
long duration = ms;
boolean interrupted = false;
do {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
interrupted = true;
}
duration = start + ms - uptimeMillis();
} while (duration > 0);
if (interrupted) {
// Important: we don't want to quietly eat an interrupt() event,
// so we make sure to re-interrupt the thread so that the next
// call to Thread.sleep() or Object.wait() will be interrupted.
Thread.currentThread().interrupt();
}
}
/**
* Sets the current wall time, in milliseconds. Requires the calling
@@ -141,9 +138,7 @@ public final class SystemClock {
*
* @return milliseconds of non-sleep uptime since boot.
*/
/*native */ public static long uptimeMillis() {
return 654000; // FIXME
}
native public static long uptimeMillis();
/**
* Returns milliseconds since boot, including time spent in sleep.

File diff suppressed because it is too large Load Diff