mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-04-28 20:27:58 +03:00
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:
parent
7ac5587fca
commit
08998b0076
15 changed files with 997 additions and 158 deletions
|
@ -59,6 +59,7 @@ libandroid_so = shared_library('android', [
|
|||
libtranslationlayer_so = shared_library('translation_layer_main', [
|
||||
'src/api-impl-jni/egl/com_google_android_gles_jni_EGLImpl.c',
|
||||
'src/api-impl-jni/android_os_Environment.c',
|
||||
'src/api-impl-jni/android_os_MessageQueue.c',
|
||||
'src/api-impl-jni/android_os_SystemClock.c',
|
||||
'src/api-impl-jni/android_view_Window.c',
|
||||
'src/api-impl-jni/util.c',
|
||||
|
|
62
src/api-impl-jni/android_os_MessageQueue.c
Normal file
62
src/api-impl-jni/android_os_MessageQueue.c
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "defines.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* TODO put these in a header */
|
||||
typedef void ALooper;
|
||||
ALooper * ALooper_prepare(void);
|
||||
void ALooper_wake(ALooper *looper);
|
||||
bool ALooper_isPolling(ALooper *looper);
|
||||
int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
|
||||
|
||||
struct native_message_queue {
|
||||
ALooper *looper;
|
||||
bool in_callback;
|
||||
};
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_android_os_MessageQueue_nativeInit(JNIEnv *env, jclass this)
|
||||
{
|
||||
struct native_message_queue *message_queue = malloc(sizeof(struct native_message_queue));
|
||||
|
||||
message_queue->in_callback = false;
|
||||
message_queue->looper = ALooper_prepare();
|
||||
|
||||
return _INTPTR(message_queue);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy(JNIEnv *env, jclass this, jlong ptr)
|
||||
{
|
||||
struct native_message_queue *message_queue = _PTR(ptr);
|
||||
|
||||
free(message_queue);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce(JNIEnv *env, jclass this, jlong ptr, jint timeout_millis)
|
||||
{
|
||||
struct native_message_queue *message_queue = _PTR(ptr);
|
||||
|
||||
// printf("Java_android_os_MessageQueue_nativePollOnce: entry (timeout: %d)\n", timeout_millis);
|
||||
message_queue->in_callback = true;
|
||||
ALooper_pollOnce(timeout_millis, NULL, NULL, NULL);
|
||||
message_queue->in_callback = false;
|
||||
// printf("Java_android_os_MessageQueue_nativePollOnce: exit\n");
|
||||
|
||||
/* TODO: what's with the exception stuff */
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeWake(JNIEnv *env, jclass this, jlong ptr)
|
||||
{
|
||||
struct native_message_queue *message_queue = _PTR(ptr);
|
||||
|
||||
ALooper_wake(message_queue->looper);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativeIsIdling(JNIEnv *env, jclass this, jlong ptr)
|
||||
{
|
||||
struct native_message_queue *message_queue = _PTR(ptr);
|
||||
|
||||
return ALooper_isPolling(message_queue->looper);
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "generated_headers/android_os_SystemClock.h"
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_elapsedRealtime(JNIEnv *env, jclass this)
|
||||
|
@ -5,3 +8,10 @@ JNIEXPORT jlong JNICALL Java_android_os_SystemClock_elapsedRealtime(JNIEnv *env,
|
|||
printf("FIXME: Java_android_os_SystemClock_elapsedRealtime: returning 0\n");
|
||||
return 0; // FIXME
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_uptimeMillis(JNIEnv *env, jclass this)
|
||||
{
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_REALTIME, &t);
|
||||
return t.tv_sec * 1000 + lround(t.tv_nsec / 1e6);
|
||||
}
|
||||
|
|
|
@ -10,42 +10,42 @@ extern "C" {
|
|||
/*
|
||||
* Class: android_os_MessageQueue
|
||||
* Method: nativeInit
|
||||
* Signature: ()I
|
||||
* Signature: ()J
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_android_os_MessageQueue_nativeInit
|
||||
JNIEXPORT jlong JNICALL Java_android_os_MessageQueue_nativeInit
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: android_os_MessageQueue
|
||||
* Method: nativeDestroy
|
||||
* Signature: (I)V
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy
|
||||
(JNIEnv *, jclass, jint);
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: android_os_MessageQueue
|
||||
* Method: nativePollOnce
|
||||
* Signature: (II)V
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce
|
||||
(JNIEnv *, jclass, jint, jint);
|
||||
(JNIEnv *, jclass, jlong, jint);
|
||||
|
||||
/*
|
||||
* Class: android_os_MessageQueue
|
||||
* Method: nativeWake
|
||||
* Signature: (I)V
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeWake
|
||||
(JNIEnv *, jclass, jint);
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: android_os_MessageQueue
|
||||
* Method: nativeIsIdling
|
||||
* Signature: (I)Z
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativeIsIdling
|
||||
(JNIEnv *, jclass, jint);
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -15,6 +15,14 @@ extern "C" {
|
|||
JNIEXPORT jboolean JNICALL Java_android_os_SystemClock_setCurrentTimeMillis
|
||||
(JNIEnv *, jclass, jlong);
|
||||
|
||||
/*
|
||||
* Class: android_os_SystemClock
|
||||
* Method: uptimeMillis
|
||||
* Signature: ()J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_uptimeMillis
|
||||
(JNIEnv *, jclass);
|
||||
|
||||
/*
|
||||
* Class: android_os_SystemClock
|
||||
* Method: elapsedRealtime
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,25 +1,648 @@
|
|||
/*
|
||||
* Copyright (C) 2011 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.view;
|
||||
|
||||
//import android.hardware.display.DisplayManagerGlobal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
//import android.os.SystemProperties;
|
||||
import android.util.Log;
|
||||
/**
|
||||
* Coordinates the timing of animations, input and drawing.
|
||||
* <p>
|
||||
* The choreographer receives timing pulses (such as vertical synchronization)
|
||||
* from the display subsystem then schedules work to occur as part of rendering
|
||||
* the next display frame.
|
||||
* </p><p>
|
||||
* Applications typically interact with the choreographer indirectly using
|
||||
* higher level abstractions in the animation framework or the view hierarchy.
|
||||
* Here are some examples of things you can do using the higher-level APIs.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>To post an animation to be processed on a regular time basis synchronized with
|
||||
* display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
|
||||
* <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
|
||||
* frame, use {@link View#postOnAnimation}.</li>
|
||||
* <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
|
||||
* frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
|
||||
* <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
|
||||
* next display frame, use {@link View#postInvalidateOnAnimation()} or
|
||||
* {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
|
||||
* <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
|
||||
* sync with display frame rendering, do nothing. This already happens automatically.
|
||||
* {@link View#onDraw} will be called at the appropriate time.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* However, there are a few cases where you might want to use the functions of the
|
||||
* choreographer directly in your application. Here are some examples.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>If your application does its rendering in a different thread, possibly using GL,
|
||||
* or does not use the animation framework or view hierarchy at all
|
||||
* and you want to ensure that it is appropriately synchronized with the display, then use
|
||||
* {@link Choreographer#postFrameCallback}.</li>
|
||||
* <li>... and that's about it.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Each {@link Looper} thread has its own choreographer. Other threads can
|
||||
* post callbacks to run on the choreographer but they will run on the {@link Looper}
|
||||
* to which the choreographer belongs.
|
||||
* </p>
|
||||
*/
|
||||
public final class Choreographer {
|
||||
public static interface FrameCallback {
|
||||
public void doFrame(long frametime_in_nanoseconds);
|
||||
private static final String TAG = "Choreographer";
|
||||
private static final boolean DEBUG = false;
|
||||
// The default amount of time in ms between animation frames.
|
||||
// When vsync is not enabled, we want to have some idea of how long we should
|
||||
// wait before posting the next animation message. It is important that the
|
||||
// default value be less than the true inter-frame delay on all devices to avoid
|
||||
// situations where we might skip frames by waiting too long (we must compensate
|
||||
// for jitter and hardware variations). Regardless of this value, the animation
|
||||
// and display loop is ultimately rate-limited by how fast new graphics buffers can
|
||||
// be dequeued.
|
||||
private static final long DEFAULT_FRAME_DELAY = 10;
|
||||
// The number of milliseconds between animation frames.
|
||||
private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
|
||||
// Thread local storage for the choreographer.
|
||||
private static final ThreadLocal<Choreographer> sThreadInstance =
|
||||
new ThreadLocal<Choreographer>() {
|
||||
@Override
|
||||
protected Choreographer initialValue() {
|
||||
Looper looper = Looper.myLooper();
|
||||
if (looper == null) {
|
||||
throw new IllegalStateException("The current thread must have a looper!");
|
||||
}
|
||||
return new Choreographer(looper);
|
||||
}
|
||||
};
|
||||
// Enable/disable vsync for animations and drawing.
|
||||
private static final boolean USE_VSYNC = false;
|
||||
// Enable/disable using the frame time instead of returning now.
|
||||
private static final boolean USE_FRAME_TIME = false;
|
||||
// Set a limit to warn about skipped frames.
|
||||
// Skipped frames imply jank.
|
||||
private static final int SKIPPED_FRAME_WARNING_LIMIT = 30;
|
||||
private static final long NANOS_PER_MS = 1000000;
|
||||
private static final int MSG_DO_FRAME = 0;
|
||||
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
|
||||
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
|
||||
// All frame callbacks posted by applications have this token.
|
||||
private static final Object FRAME_CALLBACK_TOKEN = new Object() {
|
||||
public String toString() { return "FRAME_CALLBACK_TOKEN"; }
|
||||
};
|
||||
private final Object mLock = new Object();
|
||||
private final Looper mLooper;
|
||||
private final FrameHandler mHandler;
|
||||
// The display event receiver can only be accessed by the looper thread to which
|
||||
// it is attached. We take care to ensure that we post message to the looper
|
||||
// if appropriate when interacting with the display event receiver.
|
||||
private CallbackRecord mCallbackPool;
|
||||
private final CallbackQueue[] mCallbackQueues;
|
||||
private boolean mFrameScheduled;
|
||||
private boolean mCallbacksRunning;
|
||||
private long mLastFrameTimeNanos;
|
||||
private long mFrameIntervalNanos;
|
||||
/**
|
||||
* Callback type: Input callback. Runs first.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CALLBACK_INPUT = 0;
|
||||
/**
|
||||
* Callback type: Animation callback. Runs before traversals.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CALLBACK_ANIMATION = 1;
|
||||
/**
|
||||
* Callback type: Traversal callback. Handles layout and draw. Runs last
|
||||
* after all other asynchronous messages have been handled.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CALLBACK_TRAVERSAL = 2;
|
||||
private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
|
||||
private Choreographer(Looper looper) {
|
||||
mLooper = looper;
|
||||
mHandler = new FrameHandler(looper);
|
||||
mLastFrameTimeNanos = Long.MIN_VALUE;
|
||||
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
|
||||
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
|
||||
for (int i = 0; i <= CALLBACK_LAST; i++) {
|
||||
mCallbackQueues[i] = new CallbackQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private static float getRefreshRate() {
|
||||
/* DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
|
||||
Display.DEFAULT_DISPLAY);*/
|
||||
return 60/*di.refreshRate*/; // FIXME
|
||||
}
|
||||
/**
|
||||
* Gets the choreographer for the calling thread. Must be called from
|
||||
* a thread that already has a {@link android.os.Looper} associated with it.
|
||||
*
|
||||
* @return The choreographer for this thread.
|
||||
* @throws IllegalStateException if the thread does not have a looper.
|
||||
*/
|
||||
public static Choreographer getInstance() {
|
||||
return new Choreographer();
|
||||
return sThreadInstance.get();
|
||||
}
|
||||
|
||||
public void postFrameCallback(Choreographer.FrameCallback callback) {
|
||||
/**
|
||||
* The amount of time, in milliseconds, between each frame of the animation.
|
||||
* <p>
|
||||
* This is a requested time that the animation will attempt to honor, but the actual delay
|
||||
* between frames may be different, depending on system load and capabilities. This is a static
|
||||
* function because the same delay will be applied to all animations, since they are all
|
||||
* run off of a single timing loop.
|
||||
* </p><p>
|
||||
* The frame delay may be ignored when the animation system uses an external timing
|
||||
* source, such as the display refresh rate (vsync), to govern animations.
|
||||
* </p>
|
||||
*
|
||||
* @return the requested time between frames, in milliseconds
|
||||
* @hide
|
||||
*/
|
||||
public static long getFrameDelay() {
|
||||
return sFrameDelay;
|
||||
}
|
||||
/**
|
||||
* The amount of time, in milliseconds, between each frame of the animation.
|
||||
* <p>
|
||||
* This is a requested time that the animation will attempt to honor, but the actual delay
|
||||
* between frames may be different, depending on system load and capabilities. This is a static
|
||||
* function because the same delay will be applied to all animations, since they are all
|
||||
* run off of a single timing loop.
|
||||
* </p><p>
|
||||
* The frame delay may be ignored when the animation system uses an external timing
|
||||
* source, such as the display refresh rate (vsync), to govern animations.
|
||||
* </p>
|
||||
*
|
||||
* @param frameDelay the requested time between frames, in milliseconds
|
||||
* @hide
|
||||
*/
|
||||
public static void setFrameDelay(long frameDelay) {
|
||||
sFrameDelay = frameDelay;
|
||||
}
|
||||
/**
|
||||
* Subtracts typical frame delay time from a delay interval in milliseconds.
|
||||
* <p>
|
||||
* This method can be used to compensate for animation delay times that have baked
|
||||
* in assumptions about the frame delay. For example, it's quite common for code to
|
||||
* assume a 60Hz frame time and bake in a 16ms delay. When we call
|
||||
* {@link #postAnimationCallbackDelayed} we want to know how long to wait before
|
||||
* posting the animation callback but let the animation timer take care of the remaining
|
||||
* frame delay time.
|
||||
* </p><p>
|
||||
* This method is somewhat conservative about how much of the frame delay it
|
||||
* subtracts. It uses the same value returned by {@link #getFrameDelay} which by
|
||||
* default is 10ms even though many parts of the system assume 16ms. Consequently,
|
||||
* we might still wait 6ms before posting an animation callback that we want to run
|
||||
* on the next frame, but this is much better than waiting a whole 16ms and likely
|
||||
* missing the deadline.
|
||||
* </p>
|
||||
*
|
||||
* @param delayMillis The original delay time including an assumed frame delay.
|
||||
* @return The adjusted delay time with the assumed frame delay subtracted out.
|
||||
* @hide
|
||||
*/
|
||||
public static long subtractFrameDelay(long delayMillis) {
|
||||
final long frameDelay = sFrameDelay;
|
||||
return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
|
||||
}
|
||||
/**
|
||||
* Posts a callback to run on the next frame.
|
||||
* <p>
|
||||
* The callback runs once then is automatically removed.
|
||||
* </p>
|
||||
*
|
||||
* @param callbackType The callback type.
|
||||
* @param action The callback action to run during the next frame.
|
||||
* @param token The callback token, or null if none.
|
||||
*
|
||||
* @see #removeCallbacks
|
||||
* @hide
|
||||
*/
|
||||
public void postCallback(int callbackType, Runnable action, Object token) {
|
||||
postCallbackDelayed(callbackType, action, token, 0);
|
||||
}
|
||||
/**
|
||||
* Posts a callback to run on the next frame after the specified delay.
|
||||
* <p>
|
||||
* The callback runs once then is automatically removed.
|
||||
* </p>
|
||||
*
|
||||
* @param callbackType The callback type.
|
||||
* @param action The callback action to run during the next frame after the specified delay.
|
||||
* @param token The callback token, or null if none.
|
||||
* @param delayMillis The delay time in milliseconds.
|
||||
*
|
||||
* @see #removeCallback
|
||||
* @hide
|
||||
*/
|
||||
public void postCallbackDelayed(int callbackType,
|
||||
Runnable action, Object token, long delayMillis) {
|
||||
if (action == null) {
|
||||
throw new IllegalArgumentException("action must not be null");
|
||||
}
|
||||
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
|
||||
throw new IllegalArgumentException("callbackType is invalid");
|
||||
}
|
||||
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
|
||||
}
|
||||
private void postCallbackDelayedInternal(int callbackType,
|
||||
Object action, Object token, long delayMillis) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token + ", delayMillis=" + delayMillis);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
final long dueTime = now + delayMillis;
|
||||
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
|
||||
if (dueTime <= now) {
|
||||
scheduleFrameLocked(now);
|
||||
} else {
|
||||
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
|
||||
msg.arg1 = callbackType;
|
||||
msg.setAsynchronous(true);
|
||||
mHandler.sendMessageAtTime(msg, dueTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes callbacks that have the specified action and token.
|
||||
*
|
||||
* @param callbackType The callback type.
|
||||
* @param action The action property of the callbacks to remove, or null to remove
|
||||
* callbacks with any action.
|
||||
* @param token The token property of the callbacks to remove, or null to remove
|
||||
* callbacks with any token.
|
||||
*
|
||||
* @see #postCallback
|
||||
* @see #postCallbackDelayed
|
||||
* @hide
|
||||
*/
|
||||
public void removeCallbacks(int callbackType, Runnable action, Object token) {
|
||||
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
|
||||
throw new IllegalArgumentException("callbackType is invalid");
|
||||
}
|
||||
removeCallbacksInternal(callbackType, action, token);
|
||||
}
|
||||
private void removeCallbacksInternal(int callbackType, Object action, Object token) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "RemoveCallbacks: type=" + callbackType + ", action=" + action + ", token=" + token);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
|
||||
if (action != null && token == null) {
|
||||
mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Posts a frame callback to run on the next frame.
|
||||
* <p>
|
||||
* The callback runs once then is automatically removed.
|
||||
* </p>
|
||||
*
|
||||
* @param callback The frame callback to run during the next frame.
|
||||
*
|
||||
* @see #postFrameCallbackDelayed
|
||||
* @see #removeFrameCallback
|
||||
*/
|
||||
public void postFrameCallback(FrameCallback callback) {
|
||||
postFrameCallbackDelayed(callback, 0);
|
||||
}
|
||||
/**
|
||||
* Posts a frame callback to run on the next frame after the specified delay.
|
||||
* <p>
|
||||
* The callback runs once then is automatically removed.
|
||||
* </p>
|
||||
*
|
||||
* @param callback The frame callback to run during the next frame.
|
||||
* @param delayMillis The delay time in milliseconds.
|
||||
*
|
||||
* @see #postFrameCallback
|
||||
* @see #removeFrameCallback
|
||||
*/
|
||||
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
postCallbackDelayedInternal(CALLBACK_ANIMATION,
|
||||
callback, FRAME_CALLBACK_TOKEN, delayMillis);
|
||||
}
|
||||
/**
|
||||
* Removes a previously posted frame callback.
|
||||
*
|
||||
* @param callback The frame callback to remove.
|
||||
*
|
||||
* @see #postFrameCallback
|
||||
* @see #postFrameCallbackDelayed
|
||||
*/
|
||||
public void removeFrameCallback(FrameCallback callback) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
}
|
||||
removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
|
||||
}
|
||||
/**
|
||||
* Gets the time when the current frame started.
|
||||
* <p>
|
||||
* This method provides the time in nanoseconds when the frame started being rendered.
|
||||
* The frame time provides a stable time base for synchronizing animations
|
||||
* and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
|
||||
* or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
|
||||
* time helps to reduce inter-frame jitter because the frame time is fixed at the time
|
||||
* the frame was scheduled to start, regardless of when the animations or drawing
|
||||
* callback actually runs. All callbacks that run as part of rendering a frame will
|
||||
* observe the same frame time so using the frame time also helps to synchronize effects
|
||||
* that are performed by different callbacks.
|
||||
* </p><p>
|
||||
* Please note that the framework already takes care to process animations and
|
||||
* drawing using the frame time as a stable time base. Most applications should
|
||||
* not need to use the frame time information directly.
|
||||
* </p><p>
|
||||
* This method should only be called from within a callback.
|
||||
* </p>
|
||||
*
|
||||
* @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
|
||||
*
|
||||
* @throws IllegalStateException if no frame is in progress.
|
||||
* @hide
|
||||
*/
|
||||
public long getFrameTime() {
|
||||
return getFrameTimeNanos() / NANOS_PER_MS;
|
||||
}
|
||||
/**
|
||||
* Same as {@link #getFrameTime()} but with nanosecond precision.
|
||||
*
|
||||
* @return The frame start time, in the {@link System#nanoTime()} time base.
|
||||
*
|
||||
* @throws IllegalStateException if no frame is in progress.
|
||||
* @hide
|
||||
*/
|
||||
public long getFrameTimeNanos() {
|
||||
synchronized (mLock) {
|
||||
if (!mCallbacksRunning) {
|
||||
throw new IllegalStateException("This method must only be called as "
|
||||
+ "part of a callback while a frame is in progress.");
|
||||
}
|
||||
return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
|
||||
}
|
||||
}
|
||||
private void scheduleFrameLocked(long now) {
|
||||
if (!mFrameScheduled) {
|
||||
mFrameScheduled = true;
|
||||
final long nextFrameTime = Math.max(
|
||||
mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
|
||||
}
|
||||
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
|
||||
msg.setAsynchronous(true);
|
||||
mHandler.sendMessageAtTime(msg, nextFrameTime);
|
||||
}
|
||||
}
|
||||
void doFrame(long frameTimeNanos, int frame) {
|
||||
final long startNanos;
|
||||
synchronized (mLock) {
|
||||
if (!mFrameScheduled) {
|
||||
return; // no work to do
|
||||
}
|
||||
startNanos = System.nanoTime();
|
||||
final long jitterNanos = startNanos - frameTimeNanos;
|
||||
if (jitterNanos >= mFrameIntervalNanos) {
|
||||
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
|
||||
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
|
||||
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
|
||||
+ "The application may be doing too much work on its main thread.");
|
||||
}
|
||||
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
|
||||
+ "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! "
|
||||
+ "Skipping " + skippedFrames + " frames and setting frame "
|
||||
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
|
||||
}
|
||||
frameTimeNanos = startNanos - lastFrameOffset;
|
||||
}
|
||||
if (frameTimeNanos < mLastFrameTimeNanos) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
|
||||
+ "previously skipped frame. Waiting for next vsync.");
|
||||
}
|
||||
// scheduleVsyncLocked();
|
||||
return;
|
||||
}
|
||||
mFrameScheduled = false;
|
||||
mLastFrameTimeNanos = frameTimeNanos;
|
||||
}
|
||||
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
|
||||
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
|
||||
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
|
||||
if (DEBUG) {
|
||||
final long endNanos = System.nanoTime();
|
||||
Log.d(TAG, "Frame " + frame + ": Finished, took " + (endNanos - startNanos) * 0.000001f + " ms, latency " + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
|
||||
}
|
||||
}
|
||||
void doCallbacks(int callbackType, long frameTimeNanos) {
|
||||
CallbackRecord callbacks;
|
||||
synchronized (mLock) {
|
||||
// We use "now" to determine when callbacks become due because it's possible
|
||||
// for earlier processing phases in a frame to post callbacks that should run
|
||||
// in a following phase, such as an input event that causes an animation to start.
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
|
||||
if (callbacks == null) {
|
||||
return;
|
||||
}
|
||||
mCallbacksRunning = true;
|
||||
}
|
||||
try {
|
||||
for (CallbackRecord c = callbacks; c != null; c = c.next) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
|
||||
}
|
||||
c.run(frameTimeNanos);
|
||||
}
|
||||
} finally {
|
||||
synchronized (mLock) {
|
||||
mCallbacksRunning = false;
|
||||
do {
|
||||
final CallbackRecord next = callbacks.next;
|
||||
recycleCallbackLocked(callbacks);
|
||||
callbacks = next;
|
||||
} while (callbacks != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
void doScheduleCallback(int callbackType) {
|
||||
synchronized (mLock) {
|
||||
if (!mFrameScheduled) {
|
||||
final long now = SystemClock.uptimeMillis();
|
||||
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
|
||||
scheduleFrameLocked(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private boolean isRunningOnLooperThreadLocked() {
|
||||
return Looper.myLooper() == mLooper;
|
||||
}
|
||||
private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
|
||||
CallbackRecord callback = mCallbackPool;
|
||||
if (callback == null) {
|
||||
callback = new CallbackRecord();
|
||||
} else {
|
||||
mCallbackPool = callback.next;
|
||||
callback.next = null;
|
||||
}
|
||||
callback.dueTime = dueTime;
|
||||
callback.action = action;
|
||||
callback.token = token;
|
||||
return callback;
|
||||
}
|
||||
private void recycleCallbackLocked(CallbackRecord callback) {
|
||||
callback.action = null;
|
||||
callback.token = null;
|
||||
callback.next = mCallbackPool;
|
||||
mCallbackPool = callback;
|
||||
}
|
||||
/**
|
||||
* Implement this interface to receive a callback when a new display frame is
|
||||
* being rendered. The callback is invoked on the {@link Looper} thread to
|
||||
* which the {@link Choreographer} is attached.
|
||||
*/
|
||||
public interface FrameCallback {
|
||||
/**
|
||||
* Called when a new display frame is being rendered.
|
||||
* <p>
|
||||
* This method provides the time in nanoseconds when the frame started being rendered.
|
||||
* The frame time provides a stable time base for synchronizing animations
|
||||
* and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
|
||||
* or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
|
||||
* time helps to reduce inter-frame jitter because the frame time is fixed at the time
|
||||
* the frame was scheduled to start, regardless of when the animations or drawing
|
||||
* callback actually runs. All callbacks that run as part of rendering a frame will
|
||||
* observe the same frame time so using the frame time also helps to synchronize effects
|
||||
* that are performed by different callbacks.
|
||||
* </p><p>
|
||||
* Please note that the framework already takes care to process animations and
|
||||
* drawing using the frame time as a stable time base. Most applications should
|
||||
* not need to use the frame time information directly.
|
||||
* </p>
|
||||
*
|
||||
* @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
|
||||
* in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
|
||||
* to convert it to the {@link SystemClock#uptimeMillis()} time base.
|
||||
*/
|
||||
public void doFrame(long frameTimeNanos);
|
||||
}
|
||||
private final class FrameHandler extends Handler {
|
||||
public FrameHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_DO_FRAME:
|
||||
doFrame(System.nanoTime(), 0);
|
||||
break;
|
||||
case MSG_DO_SCHEDULE_VSYNC:
|
||||
Log.d(TAG, "VSYNC not supported!");
|
||||
// doScheduleVsync();
|
||||
break;
|
||||
case MSG_DO_SCHEDULE_CALLBACK:
|
||||
doScheduleCallback(msg.arg1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void postFrameCallbackDelayed(final Choreographer.FrameCallback callback, long delayMillis) {
|
||||
// TODO - do the delay part
|
||||
// NOTE: if we do this synchronously, it gets stuck
|
||||
Thread async = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
callback.doFrame(System.nanoTime());
|
||||
} });
|
||||
async.start();
|
||||
private static final class CallbackRecord {
|
||||
public CallbackRecord next;
|
||||
public long dueTime;
|
||||
public Object action; // Runnable or FrameCallback
|
||||
public Object token;
|
||||
public void run(long frameTimeNanos) {
|
||||
if (token == FRAME_CALLBACK_TOKEN) {
|
||||
((FrameCallback)action).doFrame(frameTimeNanos);
|
||||
} else {
|
||||
((Runnable)action).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
private final class CallbackQueue {
|
||||
private CallbackRecord mHead;
|
||||
public boolean hasDueCallbacksLocked(long now) {
|
||||
return mHead != null && mHead.dueTime <= now;
|
||||
}
|
||||
public CallbackRecord extractDueCallbacksLocked(long now) {
|
||||
CallbackRecord callbacks = mHead;
|
||||
if (callbacks == null || callbacks.dueTime > now) {
|
||||
return null;
|
||||
}
|
||||
CallbackRecord last = callbacks;
|
||||
CallbackRecord next = last.next;
|
||||
while (next != null) {
|
||||
if (next.dueTime > now) {
|
||||
last.next = null;
|
||||
break;
|
||||
}
|
||||
last = next;
|
||||
next = next.next;
|
||||
}
|
||||
mHead = next;
|
||||
return callbacks;
|
||||
}
|
||||
public void addCallbackLocked(long dueTime, Object action, Object token) {
|
||||
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
|
||||
CallbackRecord entry = mHead;
|
||||
if (entry == null) {
|
||||
mHead = callback;
|
||||
return;
|
||||
}
|
||||
if (dueTime < entry.dueTime) {
|
||||
callback.next = entry;
|
||||
mHead = callback;
|
||||
return;
|
||||
}
|
||||
while (entry.next != null) {
|
||||
if (dueTime < entry.next.dueTime) {
|
||||
callback.next = entry.next;
|
||||
break;
|
||||
}
|
||||
entry = entry.next;
|
||||
}
|
||||
entry.next = callback;
|
||||
}
|
||||
public void removeCallbacksLocked(Object action, Object token) {
|
||||
CallbackRecord predecessor = null;
|
||||
for (CallbackRecord callback = mHead; callback != null;) {
|
||||
final CallbackRecord next = callback.next;
|
||||
if ((action == null || callback.action == action) && (token == null || callback.token == token)) {
|
||||
if (predecessor != null) {
|
||||
predecessor.next = next;
|
||||
} else {
|
||||
mHead = next;
|
||||
}
|
||||
recycleCallbackLocked(callback);
|
||||
} else {
|
||||
predecessor = callback;
|
||||
}
|
||||
callback = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
@ -37,7 +38,7 @@ void ALooper_release(ALooper* looper) {
|
|||
_ZNK7android7RefBase9decStrongEPKv(looper, (void*)ALooper_acquire);
|
||||
}
|
||||
|
||||
int _ZN7android6Looper7pollAllEiPiS1_PPv(ALooper *this, int timeoutMillis, int* outFd, int* outEvents, void** outData);
|
||||
int _ZN7android6Looper7pollAllEiPiS1_PPv(ALooper *this, int timeoutMillis, int *outFd, int *outEvents, void **outData);
|
||||
int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData)
|
||||
{
|
||||
ALooper *looper = ALooper_forThread();
|
||||
|
@ -49,12 +50,34 @@ int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outDat
|
|||
return _ZN7android6Looper7pollAllEiPiS1_PPv(looper, timeoutMillis, outFd, outEvents, outData);
|
||||
}
|
||||
|
||||
int _ZN7android6Looper5addFdEiiiPFiiiPvES1_(ALooper *this, int fd, int ident, int events, Looper_callbackFunc callback, void* data);
|
||||
int _ZN7android6Looper8pollOnceEiPiS1_PPv(ALooper *this, int timeoutMillis, int *outFd, int *outEvents, void **outData);
|
||||
int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)
|
||||
{
|
||||
ALooper *looper = ALooper_forThread();
|
||||
if(!looper) {
|
||||
fprintf(stderr, "ALooper_pollAll: ALooper_forThread returned NULL\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return _ZN7android6Looper8pollOnceEiPiS1_PPv(looper, timeoutMillis, outFd, outEvents, outData);
|
||||
}
|
||||
|
||||
int _ZN7android6Looper5addFdEiiiPFiiiPvES1_(ALooper *this, int fd, int ident, int events, Looper_callbackFunc callback, void *data);
|
||||
int ALooper_addFd(ALooper* looper, int fd, int ident, int events, Looper_callbackFunc callback, void* data)
|
||||
{
|
||||
return _ZN7android6Looper5addFdEiiiPFiiiPvES1_(looper, fd, ident, events, callback, data);
|
||||
}
|
||||
|
||||
void _ZN7android6Looper4wakeEv(ALooper *this);
|
||||
void ALooper_wake(ALooper* looper) {
|
||||
_ZN7android6Looper4wakeEv(looper);
|
||||
void ALooper_wake(ALooper *looper)
|
||||
{
|
||||
_ZN7android6Looper4wakeEv(looper);
|
||||
}
|
||||
|
||||
/* this is not part of the android API, but we use it internally */
|
||||
|
||||
bool _ZNK7android6Looper9isPollingEv(ALooper *this);
|
||||
bool ALooper_isPolling(ALooper *looper)
|
||||
{
|
||||
return _ZNK7android6Looper9isPollingEv(looper);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue