Loading core/java/android/view/ViewRootImpl.java +41 −15 Original line number Original line Diff line number Diff line Loading @@ -671,6 +671,7 @@ public final class ViewRootImpl implements ViewParent, private boolean mInvalidationIdleMessagePosted = false; private boolean mInvalidationIdleMessagePosted = false; // VRR: List of all Views that are animating with the threaded render // VRR: List of all Views that are animating with the threaded render private ArrayList<View> mThreadedRendererViews = new ArrayList(); private ArrayList<View> mThreadedRendererViews = new ArrayList(); private ArrayList<View> mThreadedRendererViewsCache = new ArrayList(); /** /** * Update the Choreographer's FrameInfo object with the timing information for the current * Update the Choreographer's FrameInfo object with the timing information for the current Loading Loading @@ -4548,10 +4549,7 @@ public final class ViewRootImpl implements ViewParent, } } mDrawnThisFrame = false; mDrawnThisFrame = false; if (!mInvalidationIdleMessagePosted) { sendCheckInvalidationIdle(); mInvalidationIdleMessagePosted = true; mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } setCategoryFromCategoryCounts(); setCategoryFromCategoryCounts(); updateInfrequentCount(); updateInfrequentCount(); updateFrameRateFromThreadedRendererViews(); updateFrameRateFromThreadedRendererViews(); Loading Loading @@ -4584,6 +4582,19 @@ public final class ViewRootImpl implements ViewParent, } } } } private void sendCheckInvalidationIdle() { if (shouldEnableDvrr()) { boolean wasPosted; synchronized (mThreadedRendererViews) { wasPosted = mInvalidationIdleMessagePosted; mInvalidationIdleMessagePosted = true; } if (!wasPosted) { mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } } } private void createSyncIfNeeded() { private void createSyncIfNeeded() { // WMS requested sync already started or there's nothing needing to sync // WMS requested sync already started or there's nothing needing to sync if (isInWMSRequestedSync() || !mReportNextDraw) { if (isInWMSRequestedSync() || !mReportNextDraw) { Loading Loading @@ -13182,16 +13193,22 @@ public final class ViewRootImpl implements ViewParent, * from those views. * from those views. */ */ private void updateFrameRateFromThreadedRendererViews() { private void updateFrameRateFromThreadedRendererViews() { ArrayList<View> views = mThreadedRendererViews; ArrayList<View> views = mThreadedRendererViewsCache; synchronized (mThreadedRendererViews) { views.addAll(mThreadedRendererViews); } for (int i = views.size() - 1; i >= 0; i--) { for (int i = views.size() - 1; i >= 0; i--) { View view = views.get(i); View view = views.get(i); View.AttachInfo attachInfo = view.mAttachInfo; View.AttachInfo attachInfo = view.mAttachInfo; if (attachInfo == null || attachInfo.mViewRootImpl != this) { if (attachInfo == null || attachInfo.mViewRootImpl != this) { views.remove(i); synchronized (mThreadedRendererViews) { mThreadedRendererViews.remove(view); } } else { } else { view.votePreferredFrameRate(); view.votePreferredFrameRate(); } } } } views.clear(); } } /** /** Loading Loading @@ -13400,10 +13417,14 @@ public final class ViewRootImpl implements ViewParent, * @param view The View with the ThreadedRenderer animation that started. * @param view The View with the ThreadedRenderer animation that started. */ */ public void addThreadedRendererView(View view) { public void addThreadedRendererView(View view) { if (shouldEnableDvrr() && !mThreadedRendererViews.contains(view)) { if (shouldEnableDvrr()) { synchronized (mThreadedRendererViews) { if (!mThreadedRendererViews.contains(view)) { mThreadedRendererViews.add(view); mThreadedRendererViews.add(view); } } } } } } /** /** * When a ThreadedRenderer animation ends, the View that is associated with it using * When a ThreadedRenderer animation ends, the View that is associated with it using Loading @@ -13411,10 +13432,11 @@ public final class ViewRootImpl implements ViewParent, * @param view The View whose ThreadedRender animation has stopped. * @param view The View whose ThreadedRender animation has stopped. */ */ public void removeThreadedRendererView(View view) { public void removeThreadedRendererView(View view) { if (shouldEnableDvrr()) { synchronized (mThreadedRendererViews) { mThreadedRendererViews.remove(view); mThreadedRendererViews.remove(view); if (shouldEnableDvrr() && !mInvalidationIdleMessagePosted) { } mInvalidationIdleMessagePosted = true; sendCheckInvalidationIdle(); mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } } } } Loading Loading @@ -13633,10 +13655,10 @@ public final class ViewRootImpl implements ViewParent, mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT); mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT); if (mInvalidationIdleMessagePosted) { synchronized (mThreadedRendererViews) { mInvalidationIdleMessagePosted = false; mInvalidationIdleMessagePosted = false; mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); } } mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); } } /** /** Loading @@ -13655,7 +13677,11 @@ public final class ViewRootImpl implements ViewParent, mMinusOneFrameIntervalMillis = timeIntervalMillis; mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; mLastUpdateTimeMillis = currentTimeMillis; if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis boolean isThreadedRendererViewsEmpty; synchronized (mThreadedRendererViews) { isThreadedRendererViewsEmpty = mThreadedRendererViews.isEmpty(); } if (isThreadedRendererViewsEmpty && timeIntervalMillis + mMinusTwoFrameIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { int infrequentUpdateCount = mInfrequentUpdateCount; int infrequentUpdateCount = mInfrequentUpdateCount; mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS core/tests/coretests/src/android/view/ViewRootImplTest.java +35 −0 Original line number Original line Diff line number Diff line Loading @@ -106,6 +106,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** /** * Tests for {@link ViewRootImpl} * Tests for {@link ViewRootImpl} Loading Loading @@ -1628,6 +1629,40 @@ public class ViewRootImplTest { assertThat(bounds.height()).isAtLeast(strokeWidth * 2); assertThat(bounds.height()).isAtLeast(strokeWidth * 2); } } @Test public void testOffThreadRendererViewsAccess() throws Throwable { mView = new View(sContext); attachViewToWindow(mView); AtomicInteger threadRunning = new AtomicInteger(1); View[] views = new View[10]; for (int i = 0; i < 10; i++) { views[i] = new View(sContext); } Thread offThread = new Thread(() -> { while (threadRunning.get() > 0) { for (int i = 0; i < 10; i++) { mViewRootImpl.addThreadedRendererView(views[i]); } for (int i = 0; i < 10; i++) { mViewRootImpl.removeThreadedRendererView(views[i]); } } }); offThread.start(); try { for (int i = 0; i < 1000; i++) { sInstrumentation.runOnMainSync(() -> { mView.invalidate(); runAfterDraw(() -> { }); }); waitForAfterDraw(); } } finally { threadRunning.set(0); } } static class InputView extends View { static class InputView extends View { private final BlockingQueue<InputEvent> mEvents = new LinkedBlockingQueue<>(); private final BlockingQueue<InputEvent> mEvents = new LinkedBlockingQueue<>(); private final BlockingQueueEventVerifier mVerifier = private final BlockingQueueEventVerifier mVerifier = Loading Loading
core/java/android/view/ViewRootImpl.java +41 −15 Original line number Original line Diff line number Diff line Loading @@ -671,6 +671,7 @@ public final class ViewRootImpl implements ViewParent, private boolean mInvalidationIdleMessagePosted = false; private boolean mInvalidationIdleMessagePosted = false; // VRR: List of all Views that are animating with the threaded render // VRR: List of all Views that are animating with the threaded render private ArrayList<View> mThreadedRendererViews = new ArrayList(); private ArrayList<View> mThreadedRendererViews = new ArrayList(); private ArrayList<View> mThreadedRendererViewsCache = new ArrayList(); /** /** * Update the Choreographer's FrameInfo object with the timing information for the current * Update the Choreographer's FrameInfo object with the timing information for the current Loading Loading @@ -4548,10 +4549,7 @@ public final class ViewRootImpl implements ViewParent, } } mDrawnThisFrame = false; mDrawnThisFrame = false; if (!mInvalidationIdleMessagePosted) { sendCheckInvalidationIdle(); mInvalidationIdleMessagePosted = true; mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } setCategoryFromCategoryCounts(); setCategoryFromCategoryCounts(); updateInfrequentCount(); updateInfrequentCount(); updateFrameRateFromThreadedRendererViews(); updateFrameRateFromThreadedRendererViews(); Loading Loading @@ -4584,6 +4582,19 @@ public final class ViewRootImpl implements ViewParent, } } } } private void sendCheckInvalidationIdle() { if (shouldEnableDvrr()) { boolean wasPosted; synchronized (mThreadedRendererViews) { wasPosted = mInvalidationIdleMessagePosted; mInvalidationIdleMessagePosted = true; } if (!wasPosted) { mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } } } private void createSyncIfNeeded() { private void createSyncIfNeeded() { // WMS requested sync already started or there's nothing needing to sync // WMS requested sync already started or there's nothing needing to sync if (isInWMSRequestedSync() || !mReportNextDraw) { if (isInWMSRequestedSync() || !mReportNextDraw) { Loading Loading @@ -13182,16 +13193,22 @@ public final class ViewRootImpl implements ViewParent, * from those views. * from those views. */ */ private void updateFrameRateFromThreadedRendererViews() { private void updateFrameRateFromThreadedRendererViews() { ArrayList<View> views = mThreadedRendererViews; ArrayList<View> views = mThreadedRendererViewsCache; synchronized (mThreadedRendererViews) { views.addAll(mThreadedRendererViews); } for (int i = views.size() - 1; i >= 0; i--) { for (int i = views.size() - 1; i >= 0; i--) { View view = views.get(i); View view = views.get(i); View.AttachInfo attachInfo = view.mAttachInfo; View.AttachInfo attachInfo = view.mAttachInfo; if (attachInfo == null || attachInfo.mViewRootImpl != this) { if (attachInfo == null || attachInfo.mViewRootImpl != this) { views.remove(i); synchronized (mThreadedRendererViews) { mThreadedRendererViews.remove(view); } } else { } else { view.votePreferredFrameRate(); view.votePreferredFrameRate(); } } } } views.clear(); } } /** /** Loading Loading @@ -13400,10 +13417,14 @@ public final class ViewRootImpl implements ViewParent, * @param view The View with the ThreadedRenderer animation that started. * @param view The View with the ThreadedRenderer animation that started. */ */ public void addThreadedRendererView(View view) { public void addThreadedRendererView(View view) { if (shouldEnableDvrr() && !mThreadedRendererViews.contains(view)) { if (shouldEnableDvrr()) { synchronized (mThreadedRendererViews) { if (!mThreadedRendererViews.contains(view)) { mThreadedRendererViews.add(view); mThreadedRendererViews.add(view); } } } } } } /** /** * When a ThreadedRenderer animation ends, the View that is associated with it using * When a ThreadedRenderer animation ends, the View that is associated with it using Loading @@ -13411,10 +13432,11 @@ public final class ViewRootImpl implements ViewParent, * @param view The View whose ThreadedRender animation has stopped. * @param view The View whose ThreadedRender animation has stopped. */ */ public void removeThreadedRendererView(View view) { public void removeThreadedRendererView(View view) { if (shouldEnableDvrr()) { synchronized (mThreadedRendererViews) { mThreadedRendererViews.remove(view); mThreadedRendererViews.remove(view); if (shouldEnableDvrr() && !mInvalidationIdleMessagePosted) { } mInvalidationIdleMessagePosted = true; sendCheckInvalidationIdle(); mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); } } } } Loading Loading @@ -13633,10 +13655,10 @@ public final class ViewRootImpl implements ViewParent, mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT); mHandler.removeMessages(MSG_SURFACE_REPLACED_TIMEOUT); if (mInvalidationIdleMessagePosted) { synchronized (mThreadedRendererViews) { mInvalidationIdleMessagePosted = false; mInvalidationIdleMessagePosted = false; mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); } } mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); } } /** /** Loading @@ -13655,7 +13677,11 @@ public final class ViewRootImpl implements ViewParent, mMinusOneFrameIntervalMillis = timeIntervalMillis; mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; mLastUpdateTimeMillis = currentTimeMillis; if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis boolean isThreadedRendererViewsEmpty; synchronized (mThreadedRendererViews) { isThreadedRendererViewsEmpty = mThreadedRendererViews.isEmpty(); } if (isThreadedRendererViewsEmpty && timeIntervalMillis + mMinusTwoFrameIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { int infrequentUpdateCount = mInfrequentUpdateCount; int infrequentUpdateCount = mInfrequentUpdateCount; mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS
core/tests/coretests/src/android/view/ViewRootImplTest.java +35 −0 Original line number Original line Diff line number Diff line Loading @@ -106,6 +106,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** /** * Tests for {@link ViewRootImpl} * Tests for {@link ViewRootImpl} Loading Loading @@ -1628,6 +1629,40 @@ public class ViewRootImplTest { assertThat(bounds.height()).isAtLeast(strokeWidth * 2); assertThat(bounds.height()).isAtLeast(strokeWidth * 2); } } @Test public void testOffThreadRendererViewsAccess() throws Throwable { mView = new View(sContext); attachViewToWindow(mView); AtomicInteger threadRunning = new AtomicInteger(1); View[] views = new View[10]; for (int i = 0; i < 10; i++) { views[i] = new View(sContext); } Thread offThread = new Thread(() -> { while (threadRunning.get() > 0) { for (int i = 0; i < 10; i++) { mViewRootImpl.addThreadedRendererView(views[i]); } for (int i = 0; i < 10; i++) { mViewRootImpl.removeThreadedRendererView(views[i]); } } }); offThread.start(); try { for (int i = 0; i < 1000; i++) { sInstrumentation.runOnMainSync(() -> { mView.invalidate(); runAfterDraw(() -> { }); }); waitForAfterDraw(); } } finally { threadRunning.set(0); } } static class InputView extends View { static class InputView extends View { private final BlockingQueue<InputEvent> mEvents = new LinkedBlockingQueue<>(); private final BlockingQueue<InputEvent> mEvents = new LinkedBlockingQueue<>(); private final BlockingQueueEventVerifier mVerifier = private final BlockingQueueEventVerifier mVerifier = Loading