diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e55321b2bf..761f198dad 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity 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 cf9ea699d9..cf4e84a84b 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.ViewGroup; 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 com.android.launcher3.view.ViewCaptureData.ViewNode; 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 @@ public class ViewCapture implements OnDrawListener { 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 task = new FutureTask<>(this::dumpToProtoUI); - if (Looper.myLooper() == handler.getLooper()) { - task.run(); - } else { - handler.post(task); - } + Future 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 @@ public class ViewCapture implements OnDrawListener { } } - @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(); + } + 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; } - return result; } - 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 class ViewCapture implements OnDrawListener { 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 @@ public class ViewCapture implements OnDrawListener { } 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 @@ public class ViewCapture implements OnDrawListener { .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; } }