Merge "Re-enabling queued unbinding of services after requests to the service. (3394210)" into honeycomb

This commit is contained in:
Winson Chung
2011-01-26 12:30:26 -08:00
committed by Android (Google) Code Review
8 changed files with 403 additions and 279 deletions

View File

@ -238854,7 +238854,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
return="void"
return="boolean"
abstract="false"
native="false"
synchronized="false"
@ -240832,7 +240832,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
return="void"
return="boolean"
abstract="false"
native="false"
synchronized="false"

View File

@ -238864,7 +238864,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
return="void"
return="boolean"
abstract="false"
native="false"
synchronized="false"
@ -240842,7 +240842,7 @@
>
</method>
<method name="onRemoteAdapterConnected"
return="void"
return="boolean"
abstract="false"
native="false"
synchronized="false"

View File

@ -5289,12 +5289,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
public void onRemoteAdapterConnected() {
public boolean onRemoteAdapterConnected() {
if (mRemoteAdapter != mAdapter) {
setAdapter(mRemoteAdapter);
return false;
} else if (mRemoteAdapter != null) {
mRemoteAdapter.superNotifyDataSetChanged();
return true;
}
return false;
}
/**

View File

@ -53,6 +53,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
*/
int mWhichChild = 0;
/**
* The index of the child to restore after the asynchronous connection from the
* RemoteViewsAdapter has been.
*/
private int mRestoreWhichChild = -1;
/**
* Whether or not the first view(s) should be animated in
*/
@ -780,7 +786,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
// set mWhichChild
mWhichChild = ss.whichChild;
setDisplayedChild(mWhichChild);
// When using RemoteAdapters, the async connection process can lead to
// onRestoreInstanceState to be called before setAdapter(), so we need to save the previous
// values to restore the list position after we connect, and can skip setting the displayed
// child until then.
if (mRemoteViewsAdapter != null && mAdapter == null) {
mRestoreWhichChild = mWhichChild;
} else {
setDisplayedChild(mWhichChild);
}
}
/**
@ -957,12 +971,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
/**
* Called back when the adapter connects to the RemoteViewsService.
*/
public void onRemoteAdapterConnected() {
public boolean onRemoteAdapterConnected() {
if (mRemoteViewsAdapter != mAdapter) {
setAdapter(mRemoteViewsAdapter);
// Restore the previous position (see onRestoreInstanceState)
if (mRestoreWhichChild > -1) {
setDisplayedChild(mRestoreWhichChild);
mRestoreWhichChild = -1;
}
return false;
} else if (mRemoteViewsAdapter != null) {
mRemoteViewsAdapter.superNotifyDataSetChanged();
return true;
}
return false;
}
/**
@ -995,7 +1018,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
@Override
protected void onDetachedFromWindow() {
mAdapter = null;
setAdapter(null);
super.onDetachedFromWindow();
}
}

View File

@ -48,11 +48,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private static final String TAG = "RemoteViewsAdapter";
// The max number of items in the cache
private static final int sDefaultCacheSize = 36;
private static final int sDefaultCacheSize = 50;
// The delay (in millis) to wait until attempting to unbind from a service after a request.
// This ensures that we don't stay continually bound to the service and that it can be destroyed
// if we need the memory elsewhere in the system.
private static final int sUnbindServiceDelay = 5000;
private static final int sUnbindServiceDelay = 1000;
// Type defs for controlling different messages across the main and worker message queues
private static final int sDefaultMessageType = 0;
private static final int sUnbindServiceMessageType = 1;
@ -65,6 +65,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
private FixedSizeRemoteViewsCache mCache;
// A flag to determine whether we should notify data set changed after we connect
private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
// The set of requested views that are to be notified when the associated RemoteViews are
// loaded.
private RemoteViewsFrameLayoutRefSet mRequestedViews;
@ -79,7 +82,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
* are actually connected to/disconnected from their actual services.
*/
public interface RemoteAdapterConnectionCallback {
public void onRemoteAdapterConnected();
/**
* @return whether the adapter was set or not.
*/
public boolean onRemoteAdapterConnected();
public void onRemoteAdapterDisconnected();
}
@ -93,7 +99,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
*/
private static class RemoteViewsAdapterServiceConnection extends
IRemoteViewsAdapterConnection.Stub {
private boolean mConnected;
private boolean mIsConnected;
private boolean mIsConnecting;
private WeakReference<RemoteViewsAdapter> mAdapter;
private IRemoteViewsFactory mRemoteViewsFactory;
@ -101,27 +108,58 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
}
public void onServiceConnected(IBinder service) {
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
mConnected = true;
public synchronized void bind(Context context, int appWidgetId, Intent intent) {
if (!mIsConnecting) {
try {
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
mgr.bindRemoteViewsService(appWidgetId, intent, asBinder());
mIsConnecting = true;
} catch (Exception e) {
Log.e("RemoteViewsAdapterServiceConnection", "bind(): " + e.getMessage());
mIsConnecting = false;
mIsConnected = false;
}
}
}
// Queue up work that we need to do for the callback to run
public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
try {
final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
mgr.unbindRemoteViewsService(appWidgetId, intent);
mIsConnecting = false;
} catch (Exception e) {
Log.e("RemoteViewsAdapterServiceConnection", "unbind(): " + e.getMessage());
mIsConnecting = false;
mIsConnected = false;
}
}
public synchronized void onServiceConnected(IBinder service) {
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
// Remove any deferred unbind messages
final RemoteViewsAdapter adapter = mAdapter.get();
if (adapter == null) return;
// Queue up work that we need to do for the callback to run
adapter.mWorkerQueue.post(new Runnable() {
@Override
public void run() {
// Call back to the service to notify that the data set changed
if (adapter.mServiceConnection.isConnected()) {
if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
// Handle queued notifyDataSetChanged() if necessary
adapter.onNotifyDataSetChanged();
} else {
IRemoteViewsFactory factory =
adapter.mServiceConnection.getRemoteViewsFactory();
try {
// call back to the factory
factory.onDataSetChanged();
if (!factory.isCreated()) {
// We only call onDataSetChanged() if this is the factory was just
// create in response to this bind
factory.onDataSetChanged();
}
} catch (Exception e) {
Log.e(TAG, "Error notifying factory of data set changed in " +
"onServiceConnected(): " + e.getMessage());
e.printStackTrace();
// Return early to prevent anything further from being notified
// (effectively nothing has changed)
@ -130,13 +168,16 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Request meta data so that we have up to date data when calling back to
// the remote adapter callback
adapter.updateMetaData();
adapter.updateTemporaryMetaData();
// Post a runnable to call back to the view to notify it that we have
// connected
// Notify the host that we've connected
adapter.mMainQueue.post(new Runnable() {
@Override
public void run() {
synchronized (adapter.mCache) {
adapter.mCache.commitTemporaryMetaData();
}
final RemoteAdapterConnectionCallback callback =
adapter.mCallback.get();
if (callback != null) {
@ -145,35 +186,44 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
});
}
// Enqueue unbind message
adapter.enqueueDeferredUnbindServiceMessage();
mIsConnected = true;
mIsConnecting = false;
}
});
}
public void onServiceDisconnected() {
mConnected = false;
public synchronized void onServiceDisconnected() {
mIsConnected = false;
mIsConnecting = false;
mRemoteViewsFactory = null;
// Clear the main/worker queues
final RemoteViewsAdapter adapter = mAdapter.get();
if (adapter == null) return;
// Clear the main/worker queues
adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
adapter.mMainQueue.removeMessages(sDefaultMessageType);
adapter.mWorkerQueue.removeMessages(sDefaultMessageType);
adapter.mMainQueue.post(new Runnable() {
@Override
public void run() {
// Dequeue any unbind messages
adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
if (callback != null) {
callback.onRemoteAdapterDisconnected();
}
adapter.mCache.reset();
final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
if (callback != null) {
callback.onRemoteAdapterDisconnected();
}
}
});
}
public IRemoteViewsFactory getRemoteViewsFactory() {
public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
return mRemoteViewsFactory;
}
public boolean isConnected() {
return mConnected;
public synchronized boolean isConnected() {
return mIsConnected;
}
}
@ -270,7 +320,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
int count;
int viewTypeCount;
boolean hasStableIds;
boolean isDataDirty;
// Used to determine how to construct loading views. If a loading view is not specified
// by the user, then we try and load the first view, and use its height as the height for
@ -280,22 +329,31 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
int mFirstViewHeight;
// A mapping from type id to a set of unique type ids
private Map<Integer, Integer> mTypeIdIndexMap;
private final HashMap<Integer, Integer> mTypeIdIndexMap = new HashMap<Integer, Integer>();
public RemoteViewsMetaData() {
reset();
}
public void set(RemoteViewsMetaData d) {
synchronized (d) {
count = d.count;
viewTypeCount = d.viewTypeCount;
hasStableIds = d.hasStableIds;
setLoadingViewTemplates(d.mUserLoadingView, d.mFirstView);
}
}
public void reset() {
count = 0;
// by default there is at least one dummy view type
viewTypeCount = 1;
hasStableIds = true;
isDataDirty = false;
mUserLoadingView = null;
mFirstView = null;
mFirstViewHeight = 0;
mTypeIdIndexMap = new HashMap<Integer, Integer>();
mTypeIdIndexMap.clear();
}
public void setLoadingViewTemplates(RemoteViews loadingView, RemoteViews firstView) {
@ -385,6 +443,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
private RemoteViewsMetaData mMetaData;
private RemoteViewsMetaData mTemporaryMetaData;
// The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be
// greater than or equal to the set of RemoteViews.
@ -426,6 +485,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mPreloadLowerBound = 0;
mPreloadUpperBound = -1;
mMetaData = new RemoteViewsMetaData();
mTemporaryMetaData = new RemoteViewsMetaData();
mIndexMetaData = new HashMap<Integer, RemoteViewsIndexMetaData>();
mIndexRemoteViews = new HashMap<Integer, RemoteViews>();
mRequestedIndices = new HashSet<Integer>();
@ -461,6 +521,9 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
public RemoteViewsMetaData getMetaData() {
return mMetaData;
}
public RemoteViewsMetaData getTemporaryMetaData() {
return mTemporaryMetaData;
}
public RemoteViews getRemoteViewsAt(int position) {
if (mIndexRemoteViews.containsKey(position)) {
return mIndexRemoteViews.get(position);
@ -474,6 +537,14 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return null;
}
public void commitTemporaryMetaData() {
synchronized (mTemporaryMetaData) {
synchronized (mMetaData) {
mMetaData.set(mTemporaryMetaData);
}
}
}
private int getRemoteViewsBitmapMemoryUsage() {
// Calculate the memory usage of all the RemoteViews bitmaps being cached
int mem = 0;
@ -505,12 +576,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mLoadIndices.add(position);
}
}
public void queuePositionsToBePreloadedFromRequestedPosition(int position) {
public boolean queuePositionsToBePreloadedFromRequestedPosition(int position) {
// Check if we need to preload any items
if (mPreloadLowerBound <= position && position <= mPreloadUpperBound) {
int center = (mPreloadUpperBound + mPreloadLowerBound) / 2;
if (Math.abs(position - center) < mMaxCountSlack) {
return;
return false;
}
}
@ -537,6 +608,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// But remove all the indices that have already been loaded and are cached
mLoadIndices.removeAll(mIndexRemoteViews.keySet());
}
return true;
}
public int getNextIndexToLoad() {
// We try and prioritize items that have been requested directly, instead
@ -616,100 +688,114 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
// Get the next index to load
int position = -1;
synchronized (mCache) {
position = mCache.getNextIndexToLoad();
}
if (position > -1) {
// Load the item, and notify any existing RemoteViewsFrameLayouts
updateRemoteViews(position);
if (mServiceConnection.isConnected()) {
// Get the next index to load
int position = -1;
synchronized (mCache) {
position = mCache.getNextIndexToLoad();
}
if (position > -1) {
// Load the item, and notify any existing RemoteViewsFrameLayouts
updateRemoteViews(position);
// Queue up for the next one to load
loadNextIndexInBackground();
// Queue up for the next one to load
loadNextIndexInBackground();
} else {
// No more items to load, so queue unbind
enqueueDeferredUnbindServiceMessage();
}
}
}
});
}
private void updateMetaData() {
if (mServiceConnection.isConnected()) {
try {
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
private void processException(String method, Exception e) {
Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
// get the properties/first view (so that we can use it to
// measure our dummy views)
boolean hasStableIds = factory.hasStableIds();
int viewTypeCount = factory.getViewTypeCount();
int count = factory.getCount();
RemoteViews loadingView = factory.getLoadingView();
RemoteViews firstView = null;
if ((count > 0) && (loadingView == null)) {
firstView = factory.getViewAt(0);
}
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
metaData.hasStableIds = hasStableIds;
metaData.viewTypeCount = viewTypeCount + 1;
metaData.count = count;
metaData.setLoadingViewTemplates(loadingView, firstView);
}
} catch (Exception e) {
// print the error
Log.e(TAG, "Error in requestMetaData(): " + e.getMessage());
// reset any members after the failed call
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
metaData.reset();
}
// If we encounter a crash when updating, we should reset the metadata & cache and trigger
// a notifyDataSetChanged to update the widget accordingly
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
metaData.reset();
}
synchronized (mCache) {
mCache.reset();
}
mMainQueue.post(new Runnable() {
@Override
public void run() {
superNotifyDataSetChanged();
}
});
}
private void updateTemporaryMetaData() {
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
// get the properties/first view (so that we can use it to
// measure our dummy views)
boolean hasStableIds = factory.hasStableIds();
int viewTypeCount = factory.getViewTypeCount();
int count = factory.getCount();
RemoteViews loadingView = factory.getLoadingView();
RemoteViews firstView = null;
if ((count > 0) && (loadingView == null)) {
firstView = factory.getViewAt(0);
}
final RemoteViewsMetaData tmpMetaData = mCache.getTemporaryMetaData();
synchronized (tmpMetaData) {
tmpMetaData.hasStableIds = hasStableIds;
// We +1 because the base view type is the loading view
tmpMetaData.viewTypeCount = viewTypeCount + 1;
tmpMetaData.count = count;
tmpMetaData.setLoadingViewTemplates(loadingView, firstView);
}
} catch (Exception e) {
processException("updateMetaData", e);
}
}
private void updateRemoteViews(final int position) {
if (mServiceConnection.isConnected()) {
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
if (!mServiceConnection.isConnected()) return;
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
// Load the item information from the remote service
RemoteViews remoteViews = null;
long itemId = 0;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
} catch (Throwable t) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + t.getMessage());
t.printStackTrace();
// Load the item information from the remote service
RemoteViews remoteViews = null;
long itemId = 0;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
} catch (Exception e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
}
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
}
if (remoteViews == null) {
// If a null view was returned, we break early to prevent it from getting
// into our cache and causing problems later. The effect is that the child at this
// position will remain as a loading view until it is updated.
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
"returned from RemoteViewsFactory.");
return;
}
synchronized (mCache) {
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId);
if (remoteViews == null) {
// If a null view was returned, we break early to prevent it from getting
// into our cache and causing problems later. The effect is that the child at this
// position will remain as a loading view until it is updated.
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
"returned from RemoteViewsFactory.");
return;
}
synchronized (mCache) {
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId);
// Notify all the views that we have previously returned for this index that
// there is new data for it.
final RemoteViews rv = remoteViews;
final int typeId = mCache.getMetaDataAt(position).typeId;
mMainQueue.post(new Runnable() {
@Override
public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
enqueueDeferredUnbindServiceMessage();
}
});
}
// Notify all the views that we have previously returned for this index that
// there is new data for it.
final RemoteViews rv = remoteViews;
final int typeId = mCache.getMetaDataAt(position).typeId;
mMainQueue.post(new Runnable() {
@Override
public void run() {
mRequestedViews.notifyOnRemoteViewsLoaded(position, rv, typeId);
}
});
}
}
@ -718,7 +804,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public int getCount() {
requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.count;
@ -731,7 +816,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public long getItemId(int position) {
requestBindService();
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
return mCache.getMetaDataAt(position).itemId;
@ -741,7 +825,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public int getItemViewType(int position) {
requestBindService();
int typeId = 0;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
@ -773,13 +856,23 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public View getView(int position, View convertView, ViewGroup parent) {
requestBindService();
if (mServiceConnection.isConnected()) {
// "Request" an index so that we can queue it for loading, initiate subsequent
// preloading, etc.
synchronized (mCache) {
// "Request" an index so that we can queue it for loading, initiate subsequent
// preloading, etc.
synchronized (mCache) {
boolean isInCache = mCache.containsRemoteViewAt(position);
boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
if (!isConnected) {
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
// in turn trigger another request to getView()
requestBindService();
} else {
// Queue up other indices to be preloaded based on this position
mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
hasNewItems = mCache.queuePositionsToBePreloadedFromRequestedPosition(position);
}
if (isInCache) {
View convertViewChild = null;
int convertViewTypeId = 0;
RemoteViewsFrameLayout layout = null;
@ -792,51 +885,47 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Second, we try and retrieve the RemoteViews from the cache, returning a loading
// view and queueing it to be loaded if it has not already been loaded.
if (mCache.containsRemoteViewAt(position)) {
Context context = parent.getContext();
RemoteViews rv = mCache.getRemoteViewsAt(position);
int typeId = mCache.getMetaDataAt(position).typeId;
Context context = parent.getContext();
RemoteViews rv = mCache.getRemoteViewsAt(position);
int typeId = mCache.getMetaDataAt(position).typeId;
// Reuse the convert view where possible
if (layout != null) {
if (convertViewTypeId == typeId) {
rv.reapply(context, convertViewChild);
return layout;
}
// Reuse the convert view where possible
if (layout != null) {
if (convertViewTypeId == typeId) {
rv.reapply(context, convertViewChild);
return layout;
}
// Otherwise, create a new view to be returned
View newView = rv.apply(context, parent);
newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
if (layout != null) {
layout.removeAllViews();
} else {
layout = new RemoteViewsFrameLayout(context);
}
layout.addView(newView);
return layout;
layout.removeAllViews();
} else {
// If the cache does not have the RemoteViews at this position, then create a
// loading view and queue the actual position to be loaded in the background
RemoteViewsFrameLayout loadingView = null;
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent);
}
mRequestedViews.add(position, loadingView);
mCache.queueRequestedPositionToLoad(position);
loadNextIndexInBackground();
return loadingView;
layout = new RemoteViewsFrameLayout(context);
}
// Otherwise, create a new view to be returned
View newView = rv.apply(context, parent);
newView.setTagInternal(com.android.internal.R.id.rowTypeId, new Integer(typeId));
layout.addView(newView);
if (hasNewItems) loadNextIndexInBackground();
return layout;
} else {
// If the cache does not have the RemoteViews at this position, then create a
// loading view and queue the actual position to be loaded in the background
RemoteViewsFrameLayout loadingView = null;
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent);
}
mRequestedViews.add(position, loadingView);
mCache.queueRequestedPositionToLoad(position);
loadNextIndexInBackground();
return loadingView;
}
}
return new View(parent.getContext());
}
public int getViewTypeCount() {
requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.viewTypeCount;
@ -844,7 +933,6 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
public boolean hasStableIds() {
requestBindService();
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
return metaData.hasStableIds;
@ -855,44 +943,67 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return getCount() <= 0;
}
public void notifyDataSetChanged() {
mWorkerQueue.post(new Runnable() {
private void onNotifyDataSetChanged() {
// Complete the actual notifyDataSetChanged() call initiated earlier
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
factory.onDataSetChanged();
} catch (Exception e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
// Return early to prevent from further being notified (since nothing has
// changed)
return;
}
// Flush the cache so that we can reload new items from the service
synchronized (mCache) {
mCache.reset();
}
// Re-request the new metadata (only after the notification to the factory)
updateTemporaryMetaData();
// Propagate the notification back to the base adapter
mMainQueue.post(new Runnable() {
@Override
public void run() {
// Complete the actual notifyDataSetChanged() call initiated earlier
if (mServiceConnection.isConnected()) {
IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
try {
factory.onDataSetChanged();
} catch (Exception e) {
Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
// Return early to prevent from further being notified (since nothing has
// changed)
return;
}
}
// Flush the cache so that we can reload new items from the service
synchronized (mCache) {
mCache.reset();
mCache.commitTemporaryMetaData();
}
// Re-request the new metadata (only after the notification to the factory)
updateMetaData();
// Propagate the notification back to the base adapter
mMainQueue.post(new Runnable() {
@Override
public void run() {
superNotifyDataSetChanged();
}
});
superNotifyDataSetChanged();
enqueueDeferredUnbindServiceMessage();
}
});
// Note: we do not call super.notifyDataSetChanged() until the RemoteViewsFactory has had
// a chance to update itself and return new meta data associated with the new data.
// Reset the notify flagflag
mNotifyDataSetChangedAfterOnServiceConnected = false;
}
public void notifyDataSetChanged() {
// Dequeue any unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
// If we are not connected, queue up the notifyDataSetChanged to be handled when we do
// connect
if (!mServiceConnection.isConnected()) {
if (mNotifyDataSetChangedAfterOnServiceConnected) {
return;
}
mNotifyDataSetChangedAfterOnServiceConnected = true;
requestBindService();
return;
}
mWorkerQueue.post(new Runnable() {
@Override
public void run() {
onNotifyDataSetChanged();
}
});
}
void superNotifyDataSetChanged() {
@ -904,9 +1015,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
boolean result = false;
switch (msg.what) {
case sUnbindServiceMessageType:
final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
if (mServiceConnection.isConnected()) {
mgr.unbindRemoteViewsService(mAppWidgetId, mIntent);
mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
}
result = true;
break;
@ -917,20 +1027,19 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
private void enqueueDeferredUnbindServiceMessage() {
/* Temporarily disable delayed service unbinding
// Remove any existing deferred-unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
*/
}
private boolean requestBindService() {
// Try binding the service (which will start it if it's not already running)
if (!mServiceConnection.isConnected()) {
final AppWidgetManager mgr = AppWidgetManager.getInstance(mContext);
mgr.bindRemoteViewsService(mAppWidgetId, mIntent, mServiceConnection.asBinder());
mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
}
// Remove any existing deferred-unbind messages
mMainQueue.removeMessages(sUnbindServiceMessageType);
return mServiceConnection.isConnected();
}
}

View File

@ -17,11 +17,11 @@
package android.widget;
import java.util.HashMap;
import java.util.Map;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.util.Pair;
import com.android.internal.widget.IRemoteViewsFactory;
@ -35,8 +35,13 @@ public abstract class RemoteViewsService extends Service {
private static final String LOG_TAG = "RemoteViewsService";
// multimap implementation for reference counting
private HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>> mRemoteViewFactories;
// Used for reference counting of RemoteViewsFactories
// Because we are now unbinding when we are not using the Service (to allow them to be
// reclaimed), the references to the factories that are created need to be stored and used when
// the service is restarted (in response to user input for example). When the process is
// destroyed, so is this static cache of RemoteViewsFactories.
private static final HashMap<Intent.FilterComparison, RemoteViewsFactory> mRemoteViewFactories =
new HashMap<Intent.FilterComparison, RemoteViewsFactory>();
private final Object mLock = new Object();
/**
@ -126,9 +131,13 @@ public abstract class RemoteViewsService extends Service {
* A private proxy class for the private IRemoteViewsFactory interface through the
* public RemoteViewsFactory interface.
*/
private class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) {
private static class RemoteViewsFactoryAdapter extends IRemoteViewsFactory.Stub {
public RemoteViewsFactoryAdapter(RemoteViewsFactory factory, boolean isCreated) {
mFactory = factory;
mIsCreated = isCreated;
}
public synchronized boolean isCreated() {
return mIsCreated;
}
public synchronized void onDataSetChanged() {
mFactory.onDataSetChanged();
@ -155,58 +164,28 @@ public abstract class RemoteViewsService extends Service {
}
private RemoteViewsFactory mFactory;
}
public RemoteViewsService() {
mRemoteViewFactories =
new HashMap<Intent.FilterComparison, Pair<RemoteViewsFactory, Integer>>();
private boolean mIsCreated;
}
@Override
public IBinder onBind(Intent intent) {
synchronized (mLock) {
// increment the reference count to the particular factory associated with this intent
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
Pair<RemoteViewsFactory, Integer> factoryRef = null;
RemoteViewsFactory factory = null;
boolean isCreated = false;
if (!mRemoteViewFactories.containsKey(fc)) {
factory = onGetViewFactory(intent);
factoryRef = new Pair<RemoteViewsFactory, Integer>(factory, 1);
mRemoteViewFactories.put(fc, factoryRef);
mRemoteViewFactories.put(fc, factory);
factory.onCreate();
isCreated = false;
} else {
Pair<RemoteViewsFactory, Integer> oldFactoryRef = mRemoteViewFactories.get(fc);
factory = oldFactoryRef.first;
int newRefCount = oldFactoryRef.second.intValue() + 1;
factoryRef = new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
mRemoteViewFactories.put(fc, factoryRef);
factory = mRemoteViewFactories.get(fc);
isCreated = true;
}
return new RemoteViewsFactoryAdapter(factory);
return new RemoteViewsFactoryAdapter(factory, isCreated);
}
}
@Override
public boolean onUnbind(Intent intent) {
synchronized (mLock) {
Intent.FilterComparison fc = new Intent.FilterComparison(intent);
if (mRemoteViewFactories.containsKey(fc)) {
// this alleviates the user's responsibility of having to clear all factories
Pair<RemoteViewsFactory, Integer> oldFactoryRef =
mRemoteViewFactories.get(fc);
int newRefCount = oldFactoryRef.second.intValue() - 1;
if (newRefCount <= 0) {
oldFactoryRef.first.onDestroy();
mRemoteViewFactories.remove(fc);
} else {
Pair<RemoteViewsFactory, Integer> factoryRef =
new Pair<RemoteViewsFactory, Integer>(oldFactoryRef.first, newRefCount);
mRemoteViewFactories.put(fc, factoryRef);
}
}
}
return super.onUnbind(intent);
}
/**
* To be implemented by the derived service to generate appropriate factories for
* the data.

View File

@ -27,5 +27,6 @@ interface IRemoteViewsFactory {
int getViewTypeCount();
long getItemId(int position);
boolean hasStableIds();
boolean isCreated();
}

View File

@ -22,6 +22,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -62,6 +63,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
@ -121,18 +123,15 @@ class AppWidgetService extends IAppWidgetService.Stub
* globally and may lead us to leak AppWidgetService instances (if there were more than one).
*/
static class ServiceConnectionProxy implements ServiceConnection {
private final AppWidgetService mAppWidgetService;
private final Pair<Integer, Intent.FilterComparison> mKey;
private final IBinder mConnectionCb;
ServiceConnectionProxy(AppWidgetService appWidgetService,
Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
mAppWidgetService = appWidgetService;
ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
mKey = key;
mConnectionCb = connectionCb;
}
public void onServiceConnected(ComponentName name, IBinder service) {
IRemoteViewsAdapterConnection cb =
final IRemoteViewsAdapterConnection cb =
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
try {
cb.onServiceConnected(service);
@ -141,19 +140,13 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
public void onServiceDisconnected(ComponentName name) {
IRemoteViewsAdapterConnection cb =
disconnect();
}
public void disconnect() {
final IRemoteViewsAdapterConnection cb =
IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
try {
cb.onServiceDisconnected();
mAppWidgetService.mServiceConnectionUpdateHandler.post(new Runnable() {
public void run() {
// We don't want to touch mBoundRemoteViewsServices from any other thread
// so queue this to run on the main thread.
if (mAppWidgetService.mBoundRemoteViewsServices.containsKey(mKey)) {
mAppWidgetService.mBoundRemoteViewsServices.remove(mKey);
}
}
});
} catch (RemoteException e) {
e.printStackTrace();
}
@ -163,7 +156,6 @@ class AppWidgetService extends IAppWidgetService.Stub
// Manages connections to RemoteViewsServices
private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
private final Handler mServiceConnectionUpdateHandler = new Handler();
Context mContext;
Locale mLocale;
@ -456,13 +448,24 @@ class AppWidgetService extends IAppWidgetService.Stub
throw new IllegalArgumentException("Unknown component " + componentName);
}
// Bind to the RemoteViewsService (which will trigger a callback to the
// RemoteViewsAdapter)
// If there is already a connection made for this service intent, then disconnect from
// that first. (This does not allow multiple connections to the same service under
// the same key)
ServiceConnectionProxy conn = null;
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
new FilterComparison(intent));
final ServiceConnection conn = new ServiceConnectionProxy(this, key, connection);
if (mBoundRemoteViewsServices.containsKey(key)) {
conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
conn.disconnect();
mContext.unbindService(conn);
mBoundRemoteViewsServices.remove(key);
}
// Bind to the RemoteViewsService (which will trigger a callback to the
// RemoteViewsAdapter.onServiceConnected())
final long token = Binder.clearCallingIdentity();
try {
conn = new ServiceConnectionProxy(key, connection);
mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
mBoundRemoteViewsServices.put(key, conn);
} finally {
@ -473,37 +476,43 @@ class AppWidgetService extends IAppWidgetService.Stub
public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
synchronized (mAppWidgetIds) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id == null) {
throw new IllegalArgumentException("bad appWidgetId");
}
// Unbind from the RemoteViewsService (which will trigger a callback to the bound
// RemoteViewsAdapter)
Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
new FilterComparison(intent));
if (mBoundRemoteViewsServices.containsKey(key)) {
final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
mBoundRemoteViewsServices.remove(key);
conn.onServiceDisconnected(null);
// We don't need to use the appWidgetId until after we are sure there is something
// to unbind. Note that this may mask certain issues with apps calling unbind()
// more than necessary.
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
if (id == null) {
throw new IllegalArgumentException("bad appWidgetId");
}
ServiceConnectionProxy conn =
(ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
conn.disconnect();
mContext.unbindService(conn);
mBoundRemoteViewsServices.remove(key);
} else {
Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
}
}
}
private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
int appWidgetId = id.appWidgetId;
// Unbind all connections to Services bound to this AppWidgetId
Iterator<Pair<Integer, Intent.FilterComparison>> it =
mBoundRemoteViewsServices.keySet().iterator();
int appWidgetId = id.appWidgetId;
// Unbind all connections to AppWidgets bound to this id
while (it.hasNext()) {
final Pair<Integer, Intent.FilterComparison> key = it.next();
if (key.first.intValue() == appWidgetId) {
final ServiceConnection conn = mBoundRemoteViewsServices.get(key);
it.remove();
conn.onServiceDisconnected(null);
final ServiceConnectionProxy conn = (ServiceConnectionProxy)
mBoundRemoteViewsServices.get(key);
conn.disconnect();
mContext.unbindService(conn);
it.remove();
}
}
}