Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee3ef1e9 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

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

parents 28d354c9 a38aa060
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1494,7 +1494,7 @@ public class Launcher extends StatefulActivity<LauncherState>
                root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
            }
            mViewCapture = new ViewCapture(root);
            root.getViewTreeObserver().addOnDrawListener(mViewCapture);
            mViewCapture.attach();
        }
    }

+184 −80
Original line number Diff line number Diff line
@@ -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 final Handler mHandler;
    private final ViewRef mViewRef = new ViewRef();

    private int mFrameIndex = -1;
    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();
    }

    /**
     * Creates a proto of all the data captured so far.
     * Captures the View property on the background thread, and transfer all the ViewRef objects
     * back to the pool
     */
    public void dump(FileDescriptor out) {
        Handler handler = mRoot.getHandler();
        if (handler == null) {
            handler = Executors.MAIN_EXECUTOR.getHandler();
    @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;
            }
        FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
        if (Looper.myLooper() == handler.getLooper()) {
            task.run();

            propertyRef.transfer(current);
            last = current;
            current = current.next;

            if (result == null) {
                result = propertyRef;
                resultEnd = result;
            } else {
            handler.post(task);
                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) {
        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;
    private ViewRef captureViewTree(View view, ViewRef start) {
        ViewRef ref;
        if (mPool != null) {
            ref = mPool;
            mPool = mPool.next;
            ref.next = 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;
            ref = new ViewRef();
        }
                lastChild.sibling = null;
        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 {
            result.clipChildren = false;
            result.children = null;
            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 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();
        }

        public ViewNode toProto(Resources res) {
        /**
         * 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;

            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;
        }
            return result.build();
    }

    private static class ViewRef {
        public View view;
        public int childCount = 0;
        public ViewRef next;
    }
}