Skip to content

Commit e79e875

Browse files
Romain GuyAndroid (Google) Code Review
authored andcommitted
Merge "Remove ViewTreeObserver allocations" into jb-dev
2 parents 15feb68 + b999cc1 commit e79e875

File tree

1 file changed

+161
-45
lines changed

1 file changed

+161
-45
lines changed

core/java/android/view/ViewTreeObserver.java

Lines changed: 161 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import android.graphics.Region;
2121

2222
import java.util.ArrayList;
23-
import java.util.concurrent.CopyOnWriteArrayList;
2423

2524
/**
2625
* A view tree observer is used to register listeners that can be notified of global
@@ -32,12 +31,12 @@
3231
* for more information.
3332
*/
3433
public final class ViewTreeObserver {
35-
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
36-
private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
37-
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
38-
private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
39-
private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
40-
private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
34+
private CopyOnWriteArray<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
35+
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
36+
private CopyOnWriteArray<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
37+
private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
38+
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
39+
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
4140
private ArrayList<OnDrawListener> mOnDrawListeners;
4241

4342
private boolean mAlive = true;
@@ -147,7 +146,7 @@ public final static class InternalInsetsInfo {
147146
* windows behind it should be placed.
148147
*/
149148
public final Rect contentInsets = new Rect();
150-
149+
151150
/**
152151
* Offsets from the frame of the window at which windows behind it
153152
* are visible.
@@ -166,13 +165,13 @@ public final static class InternalInsetsInfo {
166165
* can be touched.
167166
*/
168167
public static final int TOUCHABLE_INSETS_FRAME = 0;
169-
168+
170169
/**
171170
* Option for {@link #setTouchableInsets(int)}: the area inside of
172171
* the content insets can be touched.
173172
*/
174173
public static final int TOUCHABLE_INSETS_CONTENT = 1;
175-
174+
176175
/**
177176
* Option for {@link #setTouchableInsets(int)}: the area inside of
178177
* the visible insets can be touched.
@@ -195,7 +194,7 @@ public void setTouchableInsets(int val) {
195194
}
196195

197196
int mTouchableInsets;
198-
197+
199198
void reset() {
200199
contentInsets.setEmpty();
201200
visibleInsets.setEmpty();
@@ -231,7 +230,7 @@ void set(InternalInsetsInfo other) {
231230
mTouchableInsets = other.mTouchableInsets;
232231
}
233232
}
234-
233+
235234
/**
236235
* Interface definition for a callback to be invoked when layout has
237236
* completed and the client can compute its interior insets.
@@ -328,7 +327,7 @@ public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener)
328327
checkIsAlive();
329328

330329
if (mOnGlobalFocusListeners == null) {
331-
mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>();
330+
mOnGlobalFocusListeners = new CopyOnWriteArray<OnGlobalFocusChangeListener>();
332331
}
333332

334333
mOnGlobalFocusListeners.add(listener);
@@ -363,7 +362,7 @@ public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
363362
checkIsAlive();
364363

365364
if (mOnGlobalLayoutListeners == null) {
366-
mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>();
365+
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
367366
}
368367

369368
mOnGlobalLayoutListeners.add(listener);
@@ -413,7 +412,7 @@ public void addOnPreDrawListener(OnPreDrawListener listener) {
413412
checkIsAlive();
414413

415414
if (mOnPreDrawListeners == null) {
416-
mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
415+
mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
417416
}
418417

419418
mOnPreDrawListeners.add(listener);
@@ -485,7 +484,7 @@ public void addOnScrollChangedListener(OnScrollChangedListener listener) {
485484
checkIsAlive();
486485

487486
if (mOnScrollChangedListeners == null) {
488-
mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>();
487+
mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
489488
}
490489

491490
mOnScrollChangedListeners.add(listener);
@@ -519,7 +518,7 @@ public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) {
519518
checkIsAlive();
520519

521520
if (mOnTouchModeChangeListeners == null) {
522-
mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>();
521+
mOnTouchModeChangeListeners = new CopyOnWriteArray<OnTouchModeChangeListener>();
523522
}
524523

525524
mOnTouchModeChangeListeners.add(listener);
@@ -558,7 +557,7 @@ public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener l
558557

559558
if (mOnComputeInternalInsetsListeners == null) {
560559
mOnComputeInternalInsetsListeners =
561-
new CopyOnWriteArrayList<OnComputeInternalInsetsListener>();
560+
new CopyOnWriteArray<OnComputeInternalInsetsListener>();
562561
}
563562

564563
mOnComputeInternalInsetsListeners.add(listener);
@@ -622,10 +621,16 @@ final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
622621
// perform the dispatching. The iterator is a safe guard against listeners that
623622
// could mutate the list by calling the various add/remove methods. This prevents
624623
// the array from being modified while we iterate it.
625-
final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
624+
final CopyOnWriteArray<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners;
626625
if (listeners != null && listeners.size() > 0) {
627-
for (OnGlobalFocusChangeListener listener : listeners) {
628-
listener.onGlobalFocusChanged(oldFocus, newFocus);
626+
CopyOnWriteArray.Access<OnGlobalFocusChangeListener> access = listeners.start();
627+
try {
628+
int count = access.size();
629+
for (int i = 0; i < count; i++) {
630+
access.get(i).onGlobalFocusChanged(oldFocus, newFocus);
631+
}
632+
} finally {
633+
listeners.end();
629634
}
630635
}
631636
}
@@ -640,10 +645,16 @@ public final void dispatchOnGlobalLayout() {
640645
// perform the dispatching. The iterator is a safe guard against listeners that
641646
// could mutate the list by calling the various add/remove methods. This prevents
642647
// the array from being modified while we iterate it.
643-
final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
648+
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
644649
if (listeners != null && listeners.size() > 0) {
645-
for (OnGlobalLayoutListener listener : listeners) {
646-
listener.onGlobalLayout();
650+
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
651+
try {
652+
int count = access.size();
653+
for (int i = 0; i < count; i++) {
654+
access.get(i).onGlobalLayout();
655+
}
656+
} finally {
657+
listeners.end();
647658
}
648659
}
649660
}
@@ -658,17 +669,17 @@ public final void dispatchOnGlobalLayout() {
658669
*/
659670
@SuppressWarnings("unchecked")
660671
public final boolean dispatchOnPreDraw() {
661-
// NOTE: we *must* clone the listener list to perform the dispatching.
662-
// The clone is a safe guard against listeners that
663-
// could mutate the list by calling the various add/remove methods. This prevents
664-
// the array from being modified while we process it.
665672
boolean cancelDraw = false;
666-
if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) {
667-
final ArrayList<OnPreDrawListener> listeners =
668-
(ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone();
669-
int numListeners = listeners.size();
670-
for (int i = 0; i < numListeners; ++i) {
671-
cancelDraw |= !(listeners.get(i).onPreDraw());
673+
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
674+
if (listeners != null && listeners.size() > 0) {
675+
CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
676+
try {
677+
int count = access.size();
678+
for (int i = 0; i < count; i++) {
679+
cancelDraw |= !(access.get(i).onPreDraw());
680+
}
681+
} finally {
682+
listeners.end();
672683
}
673684
}
674685
return cancelDraw;
@@ -693,11 +704,17 @@ public final void dispatchOnDraw() {
693704
* @param inTouchMode True if the touch mode is now enabled, false otherwise.
694705
*/
695706
final void dispatchOnTouchModeChanged(boolean inTouchMode) {
696-
final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners =
707+
final CopyOnWriteArray<OnTouchModeChangeListener> listeners =
697708
mOnTouchModeChangeListeners;
698709
if (listeners != null && listeners.size() > 0) {
699-
for (OnTouchModeChangeListener listener : listeners) {
700-
listener.onTouchModeChanged(inTouchMode);
710+
CopyOnWriteArray.Access<OnTouchModeChangeListener> access = listeners.start();
711+
try {
712+
int count = access.size();
713+
for (int i = 0; i < count; i++) {
714+
access.get(i).onTouchModeChanged(inTouchMode);
715+
}
716+
} finally {
717+
listeners.end();
701718
}
702719
}
703720
}
@@ -710,10 +727,16 @@ final void dispatchOnScrollChanged() {
710727
// perform the dispatching. The iterator is a safe guard against listeners that
711728
// could mutate the list by calling the various add/remove methods. This prevents
712729
// the array from being modified while we iterate it.
713-
final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
730+
final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
714731
if (listeners != null && listeners.size() > 0) {
715-
for (OnScrollChangedListener listener : listeners) {
716-
listener.onScrollChanged();
732+
CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
733+
try {
734+
int count = access.size();
735+
for (int i = 0; i < count; i++) {
736+
access.get(i).onScrollChanged();
737+
}
738+
} finally {
739+
listeners.end();
717740
}
718741
}
719742
}
@@ -722,11 +745,11 @@ final void dispatchOnScrollChanged() {
722745
* Returns whether there are listeners for computing internal insets.
723746
*/
724747
final boolean hasComputeInternalInsetsListeners() {
725-
final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
748+
final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
726749
mOnComputeInternalInsetsListeners;
727750
return (listeners != null && listeners.size() > 0);
728751
}
729-
752+
730753
/**
731754
* Calls all listeners to compute the current insets.
732755
*/
@@ -735,12 +758,105 @@ final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
735758
// perform the dispatching. The iterator is a safe guard against listeners that
736759
// could mutate the list by calling the various add/remove methods. This prevents
737760
// the array from being modified while we iterate it.
738-
final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
761+
final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
739762
mOnComputeInternalInsetsListeners;
740763
if (listeners != null && listeners.size() > 0) {
741-
for (OnComputeInternalInsetsListener listener : listeners) {
742-
listener.onComputeInternalInsets(inoutInfo);
764+
CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
765+
try {
766+
int count = access.size();
767+
for (int i = 0; i < count; i++) {
768+
access.get(i).onComputeInternalInsets(inoutInfo);
769+
}
770+
} finally {
771+
listeners.end();
772+
}
773+
}
774+
}
775+
776+
/**
777+
* Copy on write array. This array is not thread safe, and only one loop can
778+
* iterate over this array at any given time. This class avoids allocations
779+
* until a concurrent modification happens.
780+
*
781+
* Usage:
782+
*
783+
* CopyOnWriteArray.Access<MyData> access = array.start();
784+
* try {
785+
* for (int i = 0; i < access.size(); i++) {
786+
* MyData d = access.get(i);
787+
* }
788+
* } finally {
789+
* access.end();
790+
* }
791+
*/
792+
static class CopyOnWriteArray<T> {
793+
private ArrayList<T> mData = new ArrayList<T>();
794+
private ArrayList<T> mDataCopy;
795+
796+
private final Access<T> mAccess = new Access<T>();
797+
798+
private boolean mStart;
799+
800+
static class Access<T> {
801+
private ArrayList<T> mData;
802+
private int mSize;
803+
804+
T get(int index) {
805+
return mData.get(index);
743806
}
807+
808+
int size() {
809+
return mSize;
810+
}
811+
}
812+
813+
CopyOnWriteArray() {
814+
}
815+
816+
private ArrayList<T> getArray() {
817+
if (mStart) {
818+
if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
819+
return mDataCopy;
820+
}
821+
return mData;
822+
}
823+
824+
Access<T> start() {
825+
if (mStart) throw new IllegalStateException("Iteration already started");
826+
mStart = true;
827+
mDataCopy = null;
828+
mAccess.mData = mData;
829+
mAccess.mSize = mData.size();
830+
return mAccess;
831+
}
832+
833+
void end() {
834+
if (!mStart) throw new IllegalStateException("Iteration not started");
835+
mStart = false;
836+
if (mDataCopy != null) {
837+
mData = mDataCopy;
838+
}
839+
mDataCopy = null;
840+
}
841+
842+
int size() {
843+
return getArray().size();
844+
}
845+
846+
void add(T item) {
847+
getArray().add(item);
848+
}
849+
850+
void addAll(CopyOnWriteArray<T> array) {
851+
getArray().addAll(array.mData);
852+
}
853+
854+
void remove(T item) {
855+
getArray().remove(item);
856+
}
857+
858+
void clear() {
859+
getArray().clear();
744860
}
745861
}
746862
}

0 commit comments

Comments
 (0)