Avoid potential leaks with Runnables posted from ProgressBar
Bug 6093695
Handle pending progress updates when a view is not attached when the
view becomes attached again. Batch pending progress updates together
rather than posting separate runnables for each.
Change-Id: I5dea671d5b9fbe1302912ca4734a63955e77ff4d
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 3bc4f7f..0b49404 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -37,9 +37,11 @@
import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.view.Choreographer;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -55,6 +57,8 @@
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
+import java.util.ArrayList;
+
/**
* <p>
@@ -218,6 +222,10 @@
private boolean mShouldStartAnimationDrawable;
private boolean mInDrawing;
+ private boolean mAttached;
+ private boolean mRefreshIsPosted;
+
+ private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
private AccessibilityEventSender mAccessibilityEventSender;
@@ -558,29 +566,76 @@
}
private class RefreshProgressRunnable implements Runnable {
-
- private int mId;
- private int mProgress;
- private boolean mFromUser;
-
- RefreshProgressRunnable(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
- }
-
public void run() {
- doRefreshProgress(mId, mProgress, mFromUser, true);
- // Put ourselves back in the cache when we are done
- mRefreshProgressRunnable = this;
+ synchronized (ProgressBar.this) {
+ final int count = mRefreshData.size();
+ for (int i = 0; i < count; i++) {
+ final RefreshData rd = mRefreshData.get(i);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+ rd.recycle();
+ }
+ mRefreshData.clear();
+ mRefreshIsPosted = false;
+ }
+ }
+ }
+
+ private static class RefreshData implements Poolable<RefreshData> {
+ public int id;
+ public int progress;
+ public boolean fromUser;
+
+ private RefreshData mNext;
+ private boolean mIsPooled;
+
+ private static final int POOL_MAX = 24;
+ private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
+ Pools.finitePool(new PoolableManager<RefreshData>() {
+ @Override
+ public RefreshData newInstance() {
+ return new RefreshData();
+ }
+
+ @Override
+ public void onAcquired(RefreshData element) {
+ }
+
+ @Override
+ public void onReleased(RefreshData element) {
+ }
+ }, POOL_MAX));
+
+ public static RefreshData obtain(int id, int progress, boolean fromUser) {
+ RefreshData rd = sPool.acquire();
+ rd.id = id;
+ rd.progress = progress;
+ rd.fromUser = fromUser;
+ return rd;
}
- public void setup(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
+ public void recycle() {
+ sPool.release(this);
}
-
+
+ @Override
+ public void setNextPoolable(RefreshData element) {
+ mNext = element;
+ }
+
+ @Override
+ public RefreshData getNextPoolable() {
+ return mNext;
+ }
+
+ @Override
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ @Override
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
}
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
@@ -619,14 +674,16 @@
if (mRefreshProgressRunnable != null) {
// Use cached RefreshProgressRunnable if available
r = mRefreshProgressRunnable;
- // Uncache it
- mRefreshProgressRunnable = null;
- r.setup(id, progress, fromUser);
} else {
// Make a new one
- r = new RefreshProgressRunnable(id, progress, fromUser);
+ r = new RefreshProgressRunnable();
}
- post(r);
+ final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
+ mRefreshData.add(rd);
+ if (mAttached && !mRefreshIsPosted) {
+ post(r);
+ mRefreshIsPosted = true;
+ }
}
}
@@ -1092,6 +1149,18 @@
if (mIndeterminate) {
startAnimation();
}
+ if (mRefreshData != null) {
+ synchronized (this) {
+ final int count = mRefreshData.size();
+ for (int i = 0; i < count; i++) {
+ final RefreshData rd = mRefreshData.get(i);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+ rd.recycle();
+ }
+ mRefreshData.clear();
+ }
+ }
+ mAttached = true;
}
@Override
@@ -1099,7 +1168,10 @@
if (mIndeterminate) {
stopAnimation();
}
- if(mRefreshProgressRunnable != null) {
+ if (mRefreshProgressRunnable != null) {
+ removeCallbacks(mRefreshProgressRunnable);
+ }
+ if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
removeCallbacks(mRefreshProgressRunnable);
}
if (mAccessibilityEventSender != null) {
@@ -1108,6 +1180,7 @@
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
+ mAttached = false;
}
@Override