Merge "Optimizing View capture logic" into tm-qpr-dev

This commit is contained in:
Sunny Goyal
2022-08-12 17:27:22 +00:00
committed by Android (Google) Code Review
2 changed files with 191 additions and 87 deletions
+1 -1
View File
@@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity<LauncherState>
root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
}
mViewCapture = new ViewCapture(root);
root.getViewTreeObserver().addOnDrawListener(mViewCapture);
mViewCapture.attach();
}
}
+190 -86
View File
@@ -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<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 @@ 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;
}
}