diff options
| -rw-r--r-- | core/java/android/view/HandlerActionQueue.java | 127 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 55 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 82 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/HandlerActionQueueTest.java | 80 |
4 files changed, 253 insertions, 91 deletions
diff --git a/core/java/android/view/HandlerActionQueue.java b/core/java/android/view/HandlerActionQueue.java new file mode 100644 index 000000000000..4758a3408dc2 --- /dev/null +++ b/core/java/android/view/HandlerActionQueue.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import com.android.internal.util.GrowingArrayUtils; + +import android.os.Handler; + +import java.util.ArrayList; + +/** + * Class used to enqueue pending work from Views when no Handler is attached. + * + * @hide Exposed for test framework only. + */ +public class HandlerActionQueue { + private HandlerAction[] mActions; + private int mCount; + + public void post(Runnable action) { + postDelayed(action, 0); + } + + public void postDelayed(Runnable action, long delayMillis) { + final HandlerAction handlerAction = new HandlerAction(action, delayMillis); + + synchronized (this) { + if (mActions == null) { + mActions = new HandlerAction[4]; + } + mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); + mCount++; + } + } + + public void removeCallbacks(Runnable action) { + synchronized (this) { + final int count = mCount; + int j = 0; + + final HandlerAction[] actions = mActions; + for (int i = 0; i < count; i++) { + if (actions[i].matches(action)) { + // Remove this action by overwriting it within + // this loop or nulling it out later. + continue; + } + + if (j != i) { + // At least one previous entry was removed, so + // this one needs to move to the "new" list. + actions[j] = actions[i]; + } + + j++; + } + + // The "new" list only has j entries. + mCount = j; + + // Null out any remaining entries. + for (; j < count; j++) { + actions[j] = null; + } + } + } + + public void executeActions(Handler handler) { + synchronized (this) { + final HandlerAction[] actions = mActions; + for (int i = 0, count = mCount; i < count; i++) { + final HandlerAction handlerAction = actions[i]; + handler.postDelayed(handlerAction.action, handlerAction.delay); + } + + mActions = null; + mCount = 0; + } + } + + public int size() { + return mCount; + } + + public Runnable getRunnable(int index) { + if (index >= mCount) { + throw new IndexOutOfBoundsException(); + } + return mActions[index].action; + } + + public long getDelay(int index) { + if (index >= mCount) { + throw new IndexOutOfBoundsException(); + } + return mActions[index].delay; + } + + private static class HandlerAction { + final Runnable action; + final long delay; + + public HandlerAction(Runnable action, long delay) { + this.action = action; + this.delay = delay; + } + + public boolean matches(Runnable otherAction) { + return otherAction == null && action == null + || action != null && action.equals(otherAction); + } + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16d9bf398673..665069c6a1ae 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3823,6 +3823,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static SparseArray<String> mAttributeMap; /** + * Queue of pending runnables. Used to postpone calls to post() until this + * view is attached and has a handler. + */ + private HandlerActionQueue mRunQueue; + + /** * @hide */ String mStartActivityRequestWho; @@ -13010,6 +13016,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns the queue of runnable for this view. + * + * @return the queue of runnables for this view + */ + private HandlerActionQueue getRunQueue() { + if (mRunQueue == null) { + mRunQueue = new HandlerActionQueue(); + } + return mRunQueue; + } + + /** * Gets the view root associated with the View. * @return The view root, or null if none. * @hide @@ -13046,8 +13064,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (attachInfo != null) { return attachInfo.mHandler.post(action); } - // Assume that post will succeed later - ViewRootImpl.getRunQueue().post(action); + + // Postpone the runnable until we know on which thread it needs to run. + // Assume that the runnable will be successfully placed after attach. + getRunQueue().post(action); return true; } @@ -13075,8 +13095,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } - // Assume that post will succeed later - ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); + + // Postpone the runnable until we know on which thread it needs to run. + // Assume that the runnable will be successfully placed after attach. + getRunQueue().postDelayed(action, delayMillis); return true; } @@ -13095,8 +13117,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, attachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null); } else { - // Assume that post will succeed later - ViewRootImpl.getRunQueue().post(action); + // Postpone the runnable until we know + // on which thread it needs to run. + getRunQueue().post(action); } } @@ -13118,8 +13141,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed( Choreographer.CALLBACK_ANIMATION, action, null, delayMillis); } else { - // Assume that post will succeed later - ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); + // Postpone the runnable until we know + // on which thread it needs to run. + getRunQueue().postDelayed(action, delayMillis); } } @@ -13146,8 +13170,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, attachInfo.mViewRootImpl.mChoreographer.removeCallbacks( Choreographer.CALLBACK_ANIMATION, action, null); } - // Assume that post will succeed later - ViewRootImpl.getRunQueue().removeCallbacks(action); + getRunQueue().removeCallbacks(action); } return true; } @@ -14565,7 +14588,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * this view */ void dispatchAttachedToWindow(AttachInfo info, int visibility) { - //System.out.println("Attached! " + this); mAttachInfo = info; if (mOverlay != null) { mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility); @@ -14581,6 +14603,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } + // Transfer all pending runnables. + if (mRunQueue != null) { + mRunQueue.executeActions(info.mHandler); + mRunQueue = null; + } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); @@ -16866,7 +16893,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Choreographer.CALLBACK_ANIMATION, what, who, Choreographer.subtractFrameDelay(delay)); } else { - ViewRootImpl.getRunQueue().postDelayed(what, delay); + // Postpone the runnable until we know + // on which thread it needs to run. + getRunQueue().postDelayed(what, delay); } } } @@ -16884,7 +16913,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks( Choreographer.CALLBACK_ANIMATION, what, who); } - ViewRootImpl.getRunQueue().removeCallbacks(what); + getRunQueue().removeCallbacks(what); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8bbaf36d4094..d26e91408666 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -28,7 +28,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; @@ -131,7 +130,7 @@ public final class ViewRootImpl implements ViewParent, */ static final int MAX_TRACKBALL_DELAY = 250; - static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); + static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>(); static boolean sFirstDrawComplete = false; @@ -6759,90 +6758,17 @@ public final class ViewRootImpl implements ViewParent, } } - static RunQueue getRunQueue() { - RunQueue rq = sRunQueues.get(); + static HandlerActionQueue getRunQueue() { + HandlerActionQueue rq = sRunQueues.get(); if (rq != null) { return rq; } - rq = new RunQueue(); + rq = new HandlerActionQueue(); sRunQueues.set(rq); return rq; } /** - * The run queue is used to enqueue pending work from Views when no Handler is - * attached. The work is executed during the next call to performTraversals on - * the thread. - * @hide - */ - static final class RunQueue { - private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); - - void post(Runnable action) { - postDelayed(action, 0); - } - - void postDelayed(Runnable action, long delayMillis) { - HandlerAction handlerAction = new HandlerAction(); - handlerAction.action = action; - handlerAction.delay = delayMillis; - - synchronized (mActions) { - mActions.add(handlerAction); - } - } - - void removeCallbacks(Runnable action) { - final HandlerAction handlerAction = new HandlerAction(); - handlerAction.action = action; - - synchronized (mActions) { - final ArrayList<HandlerAction> actions = mActions; - - while (actions.remove(handlerAction)) { - // Keep going - } - } - } - - void executeActions(Handler handler) { - synchronized (mActions) { - final ArrayList<HandlerAction> actions = mActions; - final int count = actions.size(); - - for (int i = 0; i < count; i++) { - final HandlerAction handlerAction = actions.get(i); - handler.postDelayed(handlerAction.action, handlerAction.delay); - } - - actions.clear(); - } - } - - private static class HandlerAction { - Runnable action; - long delay; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - HandlerAction that = (HandlerAction) o; - return !(action != null ? !action.equals(that.action) : that.action != null); - - } - - @Override - public int hashCode() { - int result = action != null ? action.hashCode() : 0; - result = 31 * result + (int) (delay ^ (delay >>> 32)); - return result; - } - } - } - - /** * Class for managing the accessibility interaction connection * based on the global accessibility state. */ diff --git a/core/tests/coretests/src/android/view/HandlerActionQueueTest.java b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java new file mode 100644 index 000000000000..fd8f23abc45c --- /dev/null +++ b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class HandlerActionQueueTest extends AndroidTestCase { + + @SmallTest + public void testPostAndRemove() { + HandlerActionQueue runQueue = new HandlerActionQueue(); + MockRunnable runnable1 = new MockRunnable(); + MockRunnable runnable2 = new MockRunnable(); + MockRunnable runnable3 = new MockRunnable(); + + runQueue.post(runnable1); + runQueue.post(runnable1); + runQueue.post(runnable2); + runQueue.postDelayed(runnable1, 100); + runQueue.postDelayed(null, 500); + assertEquals(5, runQueue.size()); + assertEquals(0, runQueue.getDelay(0)); + assertEquals(0, runQueue.getDelay(1)); + assertEquals(0, runQueue.getDelay(2)); + assertEquals(100, runQueue.getDelay(3)); + assertEquals(500, runQueue.getDelay(4)); + assertEquals(500, runQueue.getDelay(4)); + assertEquals(runnable1, runQueue.getRunnable(0)); + assertEquals(runnable1, runQueue.getRunnable(1)); + assertEquals(runnable2, runQueue.getRunnable(2)); + assertEquals(runnable1, runQueue.getRunnable(3)); + assertEquals(null, runQueue.getRunnable(4)); + + runQueue.removeCallbacks(runnable1); + assertEquals(2, runQueue.size()); + assertEquals(0, runQueue.getDelay(0)); + assertEquals(500, runQueue.getDelay(1)); + assertEquals(runnable2, runQueue.getRunnable(0)); + assertEquals(null, runQueue.getRunnable(1)); + + try { + assertNull(runQueue.getRunnable(2)); + assertFalse(true); + } catch (IndexOutOfBoundsException e) { + // Should throw an exception. + } + + runQueue.removeCallbacks(runnable3); + assertEquals(2, runQueue.size()); + + runQueue.removeCallbacks(runnable2); + assertEquals(1, runQueue.size()); + assertEquals(null, runQueue.getRunnable(0)); + + runQueue.removeCallbacks(null); + assertEquals(0, runQueue.size()); + } + + private static class MockRunnable implements Runnable { + @Override + public void run() { + + } + } +} |