Loading viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java +100 −53 Original line number Diff line number Diff line Loading @@ -24,11 +24,13 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.media.permission.SafeCloseable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; Loading @@ -52,6 +54,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; Loading @@ -77,6 +80,10 @@ public abstract class ViewCapture { // Number of frames to keep in memory private final int mMemorySize; // Number of ViewPropertyRef to preallocate per window private final int mInitPoolSize; protected static final int DEFAULT_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. Loading @@ -84,18 +91,16 @@ public abstract class ViewCapture { public static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper()); private final List<WindowListener> mListeners = new ArrayList<>(); private final List<WindowListener> mListeners = Collections.synchronizedList(new ArrayList<>()); protected final Executor mBgExecutor; // Pool used for capturing view tree on the UI thread. private ViewPropertyRef mPool = new ViewPropertyRef(); private boolean mIsEnabled = true; protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) { mMemorySize = memorySize; mBgExecutor = bgExecutor; mBgExecutor.execute(() -> initPool(initPoolSize)); mInitPoolSize = initPoolSize; } public static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) { Loading @@ -104,29 +109,10 @@ public abstract class ViewCapture { return new LooperExecutor(thread.getLooper()); } @UiThread private void addToPool(ViewPropertyRef start, ViewPropertyRef end) { end.next = mPool; mPool = start; } @WorkerThread private void initPool(int initPoolSize) { ViewPropertyRef start = new ViewPropertyRef(); ViewPropertyRef current = start; for (int i = 0; i < initPoolSize; i++) { current.next = new ViewPropertyRef(); current = current.next; } ViewPropertyRef finalCurrent = current; MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent)); } /** * Attaches the ViewCapture to the provided window and returns a handle to detach the listener */ @AnyThread @NonNull public SafeCloseable startCapture(@NonNull Window window) { String title = window.getAttributes().getTitle().toString(); Loading @@ -138,18 +124,25 @@ public abstract class ViewCapture { * Attaches the ViewCapture to the provided window and returns a handle to detach the listener. * Verifies that ViewCapture is enabled before actually attaching an onDrawListener. */ @AnyThread @NonNull public SafeCloseable startCapture(@NonNull View view, @NonNull String name) { WindowListener listener = new WindowListener(view, name); if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot); if (mIsEnabled) { listener.attachToRoot(); } mListeners.add(listener); view.getContext().registerComponentCallbacks(listener); runOnUiThread(() -> view.getContext().registerComponentCallbacks(listener), view); return () -> { if (listener.mRoot != null && listener.mRoot.getContext() != null) { listener.mRoot.getContext().unregisterComponentCallbacks(listener); } mListeners.remove(listener); listener.detachFromRoot(); }; } Loading @@ -164,16 +157,18 @@ public abstract class ViewCapture { * are still technically enabled to allow for dumping. */ @VisibleForTesting @AnyThread public void stopCapture(@NonNull View rootView) { mListeners.forEach(it -> { if (rootView == it.mRoot) { it.mRoot.getViewTreeObserver().removeOnDrawListener(it); runOnUiThread(() -> it.mRoot.getViewTreeObserver().removeOnDrawListener(it), it.mRoot); it.mRoot = null; } }); } @UiThread @AnyThread protected void enableOrDisableWindowListeners(boolean isEnabled) { mIsEnabled = isEnabled; mListeners.forEach(WindowListener::detachFromRoot); Loading Loading @@ -234,6 +229,24 @@ public abstract class ViewCapture { ViewPropertyRef startFlattenedViewTree) { } @AnyThread void runOnUiThread(Runnable action, View view) { if (view == null) { // Corner case. E.g.: the capture is stopped (root view set to null), // but the bg thread is still processing work. Log.i(TAG, "Skipping run on UI thread. Provided view == null."); return; } Handler handlerUi = view.getHandler(); if (handlerUi != null && handlerUi.getLooper().getThread() == Thread.currentThread()) { action.run(); return; } view.post(action); } /** * Once this window listener is attached to a window's root view, it traverses the entire * view tree on the main thread every time onDraw is called. It then saves the state of the view Loading Loading @@ -283,6 +296,8 @@ public abstract class ViewCapture { public View mRoot; public final String name; // Pool used for capturing view tree on the UI thread. private ViewPropertyRef mPool = new ViewPropertyRef(); private final ViewPropertyRef mViewPropertyRef = new ViewPropertyRef(); private int mFrameIndexBg = -1; Loading @@ -297,6 +312,7 @@ public abstract class ViewCapture { WindowListener(View view, String name) { mRoot = view; this.name = name; initPool(mInitPoolSize); } /** Loading @@ -306,6 +322,7 @@ public abstract class ViewCapture { * thread via mExecutor. */ @Override @UiThread public void onDraw() { Trace.beginSection("vc#onDraw"); captureViewTree(mRoot, mViewPropertyRef); Loading Loading @@ -397,7 +414,7 @@ public abstract class ViewCapture { // The compiler will complain about using a non-final variable from // an outer class in a lambda if we pass in 'end' directly. final ViewPropertyRef finalEnd = end; MAIN_EXECUTOR.execute(() -> addToPool(start, finalEnd)); runOnUiThread(() -> addToPool(start, finalEnd), mRoot); break; } end = end.next; Loading @@ -409,6 +426,7 @@ public abstract class ViewCapture { Trace.endSection(); } @WorkerThread private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { int lastFrameIndex = (mFrameIndexBg == 0) ? mMemorySize - 1 : mFrameIndexBg - 1; ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex]; Loading @@ -418,9 +436,41 @@ public abstract class ViewCapture { return viewPropertyRef; } private void initPool(int initPoolSize) { ViewPropertyRef start = new ViewPropertyRef(); ViewPropertyRef current = start; for (int i = 0; i < initPoolSize; i++) { current.next = new ViewPropertyRef(); current = current.next; } ViewPropertyRef finalCurrent = current; addToPool(start, finalCurrent); } private void addToPool(ViewPropertyRef start, ViewPropertyRef end) { end.next = mPool; mPool = start; } @UiThread private ViewPropertyRef getFromPool() { ViewPropertyRef ref = mPool; if (ref != null) { mPool = ref.next; ref.next = null; } else { ref = new ViewPropertyRef(); } return ref; } @AnyThread void attachToRoot() { if (mRoot == null) return; mIsActive = true; runOnUiThread(() -> { if (mRoot.isAttachedToWindow()) { safelyEnableOnDrawListener(); } else { Loading @@ -438,15 +488,18 @@ public abstract class ViewCapture { } }); } }, mRoot); } @AnyThread void detachFromRoot() { mIsActive = false; if (mRoot != null) { mRoot.getViewTreeObserver().removeOnDrawListener(this); runOnUiThread(() -> mRoot.getViewTreeObserver().removeOnDrawListener(this), mRoot); } } @UiThread private void safelyEnableOnDrawListener() { if (mRoot != null) { mRoot.getViewTreeObserver().removeOnDrawListener(this); Loading @@ -470,15 +523,9 @@ public abstract class ViewCapture { return builder.build(); } @UiThread private ViewPropertyRef captureViewTree(View view, ViewPropertyRef start) { ViewPropertyRef ref; if (mPool != null) { ref = mPool; mPool = mPool.next; ref.next = null; } else { ref = new ViewPropertyRef(); } ViewPropertyRef ref = getFromPool(); start.next = ref; if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; Loading Loading
viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java +100 −53 Original line number Diff line number Diff line Loading @@ -24,11 +24,13 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.media.permission.SafeCloseable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; Loading @@ -52,6 +54,7 @@ import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; Loading @@ -77,6 +80,10 @@ public abstract class ViewCapture { // Number of frames to keep in memory private final int mMemorySize; // Number of ViewPropertyRef to preallocate per window private final int mInitPoolSize; protected static final int DEFAULT_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. Loading @@ -84,18 +91,16 @@ public abstract class ViewCapture { public static final LooperExecutor MAIN_EXECUTOR = new LooperExecutor(Looper.getMainLooper()); private final List<WindowListener> mListeners = new ArrayList<>(); private final List<WindowListener> mListeners = Collections.synchronizedList(new ArrayList<>()); protected final Executor mBgExecutor; // Pool used for capturing view tree on the UI thread. private ViewPropertyRef mPool = new ViewPropertyRef(); private boolean mIsEnabled = true; protected ViewCapture(int memorySize, int initPoolSize, Executor bgExecutor) { mMemorySize = memorySize; mBgExecutor = bgExecutor; mBgExecutor.execute(() -> initPool(initPoolSize)); mInitPoolSize = initPoolSize; } public static LooperExecutor createAndStartNewLooperExecutor(String name, int priority) { Loading @@ -104,29 +109,10 @@ public abstract class ViewCapture { return new LooperExecutor(thread.getLooper()); } @UiThread private void addToPool(ViewPropertyRef start, ViewPropertyRef end) { end.next = mPool; mPool = start; } @WorkerThread private void initPool(int initPoolSize) { ViewPropertyRef start = new ViewPropertyRef(); ViewPropertyRef current = start; for (int i = 0; i < initPoolSize; i++) { current.next = new ViewPropertyRef(); current = current.next; } ViewPropertyRef finalCurrent = current; MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent)); } /** * Attaches the ViewCapture to the provided window and returns a handle to detach the listener */ @AnyThread @NonNull public SafeCloseable startCapture(@NonNull Window window) { String title = window.getAttributes().getTitle().toString(); Loading @@ -138,18 +124,25 @@ public abstract class ViewCapture { * Attaches the ViewCapture to the provided window and returns a handle to detach the listener. * Verifies that ViewCapture is enabled before actually attaching an onDrawListener. */ @AnyThread @NonNull public SafeCloseable startCapture(@NonNull View view, @NonNull String name) { WindowListener listener = new WindowListener(view, name); if (mIsEnabled) MAIN_EXECUTOR.execute(listener::attachToRoot); if (mIsEnabled) { listener.attachToRoot(); } mListeners.add(listener); view.getContext().registerComponentCallbacks(listener); runOnUiThread(() -> view.getContext().registerComponentCallbacks(listener), view); return () -> { if (listener.mRoot != null && listener.mRoot.getContext() != null) { listener.mRoot.getContext().unregisterComponentCallbacks(listener); } mListeners.remove(listener); listener.detachFromRoot(); }; } Loading @@ -164,16 +157,18 @@ public abstract class ViewCapture { * are still technically enabled to allow for dumping. */ @VisibleForTesting @AnyThread public void stopCapture(@NonNull View rootView) { mListeners.forEach(it -> { if (rootView == it.mRoot) { it.mRoot.getViewTreeObserver().removeOnDrawListener(it); runOnUiThread(() -> it.mRoot.getViewTreeObserver().removeOnDrawListener(it), it.mRoot); it.mRoot = null; } }); } @UiThread @AnyThread protected void enableOrDisableWindowListeners(boolean isEnabled) { mIsEnabled = isEnabled; mListeners.forEach(WindowListener::detachFromRoot); Loading Loading @@ -234,6 +229,24 @@ public abstract class ViewCapture { ViewPropertyRef startFlattenedViewTree) { } @AnyThread void runOnUiThread(Runnable action, View view) { if (view == null) { // Corner case. E.g.: the capture is stopped (root view set to null), // but the bg thread is still processing work. Log.i(TAG, "Skipping run on UI thread. Provided view == null."); return; } Handler handlerUi = view.getHandler(); if (handlerUi != null && handlerUi.getLooper().getThread() == Thread.currentThread()) { action.run(); return; } view.post(action); } /** * Once this window listener is attached to a window's root view, it traverses the entire * view tree on the main thread every time onDraw is called. It then saves the state of the view Loading Loading @@ -283,6 +296,8 @@ public abstract class ViewCapture { public View mRoot; public final String name; // Pool used for capturing view tree on the UI thread. private ViewPropertyRef mPool = new ViewPropertyRef(); private final ViewPropertyRef mViewPropertyRef = new ViewPropertyRef(); private int mFrameIndexBg = -1; Loading @@ -297,6 +312,7 @@ public abstract class ViewCapture { WindowListener(View view, String name) { mRoot = view; this.name = name; initPool(mInitPoolSize); } /** Loading @@ -306,6 +322,7 @@ public abstract class ViewCapture { * thread via mExecutor. */ @Override @UiThread public void onDraw() { Trace.beginSection("vc#onDraw"); captureViewTree(mRoot, mViewPropertyRef); Loading Loading @@ -397,7 +414,7 @@ public abstract class ViewCapture { // The compiler will complain about using a non-final variable from // an outer class in a lambda if we pass in 'end' directly. final ViewPropertyRef finalEnd = end; MAIN_EXECUTOR.execute(() -> addToPool(start, finalEnd)); runOnUiThread(() -> addToPool(start, finalEnd), mRoot); break; } end = end.next; Loading @@ -409,6 +426,7 @@ public abstract class ViewCapture { Trace.endSection(); } @WorkerThread private @Nullable ViewPropertyRef findInLastFrame(int hashCode) { int lastFrameIndex = (mFrameIndexBg == 0) ? mMemorySize - 1 : mFrameIndexBg - 1; ViewPropertyRef viewPropertyRef = mNodesBg[lastFrameIndex]; Loading @@ -418,9 +436,41 @@ public abstract class ViewCapture { return viewPropertyRef; } private void initPool(int initPoolSize) { ViewPropertyRef start = new ViewPropertyRef(); ViewPropertyRef current = start; for (int i = 0; i < initPoolSize; i++) { current.next = new ViewPropertyRef(); current = current.next; } ViewPropertyRef finalCurrent = current; addToPool(start, finalCurrent); } private void addToPool(ViewPropertyRef start, ViewPropertyRef end) { end.next = mPool; mPool = start; } @UiThread private ViewPropertyRef getFromPool() { ViewPropertyRef ref = mPool; if (ref != null) { mPool = ref.next; ref.next = null; } else { ref = new ViewPropertyRef(); } return ref; } @AnyThread void attachToRoot() { if (mRoot == null) return; mIsActive = true; runOnUiThread(() -> { if (mRoot.isAttachedToWindow()) { safelyEnableOnDrawListener(); } else { Loading @@ -438,15 +488,18 @@ public abstract class ViewCapture { } }); } }, mRoot); } @AnyThread void detachFromRoot() { mIsActive = false; if (mRoot != null) { mRoot.getViewTreeObserver().removeOnDrawListener(this); runOnUiThread(() -> mRoot.getViewTreeObserver().removeOnDrawListener(this), mRoot); } } @UiThread private void safelyEnableOnDrawListener() { if (mRoot != null) { mRoot.getViewTreeObserver().removeOnDrawListener(this); Loading @@ -470,15 +523,9 @@ public abstract class ViewCapture { return builder.build(); } @UiThread private ViewPropertyRef captureViewTree(View view, ViewPropertyRef start) { ViewPropertyRef ref; if (mPool != null) { ref = mPool; mPool = mPool.next; ref.next = null; } else { ref = new ViewPropertyRef(); } ViewPropertyRef ref = getFromPool(); start.next = ref; if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup) view; Loading