Add support for non-blocking I/O with Looper.

Bug: 10349083
Change-Id: I4a94b1eac53df57c05103913bd593d92b1e062d7
This commit is contained in:
Jeff Brown
2015-03-05 18:21:06 -08:00
parent 803c2affcb
commit dc3eb4bf91
6 changed files with 352 additions and 22 deletions

View File

@@ -22372,7 +22372,17 @@ package android.os {
public final class MessageQueue {
method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
method public boolean isIdle();
method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
}
public static abstract class MessageQueue.FileDescriptorCallback {
ctor public MessageQueue.FileDescriptorCallback();
method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
field public static final int EVENT_ERROR = 4; // 0x4
field public static final int EVENT_INPUT = 1; // 0x1
field public static final int EVENT_OUTPUT = 2; // 0x2
}
public static abstract interface MessageQueue.IdleHandler {

View File

@@ -23967,7 +23967,17 @@ package android.os {
public final class MessageQueue {
method public void addIdleHandler(android.os.MessageQueue.IdleHandler);
method public boolean isIdle();
method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback);
method public void removeIdleHandler(android.os.MessageQueue.IdleHandler);
method public void unregisterFileDescriptorCallback(java.io.FileDescriptor);
}
public static abstract class MessageQueue.FileDescriptorCallback {
ctor public MessageQueue.FileDescriptorCallback();
method public int onFileDescriptorEvents(java.io.FileDescriptor, int);
field public static final int EVENT_ERROR = 4; // 0x4
field public static final int EVENT_INPUT = 1; // 0x1
field public static final int EVENT_OUTPUT = 2; // 0x2
}
public static abstract interface MessageQueue.IdleHandler {

View File

@@ -273,6 +273,7 @@ public final class Looper {
mQueue.dump(pw, prefix + " ");
}
@Override
public String toString() {
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";

View File

@@ -16,9 +16,15 @@
package android.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -30,6 +36,9 @@ import java.util.ArrayList;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
private static final String TAG = "MessageQueue";
private static final boolean DEBUG = false;
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@@ -38,6 +47,7 @@ public final class MessageQueue {
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
@@ -50,9 +60,10 @@ public final class MessageQueue {
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native static void nativePollOnce(long ptr, int timeoutMillis);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
@@ -101,7 +112,7 @@ public final class MessageQueue {
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(IdleHandler handler) {
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
@@ -119,7 +130,7 @@ public final class MessageQueue {
*
* @param handler The IdleHandler to be removed.
*/
public void removeIdleHandler(IdleHandler handler) {
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
@@ -148,6 +159,151 @@ public final class MessageQueue {
return !mQuitting && nativeIsPolling(mPtr);
}
/**
* Registers a file descriptor callback to receive notification when file descriptor
* related events occur.
* <p>
* If the file descriptor has already been registered, the specified events
* and callback will replace any that were previously associated with it.
* It is not possible to set more than one callback per file descriptor.
* </p><p>
* It is important to always unregister the callback when the file descriptor
* is no longer of use.
* </p>
*
* @param fd The file descriptor for which a callback will be registered.
* @param events The set of events to receive: a combination of the
* {@link FileDescriptorCallback#EVENT_INPUT},
* {@link FileDescriptorCallback#EVENT_OUTPUT}, and
* {@link FileDescriptorCallback#EVENT_ERROR} event masks. If the requested
* set of events is zero, then the callback is unregistered.
* @param callback The callback to invoke when file descriptor events occur.
*
* @see FileDescriptorCallback
* @see #unregisterFileDescriptorCallback
*/
public void registerFileDescriptorCallback(@NonNull FileDescriptor fd,
@FileDescriptorCallback.Events int events,
@NonNull FileDescriptorCallback callback) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
}
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
synchronized (this) {
setFileDescriptorCallbackLocked(fd, events, callback);
}
}
/**
* Unregisters a file descriptor callback.
* <p>
* This method does nothing if no callback has been registered for the
* specified file descriptor.
* </p>
*
* @param fd The file descriptor whose callback will be unregistered.
*
* @see FileDescriptorCallback
* @see #registerFileDescriptorCallback
*/
public void unregisterFileDescriptorCallback(@NonNull FileDescriptor fd) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
}
synchronized (this) {
setFileDescriptorCallbackLocked(fd, 0, null);
}
}
private void setFileDescriptorCallbackLocked(FileDescriptor fd, int events,
FileDescriptorCallback callback) {
final int fdNum = fd.getInt$();
int index = -1;
FileDescriptorRecord record = null;
if (mFileDescriptorRecords != null) {
index = mFileDescriptorRecords.indexOfKey(fdNum);
if (index >= 0) {
record = mFileDescriptorRecords.valueAt(index);
if (record != null && record.mEvents == events) {
return;
}
}
}
if (events != 0) {
events |= FileDescriptorCallback.EVENT_ERROR;
if (record == null) {
if (mFileDescriptorRecords == null) {
mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
}
record = new FileDescriptorRecord(fd, events, callback);
mFileDescriptorRecords.put(fdNum, record);
} else {
record.mCallback = callback;
record.mEvents = events;
record.mSeq += 1;
}
nativeSetFileDescriptorEvents(mPtr, fdNum, events);
} else if (record != null) {
record.mEvents = 0;
mFileDescriptorRecords.removeAt(index);
}
}
// Called from native code.
private int dispatchEvents(int fd, int events) {
// Get the file descriptor record and any state that might change.
final FileDescriptorRecord record;
final int oldWatchedEvents;
final FileDescriptorCallback callback;
final int seq;
synchronized (this) {
record = mFileDescriptorRecords.get(fd);
if (record == null) {
return 0; // spurious, no callback registered
}
oldWatchedEvents = record.mEvents;
events &= oldWatchedEvents; // filter events based on current watched set
if (events == 0) {
return oldWatchedEvents; // spurious, watched events changed
}
callback = record.mCallback;
seq = record.mSeq;
}
// Invoke the callback outside of the lock.
int newWatchedEvents = callback.onFileDescriptorEvents(
record.mDescriptor, events);
if (newWatchedEvents != 0) {
newWatchedEvents |= FileDescriptorCallback.EVENT_ERROR;
}
// Update the file descriptor record if the callback changed the set of
// events to watch and the callback itself hasn't been updated since.
if (newWatchedEvents != oldWatchedEvents) {
synchronized (this) {
int index = mFileDescriptorRecords.indexOfKey(fd);
if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
&& record.mSeq == seq) {
record.mEvents = newWatchedEvents;
if (newWatchedEvents == 0) {
mFileDescriptorRecords.removeAt(index);
}
}
}
}
// Return the new set of events to watch for native code to take care of.
return newWatchedEvents;
}
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
@@ -191,7 +347,8 @@ public final class MessageQueue {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
@@ -234,7 +391,7 @@ public final class MessageQueue {
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
@@ -385,7 +542,7 @@ public final class MessageQueue {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
@@ -627,4 +784,94 @@ public final class MessageQueue {
*/
boolean queueIdle();
}
/**
* A callback which is invoked when file descriptor related events occur.
*/
public static abstract class FileDescriptorCallback {
/**
* File descriptor event: Indicates that the file descriptor is ready for input
* operations, such as reading.
* <p>
* The callback should read all available data from the file descriptor
* then return <code>true</code> to keep the callback active or <code>false</code>
* to remove the callback.
* </p><p>
* In the case of a socket, this event may be generated to indicate
* that there is at least one incoming connection that the callback
* should accept.
* </p><p>
* This event will only be generated if the {@link #EVENT_INPUT} event mask was
* specified when the callback was added.
* </p>
*/
public static final int EVENT_INPUT = 1 << 0;
/**
* File descriptor event: Indicates that the file descriptor is ready for output
* operations, such as writing.
* <p>
* The callback should write as much data as it needs. If it could not
* write everything at once, then it should return <code>true</code> to
* keep the callback active. Otherwise, it should return <code>false</code>
* to remove the callback then re-register it later when it needs to write
* something else.
* </p><p>
* This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
* specified when the callback was added.
* </p>
*/
public static final int EVENT_OUTPUT = 1 << 1;
/**
* File descriptor event: Indicates that the file descriptor encountered a
* fatal error.
* <p>
* File descriptor errors can occur for various reasons. One common error
* is when the remote peer of a socket or pipe closes its end of the connection.
* </p><p>
* This event may be generated at any time regardless of whether the
* {@link #EVENT_ERROR} event mask was specified when the callback was added.
* </p>
*/
public static final int EVENT_ERROR = 1 << 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR})
public @interface Events {}
/**
* Called when a file descriptor receives events.
* <p>
* The default implementation does nothing and returns 0 to unregister the callback.
* </p>
*
* @param fd The file descriptor.
* @param events The set of events that occurred: a combination of the
* {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
* @return The new set of events to watch, or 0 to unregister the callback.
*
* @see #EVENT_INPUT
* @see #EVENT_OUTPUT
* @see #EVENT_ERROR
*/
public @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events) {
return 0;
}
}
private static final class FileDescriptorRecord {
public final FileDescriptor mDescriptor;
public int mEvents;
public FileDescriptorCallback mCallback;
public int mSeq;
public FileDescriptorRecord(FileDescriptor descriptor,
int events, FileDescriptorCallback callback) {
mDescriptor = descriptor;
mEvents = events;
mCallback = callback;
}
}
}

View File

@@ -29,22 +29,31 @@ namespace android {
static struct {
jfieldID mPtr; // native object attached to the DVM MessageQueue
jmethodID dispatchEvents;
} gMessageQueueClassInfo;
// Must be kept in sync with the constants in Looper.FileDescriptorCallback
static const int CALLBACK_EVENT_INPUT = 1 << 0;
static const int CALLBACK_EVENT_OUTPUT = 1 << 1;
static const int CALLBACK_EVENT_ERROR = 1 << 2;
class NativeMessageQueue : public MessageQueue {
class NativeMessageQueue : public MessageQueue, public LooperCallback {
public:
NativeMessageQueue();
virtual ~NativeMessageQueue();
virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
void pollOnce(JNIEnv* env, int timeoutMillis);
void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis);
void wake();
void setFileDescriptorEvents(int fd, int events);
virtual int handleEvent(int fd, int events, void* data);
private:
bool mInCallback;
JNIEnv* mPollEnv;
jobject mPollObj;
jthrowable mExceptionObj;
};
@@ -66,10 +75,11 @@ bool MessageQueue::raiseAndClearException(JNIEnv* env, const char* msg) {
return false;
}
NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
mLooper = new Looper(true);
Looper::setForThread(mLooper);
}
}
@@ -79,7 +89,7 @@ NativeMessageQueue::~NativeMessageQueue() {
void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) {
if (exceptionObj) {
if (mInCallback) {
if (mPollEnv == env) {
if (mExceptionObj) {
env->DeleteLocalRef(mExceptionObj);
}
@@ -94,10 +104,13 @@ void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable
}
}
void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
mInCallback = true;
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mInCallback = false;
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
@@ -109,6 +122,46 @@ void NativeMessageQueue::wake() {
mLooper->wake();
}
void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {
if (events) {
int looperEvents = 0;
if (events & CALLBACK_EVENT_INPUT) {
looperEvents |= Looper::EVENT_INPUT;
}
if (events & CALLBACK_EVENT_OUTPUT) {
looperEvents |= Looper::EVENT_OUTPUT;
}
mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
reinterpret_cast<void*>(events));
} else {
mLooper->removeFd(fd);
}
}
int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
int events = 0;
if (looperEvents & Looper::EVENT_INPUT) {
events |= CALLBACK_EVENT_INPUT;
}
if (looperEvents & Looper::EVENT_OUTPUT) {
events |= CALLBACK_EVENT_OUTPUT;
}
if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {
events |= CALLBACK_EVENT_ERROR;
}
int oldWatchedEvents = reinterpret_cast<int>(data);
int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,
gMessageQueueClassInfo.dispatchEvents, fd, events);
if (!newWatchedEvents) {
return 0; // unregister the fd
}
if (newWatchedEvents != oldWatchedEvents) {
setFileDescriptorEvents(fd, newWatchedEvents);
}
return 1;
}
// ----------------------------------------------------------------------------
sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
@@ -132,15 +185,15 @@ static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlo
nativeMessageQueue->decStrong(env);
}
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, timeoutMillis);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->wake();
nativeMessageQueue->wake();
}
static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) {
@@ -148,6 +201,12 @@ static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass claz
return nativeMessageQueue->getLooper()->isPolling();
}
static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,
jlong ptr, jint fd, jint events) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->setFileDescriptorEvents(fd, events);
}
// ----------------------------------------------------------------------------
static JNINativeMethod gMessageQueueMethods[] = {
@@ -156,7 +215,9 @@ static JNINativeMethod gMessageQueueMethods[] = {
{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling }
{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
{ "nativeSetFileDescriptorEvents", "(JII)V",
(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};
int register_android_os_MessageQueue(JNIEnv* env) {
@@ -164,8 +225,9 @@ int register_android_os_MessageQueue(JNIEnv* env) {
NELEM(gMessageQueueMethods));
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
"dispatchEvents", "(II)I");
return res;
}

View File

@@ -22,7 +22,7 @@
namespace android {
class MessageQueue : public RefBase {
class MessageQueue : public virtual RefBase {
public:
/* Gets the message queue's looper. */
inline sp<Looper> getLooper() const {