diff options
author | John Reck <jreck@google.com> | 2019-10-03 13:20:08 -0700 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-16 00:27:20 +0000 |
commit | 73e6f19b4f187ae954c828ead2c4932656bb103a (patch) | |
tree | 892ab98cd0b75a43c7193ec2aed45fe43a9263ba | |
parent | f67faf9eb24af28e773b46b0e5c30e8cb0905648 (diff) | |
download | base-73e6f19b4f187ae954c828ead2c4932656bb103a.tar.gz |
Ensure SKP serialization occurs on RenderThread
Instead of doing lazy serialization of SKP on the
background executor serialize to a byte[] immediately
at callback invocation. This ensures no potential
for later mutations, race conditions, or wrong-thread issues
at the expense of potentially impacting app rendering performance.
However it seems preferable for a debug-only tool to be a slow
instead of very crashy.
Bug: 141772764
Test: test app
Change-Id: I3316d49970b96f1c59bb0a28ff7335db608e539e
(cherry picked from commit 8d0da1a6c5271d7f803b665d6a0784a2ca74d3c8)
-rw-r--r-- | core/java/android/view/ViewDebug.java | 101 |
1 files changed, 89 insertions, 12 deletions
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index a390db2e9644..1ee33660cc62 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -868,6 +868,94 @@ public class ViewDebug { return null; } + private static class StreamingPictureCallbackHandler implements AutoCloseable, + HardwareRenderer.PictureCapturedCallback, Runnable { + private final HardwareRenderer mRenderer; + private final Callable<OutputStream> mCallback; + private final Executor mExecutor; + private final ReentrantLock mLock = new ReentrantLock(false); + private final ArrayDeque<byte[]> mQueue = new ArrayDeque<>(3); + private final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream(); + private boolean mStopListening; + private Thread mRenderThread; + + private StreamingPictureCallbackHandler(HardwareRenderer renderer, + Callable<OutputStream> callback, Executor executor) { + mRenderer = renderer; + mCallback = callback; + mExecutor = executor; + mRenderer.setPictureCaptureCallback(this); + } + + @Override + public void close() { + mLock.lock(); + mStopListening = true; + mLock.unlock(); + mRenderer.setPictureCaptureCallback(null); + } + + @Override + public void onPictureCaptured(Picture picture) { + mLock.lock(); + if (mStopListening) { + mLock.unlock(); + mRenderer.setPictureCaptureCallback(null); + return; + } + if (mRenderThread == null) { + mRenderThread = Thread.currentThread(); + } + boolean needsInvoke = true; + if (mQueue.size() == 3) { + mQueue.removeLast(); + needsInvoke = false; + } + picture.writeToStream(mByteStream); + mQueue.add(mByteStream.toByteArray()); + mByteStream.reset(); + mLock.unlock(); + + if (needsInvoke) { + mExecutor.execute(this); + } + } + + @Override + public void run() { + mLock.lock(); + final byte[] picture = mQueue.poll(); + final boolean isStopped = mStopListening; + mLock.unlock(); + if (Thread.currentThread() == mRenderThread) { + close(); + throw new IllegalStateException( + "ViewDebug#startRenderingCommandsCapture must be given an executor that " + + "invokes asynchronously"); + } + if (isStopped) { + return; + } + OutputStream stream = null; + try { + stream = mCallback.call(); + } catch (Exception ex) { + Log.w("ViewDebug", "Aborting rendering commands capture " + + "because callback threw exception", ex); + } + if (stream != null) { + try { + stream.write(picture); + } catch (IOException ex) { + Log.w("ViewDebug", "Aborting rendering commands capture " + + "due to IOException writing to output stream", ex); + } + } else { + close(); + } + } + } + /** * Begins capturing the entire rendering commands for the view tree referenced by the given * view. The view passed may be any View in the tree as long as it is attached. That is, @@ -913,18 +1001,7 @@ public class ViewDebug { } final HardwareRenderer renderer = attachInfo.mThreadedRenderer; if (renderer != null) { - return new PictureCallbackHandler(renderer, (picture -> { - try { - OutputStream stream = callback.call(); - if (stream != null) { - picture.writeToStream(stream); - return true; - } - } catch (Exception ex) { - // fall through - } - return false; - }), executor); + return new StreamingPictureCallbackHandler(renderer, callback, executor); } return null; } |