Add new ViewDebug APIs to profile the event queue.
Change-Id: I225bf288780b0244f459316e2765cfa29cd22c89
diff --git a/api/current.txt b/api/current.txt
index 3503fb3..7c78c06 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14035,6 +14035,7 @@
method public void dispatchMessage(android.os.Message);
method public final void dump(android.util.Printer, java.lang.String);
method public final android.os.Looper getLooper();
+ method public java.lang.String getMessageName(android.os.Message);
method public void handleMessage(android.os.Message);
method public final boolean hasMessages(int);
method public final boolean hasMessages(int, java.lang.Object);
@@ -14104,13 +14105,13 @@
public class Looper {
method public void dump(android.util.Printer, java.lang.String);
- method public static final synchronized android.os.Looper getMainLooper();
+ method public static synchronized android.os.Looper getMainLooper();
method public java.lang.Thread getThread();
- method public static final void loop();
- method public static final android.os.Looper myLooper();
- method public static final android.os.MessageQueue myQueue();
- method public static final void prepare();
- method public static final void prepareMainLooper();
+ method public static void loop();
+ method public static android.os.Looper myLooper();
+ method public static android.os.MessageQueue myQueue();
+ method public static void prepare();
+ method public static void prepareMainLooper();
method public void quit();
method public void setMessageLogging(android.util.Printer);
}
@@ -22642,8 +22643,10 @@
ctor public ViewDebug();
method public static void dumpCapturedView(java.lang.String, java.lang.Object);
method public static void startHierarchyTracing(java.lang.String, android.view.View);
+ method public static void startLooperProfiling(java.io.File);
method public static void startRecyclerTracing(java.lang.String, android.view.View);
method public static void stopHierarchyTracing();
+ method public static void stopLooperProfiling();
method public static void stopRecyclerTracing();
method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 165e438..cd39d5c 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -169,6 +169,21 @@
}
/**
+ * Returns a string representing the name of the specified message.
+ * The default implementation will either return the class name of the
+ * message callback if any, or the hexadecimal representation of the
+ * message "what" field.
+ *
+ * @param message The message whose name is being queried
+ */
+ public String getMessageName(Message message) {
+ if (message.callback != null) {
+ return message.callback.getClass().getName();
+ }
+ return "0x" + Integer.toHexString(message.what);
+ }
+
+ /**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index c0be664..720e802b 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -52,7 +52,6 @@
*/
public class Looper {
private static final String TAG = "Looper";
- private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
// sThreadLocal.get() will return null unless you've called prepare().
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@@ -70,7 +69,7 @@
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
- public static final void prepare() {
+ public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
@@ -83,7 +82,7 @@
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
- public static final void prepareMainLooper() {
+ public static void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
myLooper().mQueue.mQuitAllowed = false;
@@ -95,7 +94,7 @@
/** Returns the application's main looper, which lives in the main thread of the application.
*/
- public synchronized static final Looper getMainLooper() {
+ public synchronized static Looper getMainLooper() {
return mMainLooper;
}
@@ -103,7 +102,7 @@
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
- public static final void loop() {
+ public static void loop() {
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
@@ -122,20 +121,36 @@
// No target is a magic identifier for the quit message.
return;
}
- if (me.mLogging != null) me.mLogging.println(
- ">>>>> Dispatching to " + msg.target + " "
- + msg.callback + ": " + msg.what
- );
+
+ long wallStart = 0;
+ long threadStart = 0;
+
+ // 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);
+ wallStart = System.currentTimeMillis();
+ threadStart = SystemClock.currentThreadTimeMillis();
+ }
+
msg.target.dispatchMessage(msg);
- if (me.mLogging != null) me.mLogging.println(
- "<<<<< Finished to " + msg.target + " "
- + msg.callback);
-
+
+ if (logging != null) {
+ long wallTime = System.currentTimeMillis() - wallStart;
+ long threadTime = SystemClock.currentThreadTimeMillis() - threadStart;
+
+ logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
+ if (logging instanceof Profiler) {
+ ((Profiler) logging).profile(msg, wallStart, wallTime, threadTime);
+ }
+ }
+
// 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("Looper", "Thread identity changed from 0x"
+ Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
@@ -151,7 +166,7 @@
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
- public static final Looper myLooper() {
+ public static Looper myLooper() {
return sThreadLocal.get();
}
@@ -173,7 +188,7 @@
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
- public static final MessageQueue myQueue() {
+ public static MessageQueue myQueue() {
return myLooper().mQueue;
}
@@ -225,23 +240,13 @@
}
public String toString() {
- return "Looper{"
- + Integer.toHexString(System.identityHashCode(this))
- + "}";
+ return "Looper{" + Integer.toHexString(System.identityHashCode(this)) + "}";
}
- static class HandlerException extends Exception {
-
- HandlerException(Message message, Throwable cause) {
- super(createMessage(cause), cause);
- }
-
- static String createMessage(Throwable cause) {
- String causeMsg = cause.getMessage();
- if (causeMsg == null) {
- causeMsg = cause.toString();
- }
- return causeMsg;
- }
+ /**
+ * @hide
+ */
+ public static interface Profiler {
+ void profile(Message message, long wallStart, long wallTime, long threadTime);
}
}
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index d539a03..ac73611 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -2209,6 +2209,62 @@
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;
@Override
+ public String getMessageName(Message message) {
+ switch (message.what) {
+ case DO_TRAVERSAL:
+ return "DO_TRAVERSAL";
+ case DIE:
+ return "DIE";
+ case RESIZED:
+ return "RESIZED";
+ case RESIZED_REPORT:
+ return "RESIZED_REPORT";
+ case WINDOW_FOCUS_CHANGED:
+ return "WINDOW_FOCUS_CHANGED";
+ case DISPATCH_KEY:
+ return "DISPATCH_KEY";
+ case DISPATCH_POINTER:
+ return "DISPATCH_POINTER";
+ case DISPATCH_TRACKBALL:
+ return "DISPATCH_TRACKBALL";
+ case DISPATCH_APP_VISIBILITY:
+ return "DISPATCH_APP_VISIBILITY";
+ case DISPATCH_GET_NEW_SURFACE:
+ return "DISPATCH_GET_NEW_SURFACE";
+ case FINISHED_EVENT:
+ return "FINISHED_EVENT";
+ case DISPATCH_KEY_FROM_IME:
+ return "DISPATCH_KEY_FROM_IME";
+ case FINISH_INPUT_CONNECTION:
+ return "FINISH_INPUT_CONNECTION";
+ case CHECK_FOCUS:
+ return "CHECK_FOCUS";
+ case CLOSE_SYSTEM_DIALOGS:
+ return "CLOSE_SYSTEM_DIALOGS";
+ case DISPATCH_DRAG_EVENT:
+ return "DISPATCH_DRAG_EVENT";
+ case DISPATCH_DRAG_LOCATION_EVENT:
+ return "DISPATCH_DRAG_LOCATION_EVENT";
+ case DISPATCH_SYSTEM_UI_VISIBILITY:
+ return "DISPATCH_SYSTEM_UI_VISIBILITY";
+ case DISPATCH_GENERIC_MOTION:
+ return "DISPATCH_GENERIC_MOTION";
+ case UPDATE_CONFIGURATION:
+ return "UPDATE_CONFIGURATION";
+ case DO_PERFORM_ACCESSIBILITY_ACTION:
+ return "DO_PERFORM_ACCESSIBILITY_ACTION";
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+ return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
+ return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT:
+ return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT";
+
+ }
+ return super.getMessageName(message);
+ }
+
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case View.AttachInfo.INVALIDATE_MSG:
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index f014070..f7f5a21 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -16,41 +16,45 @@
package android.view;
-import android.util.Log;
-import android.util.DisplayMetrics;
-import android.content.res.Resources;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.os.Environment;
import android.os.Debug;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Printer;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
+import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
-import java.io.FileOutputStream;
-import java.io.DataOutputStream;
-import java.io.OutputStreamWriter;
-import java.io.BufferedOutputStream;
import java.io.OutputStream;
-import java.util.List;
-import java.util.LinkedList;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.lang.annotation.Target;
+import java.io.OutputStreamWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -106,13 +110,6 @@
public static final boolean DEBUG_PROFILE_LAYOUT = false;
/**
- * Profiles real fps (times between draws) and displays the result.
- *
- * @hide
- */
- public static final boolean DEBUG_SHOW_FPS = false;
-
- /**
* Enables detailed logging of drag/drop operations.
* @hide
*/
@@ -396,6 +393,9 @@
private static List<RecyclerTrace> sRecyclerTraces;
private static String sRecyclerTracePrefix;
+ private static final ThreadLocal<LooperProfiler> sLooperProfilerStorage =
+ new ThreadLocal<LooperProfiler>();
+
/**
* Returns the number of instanciated Views.
*
@@ -419,6 +419,124 @@
}
/**
+ * Starts profiling the looper associated with the current thread.
+ * You must call {@link #stopLooperProfiling} to end profiling
+ * and obtain the traces. Both methods must be invoked on the
+ * same thread.
+ *
+ * @param traceFile The path where to write the looper traces
+ *
+ * @see #stopLooperProfiling()
+ */
+ public static void startLooperProfiling(File traceFile) {
+ if (sLooperProfilerStorage.get() == null) {
+ LooperProfiler profiler = new LooperProfiler(traceFile);
+ sLooperProfilerStorage.set(profiler);
+ Looper.myLooper().setMessageLogging(profiler);
+ }
+ }
+
+ /**
+ * Stops profiling the looper associated with the current thread.
+ *
+ * @see #startLooperProfiling(java.io.File)
+ */
+ public static void stopLooperProfiling() {
+ LooperProfiler profiler = sLooperProfilerStorage.get();
+ if (profiler != null) {
+ sLooperProfilerStorage.remove();
+ Looper.myLooper().setMessageLogging(null);
+ profiler.save();
+ }
+ }
+
+ private static class LooperProfiler implements Looper.Profiler, Printer {
+ private static final int LOOPER_PROFILER_VERSION = 1;
+
+ private static final String LOG_TAG = "LooperProfiler";
+
+ private final ArrayList<Entry> mTraces = new ArrayList<Entry>(512);
+ private final File mTraceFile;
+
+ public LooperProfiler(File traceFile) {
+ mTraceFile = traceFile;
+ }
+
+ @Override
+ public void println(String x) {
+ // Ignore messages
+ }
+
+ @Override
+ public void profile(Message message, long wallStart, long wallTime, long threadTime) {
+ Entry entry = new Entry();
+ entry.messageId = message.what;
+ entry.name = message.getTarget().getMessageName(message);
+ entry.wallStart = wallStart;
+ entry.wallTime = wallTime;
+ entry.threadTime = threadTime;
+
+ mTraces.add(entry);
+ }
+
+ void save() {
+ // Don't block the UI thread
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ saveTraces();
+ }
+ }, "LooperProfiler[" + mTraceFile + "]").start();
+ }
+
+ private void saveTraces() {
+ FileOutputStream fos;
+ try {
+ fos = new FileOutputStream(mTraceFile);
+ } catch (FileNotFoundException e) {
+ Log.e(LOG_TAG, "Could not open trace file: " + mTraceFile);
+ return;
+ }
+
+ DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
+
+ try {
+ out.writeInt(LOOPER_PROFILER_VERSION);
+ out.writeInt(mTraces.size());
+ for (Entry entry : mTraces) {
+ saveTrace(entry, out);
+ }
+
+ Log.d(LOG_TAG, "Looper traces ready: " + mTraceFile);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not write trace file: ", e);
+ } finally {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private void saveTrace(Entry entry, DataOutputStream out) throws IOException {
+ out.writeInt(entry.messageId);
+ out.writeUTF(entry.name);
+ out.writeLong(entry.wallStart);
+ out.writeLong(entry.wallTime);
+ out.writeLong(entry.threadTime);
+ }
+
+ static class Entry {
+ int messageId;
+ String name;
+ long wallStart;
+ long wallTime;
+ long threadTime;
+ }
+ }
+
+ /**
* Outputs a trace to the currently opened recycler traces. The trace records the type of
* recycler action performed on the supplied view as well as a number of parameters.
*
diff --git a/tests/HwAccelerationTest/res/layout/list_activity.xml b/tests/HwAccelerationTest/res/layout/list_activity.xml
index 6bba370..1a5d3d9 100644
--- a/tests/HwAccelerationTest/res/layout/list_activity.xml
+++ b/tests/HwAccelerationTest/res/layout/list_activity.xml
@@ -30,8 +30,10 @@
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="3dip"
+
+ android:onClick="startProfiling"
- android:text="Add" />
+ android:text="Start" />
<Button
android:layout_width="0dip"
@@ -39,8 +41,10 @@
android:layout_height="wrap_content"
android:layout_marginLeft="3dip"
android:layout_marginRight="10dip"
+
+ android:onClick="stopProfiling"
- android:text="Remove" />
+ android:text="Stop" />
</LinearLayout>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
index 8fd4f6b..1493ab9 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
@@ -20,15 +20,19 @@
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Environment;
import android.util.DisplayMetrics;
import android.view.ContextMenu;
import android.view.View;
+import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
+import java.io.File;
+
@SuppressWarnings({"UnusedDeclaration"})
public class ListActivity extends Activity {
private static final String[] DATA_LIST = {
@@ -87,6 +91,15 @@
registerForContextMenu(list);
}
+
+ public void startProfiling(View v) {
+ ViewDebug.startLooperProfiling(new File(Environment.getExternalStorageDirectory(),
+ "looper.trace"));
+ }
+
+ public void stopProfiling(View v) {
+ ViewDebug.stopLooperProfiling();
+ }
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {