Revert "Remove ViewTreeObserver allocations"
This reverts commit b999cc118fe430699e9a67d5dab355125b873abb.
There was an assumption in this earlier change that observer dispatching could not be
recursive - we could only ever have one iteration on the observer listener list. This
assumption broke down in a specific app, and maybe in more, so reverting the change for now.
We should probably find a way to accomplish the same allocation-minimizing goal without
causing exceptions when violating our assumptions.
Issue #6620795 [Application compatibility] Lufthansa app crashes
Change-Id: I1c1f9ad329c14398feb0e74ce77e1a07111f7d1f
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 6a8a60a..1c5d436 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -20,6 +20,7 @@
import android.graphics.Region;
import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* A view tree observer is used to register listeners that can be notified of global
@@ -31,12 +32,12 @@
* for more information.
*/
public final class ViewTreeObserver {
- private CopyOnWriteArray<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
- private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
- private CopyOnWriteArray<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
- private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
- private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
- private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+ private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
+ private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+ private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+ private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+ private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
+ private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
private ArrayList<OnDrawListener> mOnDrawListeners;
private boolean mAlive = true;
@@ -146,7 +147,7 @@
* windows behind it should be placed.
*/
public final Rect contentInsets = new Rect();
-
+
/**
* Offsets from the frame of the window at which windows behind it
* are visible.
@@ -165,13 +166,13 @@
* can be touched.
*/
public static final int TOUCHABLE_INSETS_FRAME = 0;
-
+
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the content insets can be touched.
*/
public static final int TOUCHABLE_INSETS_CONTENT = 1;
-
+
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the visible insets can be touched.
@@ -194,7 +195,7 @@
}
int mTouchableInsets;
-
+
void reset() {
contentInsets.setEmpty();
visibleInsets.setEmpty();
@@ -230,7 +231,7 @@
mTouchableInsets = other.mTouchableInsets;
}
}
-
+
/**
* Interface definition for a callback to be invoked when layout has
* completed and the client can compute its interior insets.
@@ -327,7 +328,7 @@
checkIsAlive();
if (mOnGlobalFocusListeners == null) {
- mOnGlobalFocusListeners = new CopyOnWriteArray<OnGlobalFocusChangeListener>();
+ mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
}
mOnGlobalFocusListeners.add(listener);
@@ -362,7 +363,7 @@
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
- mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
+ mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener);
@@ -412,7 +413,7 @@
checkIsAlive();
if (mOnPreDrawListeners == null) {
- mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
+ mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
}
mOnPreDrawListeners.add(listener);
@@ -484,7 +485,7 @@
checkIsAlive();
if (mOnScrollChangedListeners == null) {
- mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
+ mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>();
}
mOnScrollChangedListeners.add(listener);
@@ -518,7 +519,7 @@
checkIsAlive();
if (mOnTouchModeChangeListeners == null) {
- mOnTouchModeChangeListeners = new CopyOnWriteArray<OnTouchModeChangeListener>();
+ mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
}
mOnTouchModeChangeListeners.add(listener);
@@ -557,7 +558,7 @@
if (mOnComputeInternalInsetsListeners == null) {
mOnComputeInternalInsetsListeners =
- new CopyOnWriteArray<OnComputeInternalInsetsListener>();
+ new CopyOnWriteArrayList<OnComputeInternalInsetsListener>();
}
mOnComputeInternalInsetsListeners.add(listener);
@@ -621,16 +622,10 @@
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArray<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
+ final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnGlobalFocusChangeListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- access.get(i).onGlobalFocusChanged(oldFocus, newFocus);
- }
- } finally {
- listeners.end();
+ for (OnGlobalFocusChangeListener listener : listeners) {
+ listener.onGlobalFocusChanged(oldFocus, newFocus);
}
}
}
@@ -645,16 +640,10 @@
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
+ final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- access.get(i).onGlobalLayout();
- }
- } finally {
- listeners.end();
+ for (OnGlobalLayoutListener listener : listeners) {
+ listener.onGlobalLayout();
}
}
}
@@ -669,17 +658,17 @@
*/
@SuppressWarnings("unchecked")
public final boolean dispatchOnPreDraw() {
+ // NOTE: we *must* clone the listener list to perform the dispatching.
+ // The clone is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we process it.
boolean cancelDraw = false;
- final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
- if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- cancelDraw |= !(access.get(i).onPreDraw());
- }
- } finally {
- listeners.end();
+ if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) {
+ final ArrayList<OnPreDrawListener> listeners =
+ (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone();
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ cancelDraw |= !(listeners.get(i).onPreDraw());
}
}
return cancelDraw;
@@ -704,17 +693,11 @@
* @param inTouchMode True if the touch mode is now enabled, false otherwise.
*/
final void dispatchOnTouchModeChanged(boolean inTouchMode) {
- final CopyOnWriteArray<OnTouchModeChangeListener> listeners =
+ final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
mOnTouchModeChangeListeners;
if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnTouchModeChangeListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- access.get(i).onTouchModeChanged(inTouchMode);
- }
- } finally {
- listeners.end();
+ for (OnTouchModeChangeListener listener : listeners) {
+ listener.onTouchModeChanged(inTouchMode);
}
}
}
@@ -727,16 +710,10 @@
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+ final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- access.get(i).onScrollChanged();
- }
- } finally {
- listeners.end();
+ for (OnScrollChangedListener listener : listeners) {
+ listener.onScrollChanged();
}
}
}
@@ -745,11 +722,11 @@
* Returns whether there are listeners for computing internal insets.
*/
final boolean hasComputeInternalInsetsListeners() {
- final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+ final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
return (listeners != null && listeners.size() > 0);
}
-
+
/**
* Calls all listeners to compute the current insets.
*/
@@ -758,105 +735,12 @@
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
+ final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
if (listeners != null && listeners.size() > 0) {
- CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
- try {
- int count = access.size();
- for (int i = 0; i < count; i++) {
- access.get(i).onComputeInternalInsets(inoutInfo);
- }
- } finally {
- listeners.end();
+ for (OnComputeInternalInsetsListener listener : listeners) {
+ listener.onComputeInternalInsets(inoutInfo);
}
}
}
-
- /**
- * Copy on write array. This array is not thread safe, and only one loop can
- * iterate over this array at any given time. This class avoids allocations
- * until a concurrent modification happens.
- *
- * Usage:
- *
- * CopyOnWriteArray.Access<MyData> access = array.start();
- * try {
- * for (int i = 0; i < access.size(); i++) {
- * MyData d = access.get(i);
- * }
- * } finally {
- * access.end();
- * }
- */
- static class CopyOnWriteArray<T> {
- private ArrayList<T> mData = new ArrayList<T>();
- private ArrayList<T> mDataCopy;
-
- private final Access<T> mAccess = new Access<T>();
-
- private boolean mStart;
-
- static class Access<T> {
- private ArrayList<T> mData;
- private int mSize;
-
- T get(int index) {
- return mData.get(index);
- }
-
- int size() {
- return mSize;
- }
- }
-
- CopyOnWriteArray() {
- }
-
- private ArrayList<T> getArray() {
- if (mStart) {
- if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
- return mDataCopy;
- }
- return mData;
- }
-
- Access<T> start() {
- if (mStart) throw new IllegalStateException("Iteration already started");
- mStart = true;
- mDataCopy = null;
- mAccess.mData = mData;
- mAccess.mSize = mData.size();
- return mAccess;
- }
-
- void end() {
- if (!mStart) throw new IllegalStateException("Iteration not started");
- mStart = false;
- if (mDataCopy != null) {
- mData = mDataCopy;
- }
- mDataCopy = null;
- }
-
- int size() {
- return getArray().size();
- }
-
- void add(T item) {
- getArray().add(item);
- }
-
- void addAll(CopyOnWriteArray<T> array) {
- getArray().addAll(array.mData);
- }
-
- void remove(T item) {
- getArray().remove(item);
- }
-
- void clear() {
- getArray().clear();
- }
- }
}