Merge "Optimizing View capture logic" into tm-qpr-dev am: ee3ef1e991
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/19572602 Change-Id: I3a1db7ee56c1f4f6453942baa08af6fd862da37b Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
|
||||
}
|
||||
mViewCapture = new ViewCapture(root);
|
||||
root.getViewTreeObserver().addOnDrawListener(mViewCapture);
|
||||
mViewCapture.attach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user