Caching the FixedSizeRemoteViewsCaches across rotation
-> This prevents unnecessary flashing of collection widgets on rotation
Change-Id: Id29d4952aa640ca90b31dc3e02e2402cc0fb84d3
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 19aef8e..437da59 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -69,6 +67,8 @@
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.R;
+
import java.util.ArrayList;
import java.util.List;
@@ -1813,6 +1813,10 @@
}
ss.checkedItemCount = mCheckedItemCount;
+ if (mRemoteAdapter != null) {
+ mRemoteAdapter.saveRemoteViewsCache();
+ }
+
return ss;
}
@@ -5974,6 +5978,9 @@
mDeferNotifyDataSetChanged = false;
// Otherwise, create a new RemoteViewsAdapter for binding
mRemoteAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+ if (mRemoteAdapter.isDataReady()) {
+ setAdapter(mRemoteAdapter);
+ }
}
/**
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index c557963..2266cea 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -813,6 +813,9 @@
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
+ if (mRemoteViewsAdapter != null) {
+ mRemoteViewsAdapter.saveRemoteViewsCache();
+ }
return new SavedState(superState, mWhichChild);
}
@@ -984,6 +987,9 @@
mDeferNotifyDataSetChanged = false;
// Otherwise, create a new RemoteViewsAdapter for binding
mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
+ if (mRemoteViewsAdapter.isDataReady()) {
+ setAdapter(mRemoteViewsAdapter);
+ }
}
@Override
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index aa95397..e9c753a 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -29,12 +29,16 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.widget.RemoteViewsService.RemoteViewsFactory;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -83,6 +87,26 @@
private Handler mWorkerQueue;
private Handler mMainQueue;
+ // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
+ // structures;
+ private static final HashMap<Pair<Intent.FilterComparison, Integer>, Parcel>
+ sCachedRemoteViewsCaches = new HashMap<Pair<Intent.FilterComparison, Integer>,
+ Parcel>();
+ private static final HashMap<Pair<Intent.FilterComparison, Integer>, Runnable>
+ sRemoteViewsCacheRemoveRunnables = new HashMap<Pair<Intent.FilterComparison, Integer>,
+ Runnable>();
+ private static HandlerThread sCacheRemovalThread;
+ private static Handler sCacheRemovalQueue;
+
+ // We keep the cache around for a duration after onSaveInstanceState for use on re-inflation.
+ // If a new RemoteViewsAdapter with the same intent / widget id isn't constructed within this
+ // duration, the cache is dropped.
+ private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
+
+ // Used to indicate to the AdapterView that it can use this Adapter immediately after
+ // construction (happens when we have a cached FixedSizeRemoteViewsCache).
+ private boolean mDataReady = false;
+
/**
* An interface for the RemoteAdapter to notify other classes when adapters
* are actually connected to/disconnected from their actual services.
@@ -331,7 +355,7 @@
/**
* The meta-data associated with the cache in it's current state.
*/
- private static class RemoteViewsMetaData {
+ private static class RemoteViewsMetaData implements Parcelable {
int count;
int viewTypeCount;
boolean hasStableIds;
@@ -350,6 +374,51 @@
reset();
}
+ public RemoteViewsMetaData(Parcel src) {
+ count = src.readInt();
+ viewTypeCount = src.readInt();
+ hasStableIds = src.readInt() == 0 ? false : true;
+ mFirstViewHeight = src.readInt();
+ if (src.readInt() != 0) {
+ mUserLoadingView = new RemoteViews(src);
+ }
+ if (src.readInt() != 0) {
+ mFirstView = new RemoteViews(src);
+ }
+ int count = src.readInt();
+ for (int i = 0; i < count; i++) {
+ mTypeIdIndexMap.put(src.readInt(), src.readInt());
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(count);
+ dest.writeInt(viewTypeCount);
+ dest.writeInt(hasStableIds ? 1 : 0);
+ dest.writeInt(mFirstViewHeight);
+ dest.writeInt(mUserLoadingView != null ? 1 : 0);
+ if (mUserLoadingView != null) {
+ mUserLoadingView.writeToParcel(dest, flags);
+ }
+ dest.writeInt(mFirstView != null ? 1 : 0);
+ if (mFirstView != null) {
+ mFirstView.writeToParcel(dest, flags);
+ }
+
+ int count = mTypeIdIndexMap.size();
+ dest.writeInt(count);
+ for (Integer key: mTypeIdIndexMap.keySet()) {
+ dest.writeInt(key);
+ dest.writeInt(mTypeIdIndexMap.get(key));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
public void set(RemoteViewsMetaData d) {
synchronized (d) {
count = d.count;
@@ -460,7 +529,7 @@
/**
* The meta-data associated with a single item in the cache.
*/
- private static class RemoteViewsIndexMetaData {
+ private static class RemoteViewsIndexMetaData implements Parcelable {
int typeId;
long itemId;
boolean isRequested;
@@ -469,6 +538,22 @@
set(v, itemId, requested);
}
+ public RemoteViewsIndexMetaData(Parcel src) {
+ typeId = src.readInt();
+ itemId = src.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(typeId);
+ dest.writeLong(itemId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
public void set(RemoteViews v, long id, boolean requested) {
itemId = id;
if (v != null) {
@@ -478,12 +563,14 @@
}
isRequested = requested;
}
+
+
}
/**
*
*/
- private static class FixedSizeRemoteViewsCache {
+ private static class FixedSizeRemoteViewsCache implements Parcelable {
private static final String TAG = "FixedSizeRemoteViewsCache";
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
@@ -545,6 +632,57 @@
mLoadIndices = new HashSet<Integer>();
}
+ public FixedSizeRemoteViewsCache(Parcel src) {
+ mMaxCount = src.readInt();
+ mMaxCountSlack = src.readInt();
+ mPreloadLowerBound = src.readInt();
+ mPreloadUpperBound = src.readInt();
+ mMetaData = new RemoteViewsMetaData(src);
+ int count = src.readInt();
+ mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
+ for (int i = 0; i < count; i++) {
+ mIndexMetaData.put(src.readInt(), new RemoteViewsIndexMetaData(src));
+ }
+ count = src.readInt();
+ mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
+ for (int i = 0; i < count; i++) {
+ mIndexRemoteViews.put(src.readInt(), new RemoteViews(src));
+ }
+
+ mTemporaryMetaData = new RemoteViewsMetaData();
+ mRequestedIndices = new HashSet<Integer>();
+ mLastRequestedIndex = -1;
+ mLoadIndices = new HashSet<Integer>();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMaxCount);
+ dest.writeInt(mMaxCountSlack);
+ dest.writeInt(mPreloadLowerBound);
+ dest.writeInt(mPreloadUpperBound);
+ mMetaData.writeToParcel(dest, 0);
+
+ // We write the index data and cache
+ int count = mIndexMetaData.size();
+ dest.writeInt(count);
+ for (Integer key: mIndexMetaData.keySet()) {
+ dest.writeInt(key);
+ mIndexMetaData.get(key).writeToParcel(dest, flags);
+ }
+ count = mIndexRemoteViews.size();
+ dest.writeInt(count);
+ for (Integer key: mIndexRemoteViews.keySet()) {
+ dest.writeInt(key);
+ mIndexRemoteViews.get(key).writeToParcel(dest, flags);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
public void insert(int position, RemoteViews v, long itemId, boolean isRequested) {
// Trim the cache if we go beyond the count
if (mIndexRemoteViews.size() >= mMaxCount) {
@@ -747,11 +885,30 @@
mWorkerQueue = new Handler(mWorkerThread.getLooper());
mMainQueue = new Handler(Looper.myLooper(), this);
+ if (sCacheRemovalThread == null) {
+ sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
+ sCacheRemovalThread.start();
+ sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
+ }
+
// Initialize the cache and the service connection on startup
- mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
- requestBindService();
+
+ Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison, Integer>
+ (new Intent.FilterComparison(mIntent), mAppWidgetId);
+
+ synchronized(sCachedRemoteViewsCaches) {
+ if (sCachedRemoteViewsCaches.containsKey(key)) {
+ Parcel src = sCachedRemoteViewsCaches.get(key);
+ src.setDataPosition(0);
+ mCache = new FixedSizeRemoteViewsCache(src);
+ mDataReady = true;
+ } else {
+ mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+ requestBindService();
+ }
+ }
}
@Override
@@ -765,6 +922,44 @@
}
}
+ public boolean isDataReady() {
+ return mDataReady;
+ }
+
+ public void saveRemoteViewsCache() {
+ final Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison,
+ Integer> (new Intent.FilterComparison(mIntent), mAppWidgetId);
+
+ synchronized(sCachedRemoteViewsCaches) {
+ // If we already have a remove runnable posted for this key, remove it.
+ if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+ sCacheRemovalQueue.removeCallbacks(sRemoteViewsCacheRemoveRunnables.get(key));
+ sRemoteViewsCacheRemoveRunnables.remove(key);
+ }
+
+ Parcel p = Parcel.obtain();
+ synchronized(mCache) {
+ mCache.writeToParcel(p, 0);
+ }
+ sCachedRemoteViewsCaches.put(key, p);
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (sCachedRemoteViewsCaches) {
+ if (sCachedRemoteViewsCaches.containsKey(key)) {
+ sCachedRemoteViewsCaches.remove(key);
+ }
+ if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+ sRemoteViewsCacheRemoveRunnables.remove(key);
+ }
+ }
+ }
+ };
+ sRemoteViewsCacheRemoveRunnables.put(key, r);
+ sCacheRemovalQueue.postDelayed(r, REMOTE_VIEWS_CACHE_DURATION);
+ }
+ }
+
private void loadNextIndexInBackground() {
mWorkerQueue.post(new Runnable() {
@Override