Merge "Optimizing View capture logic" into tm-qpr-dev
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e55321b..761f198 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1494,7 +1494,7 @@
root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
}
mViewCapture = new ViewCapture(root);
- root.getViewTreeObserver().addOnDrawListener(mViewCapture);
+ mViewCapture.attach();
}
}
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java
index cf9ea69..cf4e84a 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -15,10 +15,11 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.res.Resources;
import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
+import android.os.Message;
import android.os.Trace;
import android.util.Base64;
import android.util.Base64OutputStream;
@@ -28,6 +29,7 @@
import android.view.ViewTreeObserver.OnDrawListener;
import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.view.ViewCaptureData.ExportedData;
import com.android.launcher3.view.ViewCaptureData.FrameData;
@@ -36,7 +38,7 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.OutputStream;
-import java.util.concurrent.FutureTask;
+import java.util.concurrent.Future;
/**
* Utility class for capturing view data every frame
@@ -45,49 +47,132 @@
private static final String TAG = "ViewCapture";
+ // Number of frames to keep in memory
private static final int MEMORY_SIZE = 2000;
+ // Initial size of the reference pool. This is at least be 5 * total number of views in
+ // Launcher. This allows the first free frames avoid object allocation during view capture.
+ private static final int INIT_POOL_SIZE = 300;
private final View mRoot;
- private final long[] mFrameTimes = new long[MEMORY_SIZE];
- private final Node[] mNodes = new Node[MEMORY_SIZE];
+ private final Resources mResources;
- private int mFrameIndex = -1;
+ private final Handler mHandler;
+ private final ViewRef mViewRef = new ViewRef();
+
+ private int mFrameIndexBg = -1;
+ private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
+ private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+
+ // Pool used for capturing view tree on the UI thread.
+ private ViewRef mPool = new ViewRef();
/**
* @param root the root view for the capture data
*/
public ViewCapture(View root) {
mRoot = root;
+ mResources = root.getResources();
+ mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg);
+ }
+
+ /**
+ * Attaches the ViewCapture to the root
+ */
+ public void attach() {
+ mHandler.post(this::initPool);
}
@Override
public void onDraw() {
Trace.beginSection("view_capture");
- long now = SystemClock.elapsedRealtimeNanos();
-
- mFrameIndex++;
- if (mFrameIndex >= MEMORY_SIZE) {
- mFrameIndex = 0;
- }
- mFrameTimes[mFrameIndex] = now;
- mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]);
+ captureViewTree(mRoot, mViewRef);
+ Message m = Message.obtain(mHandler);
+ m.obj = mViewRef.next;
+ mHandler.sendMessage(m);
Trace.endSection();
}
/**
+ * Captures the View property on the background thread, and transfer all the ViewRef objects
+ * back to the pool
+ */
+ @WorkerThread
+ private boolean captureViewPropertiesBg(Message msg) {
+ ViewRef start = (ViewRef) msg.obj;
+ long time = msg.getWhen();
+ if (start == null) {
+ return false;
+ }
+ mFrameIndexBg++;
+ if (mFrameIndexBg >= MEMORY_SIZE) {
+ mFrameIndexBg = 0;
+ }
+ mFrameTimesBg[mFrameIndexBg] = time;
+
+ ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
+
+ ViewPropertyRef result = null;
+ ViewPropertyRef resultEnd = null;
+
+ ViewRef current = start;
+ ViewRef last = start;
+ while (current != null) {
+ ViewPropertyRef propertyRef = recycle;
+ if (propertyRef == null) {
+ propertyRef = new ViewPropertyRef();
+ } else {
+ recycle = recycle.next;
+ propertyRef.next = null;
+ }
+
+ propertyRef.transfer(current);
+ last = current;
+ current = current.next;
+
+ if (result == null) {
+ result = propertyRef;
+ resultEnd = result;
+ } else {
+ resultEnd.next = propertyRef;
+ resultEnd = propertyRef;
+ }
+ }
+ mNodesBg[mFrameIndexBg] = result;
+ ViewRef end = last;
+ Executors.MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+ return true;
+ }
+
+ @UiThread
+ private void addToPool(ViewRef start, ViewRef end) {
+ end.next = mPool;
+ mPool = start;
+ }
+
+ @WorkerThread
+ private void initPool() {
+ ViewRef start = new ViewRef();
+ ViewRef current = start;
+
+ for (int i = 0; i < INIT_POOL_SIZE; i++) {
+ current.next = new ViewRef();
+ current = current.next;
+ }
+
+ ViewRef end = current;
+ Executors.MAIN_EXECUTOR.execute(() -> {
+ addToPool(start, end);
+ if (mRoot.isAttachedToWindow()) {
+ mRoot.getViewTreeObserver().addOnDrawListener(this);
+ }
+ });
+ }
+
+ /**
* Creates a proto of all the data captured so far.
*/
public void dump(FileDescriptor out) {
- Handler handler = mRoot.getHandler();
- if (handler == null) {
- handler = Executors.MAIN_EXECUTOR.getHandler();
- }
- FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
- if (Looper.myLooper() == handler.getLooper()) {
- task.run();
- } else {
- handler.post(task);
- }
+ Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);
try (OutputStream os = new FileOutputStream(out)) {
ExportedData data = task.get();
Base64OutputStream encodedOS = new Base64OutputStream(os,
@@ -100,70 +185,53 @@
}
}
- @UiThread
- private ExportedData dumpToProtoUI() {
+ @WorkerThread
+ private ExportedData dumpToProto() {
ExportedData.Builder dataBuilder = ExportedData.newBuilder();
- Resources res = mRoot.getResources();
+ Resources res = mResources;
- int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE;
+ int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE;
for (int i = size - 1; i >= 0; i--) {
- int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE;
+ int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE;
+ ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
+ mNodesBg[index].toProto(res, nodeBuilder);
dataBuilder.addFrameData(FrameData.newBuilder()
- .setNode(mNodes[index].toProto(res))
- .setTimestamp(mFrameTimes[index]));
+ .setNode(nodeBuilder)
+ .setTimestamp(mFrameTimesBg[index]));
}
return dataBuilder.build();
}
- private Node captureView(View view, Node recycle) {
- Node result = recycle == null ? new Node() : recycle;
-
- result.clazz = view.getClass();
- result.hashCode = view.hashCode();
- result.id = view.getId();
- result.left = view.getLeft();
- result.top = view.getTop();
- result.right = view.getRight();
- result.bottom = view.getBottom();
- result.scrollX = view.getScrollX();
- result.scrollY = view.getScrollY();
-
- result.translateX = view.getTranslationX();
- result.translateY = view.getTranslationY();
- result.scaleX = view.getScaleX();
- result.scaleY = view.getScaleY();
- result.alpha = view.getAlpha();
-
- result.visibility = view.getVisibility();
- result.willNotDraw = view.willNotDraw();
-
- if (view instanceof ViewGroup) {
- ViewGroup parent = (ViewGroup) view;
- result.clipChildren = parent.getClipChildren();
- int childCount = parent.getChildCount();
- if (childCount == 0) {
- result.children = null;
- } else {
- result.children = captureView(parent.getChildAt(0), result.children);
- Node lastChild = result.children;
- for (int i = 1; i < childCount; i++) {
- lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling);
- lastChild = lastChild.sibling;
- }
- lastChild.sibling = null;
- }
+ private ViewRef captureViewTree(View view, ViewRef start) {
+ ViewRef ref;
+ if (mPool != null) {
+ ref = mPool;
+ mPool = mPool.next;
+ ref.next = null;
} else {
- result.clipChildren = false;
- result.children = null;
+ ref = new ViewRef();
}
- return result;
+ ref.view = view;
+ start.next = ref;
+ if (view instanceof ViewGroup) {
+ ViewRef result = ref;
+ ViewGroup parent = (ViewGroup) view;
+ int childCount = ref.childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ result = captureViewTree(parent.getChildAt(i), result);
+ }
+ return result;
+ } else {
+ ref.childCount = 0;
+ return ref;
+ }
}
- private static class Node {
-
+ private static class ViewPropertyRef {
// We store reference in memory to avoid generating and storing too many strings
public Class clazz;
public int hashCode;
+ public int childCount = 0;
public int id;
public int left, top, right, bottom;
@@ -177,10 +245,41 @@
public boolean willNotDraw;
public boolean clipChildren;
- public Node sibling;
- public Node children;
+ public ViewPropertyRef next;
- public ViewNode toProto(Resources res) {
+ public void transfer(ViewRef viewRef) {
+ childCount = viewRef.childCount;
+
+ View view = viewRef.view;
+ viewRef.view = null;
+
+ clazz = view.getClass();
+ hashCode = view.hashCode();
+ id = view.getId();
+ left = view.getLeft();
+ top = view.getTop();
+ right = view.getRight();
+ bottom = view.getBottom();
+ scrollX = view.getScrollX();
+ scrollY = view.getScrollY();
+
+ translateX = view.getTranslationX();
+ translateY = view.getTranslationY();
+ scaleX = view.getScaleX();
+ scaleY = view.getScaleY();
+ alpha = view.getAlpha();
+
+ visibility = view.getVisibility();
+ willNotDraw = view.willNotDraw();
+ }
+
+ /**
+ * Converts the data to the proto representation and returns the next property ref
+ * at the end of the iteration.
+ * @param res
+ * @return
+ */
+ public ViewPropertyRef toProto(Resources res, ViewNode.Builder outBuilder) {
String resolvedId;
if (id >= 0) {
try {
@@ -191,9 +290,7 @@
} else {
resolvedId = "NO_ID";
}
-
- ViewNode.Builder result = ViewNode.newBuilder()
- .setClassname(clazz.getName() + "@" + hashCode)
+ outBuilder.setClassname(clazz.getName() + "@" + hashCode)
.setId(resolvedId)
.setLeft(left)
.setTop(top)
@@ -207,13 +304,20 @@
.setVisibility(visibility)
.setWillNotDraw(willNotDraw)
.setClipChildren(clipChildren);
- Node child = children;
- while (child != null) {
- result.addChildren(child.toProto(res));
- child = child.sibling;
- }
- return result.build();
- }
+ ViewPropertyRef result = next;
+ for (int i = 0; (i < childCount) && (result != null); i++) {
+ ViewNode.Builder childBuilder = ViewNode.newBuilder();
+ result = result.toProto(res, childBuilder);
+ outBuilder.addChildren(childBuilder);
+ }
+ return result;
+ }
+ }
+
+ private static class ViewRef {
+ public View view;
+ public int childCount = 0;
+ public ViewRef next;
}
}