RemoteCallbackListpublic class RemoteCallbackList extends Object Takes care of the grunt work of maintaining a list of remote interfaces,
typically for the use of performing callbacks from a
{@link android.app.Service} to its clients. In particular, this:
- Keeps track of a set of registered {@link IInterface} callbacks,
taking care to identify them through their underlying unique {@link IBinder}
(by calling {@link IInterface#asBinder IInterface.asBinder()}.
- Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to
each registered interface, so that it can be cleaned out of the list if its
process goes away.
- Performs locking of the underlying list of interfaces to deal with
multithreaded incoming calls, and a thread-safe way to iterate over a
snapshot of the list without holding its lock.
To use this class, simply create a single instance along with your
service, and call its {@link #register} and {@link #unregister} methods
as client register and unregister with your service. To call back on to
the registered clients, use {@link #beginBroadcast},
{@link #getBroadcastItem}, and {@link #finishBroadcast}.
If a registered callback's process goes away, this class will take
care of automatically removing it from the list. If you want to do
additional work in this situation, you can create a subclass that
implements the {@link #onCallbackDied} method. |
Fields Summary |
---|
android.util.ArrayMap | mCallbacks | private Object[] | mActiveBroadcast | private int | mBroadcastCount | private boolean | mKilled |
Methods Summary |
---|
public int | beginBroadcast()Prepare to start making calls to the currently registered callbacks.
This creates a copy of the callback list, which you can retrieve items
from using {@link #getBroadcastItem}. Note that only one broadcast can
be active at a time, so you must be sure to always call this from the
same thread (usually by scheduling with {@link Handler}) or
do your own synchronization. You must call {@link #finishBroadcast}
when done.
A typical loop delivering a broadcast looks like this:
int i = callbacks.beginBroadcast();
while (i > 0) {
i--;
try {
callbacks.getBroadcastItem(i).somethingHappened();
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
callbacks.finishBroadcast();
synchronized (mCallbacks) {
if (mBroadcastCount > 0) {
throw new IllegalStateException(
"beginBroadcast() called while already in a broadcast");
}
final int N = mBroadcastCount = mCallbacks.size();
if (N <= 0) {
return 0;
}
Object[] active = mActiveBroadcast;
if (active == null || active.length < N) {
mActiveBroadcast = active = new Object[N];
}
for (int i=0; i<N; i++) {
active[i] = mCallbacks.valueAt(i);
}
return N;
}
| public void | finishBroadcast()Clean up the state of a broadcast previously initiated by calling
{@link #beginBroadcast}. This must always be called when you are done
with a broadcast.
if (mBroadcastCount < 0) {
throw new IllegalStateException(
"finishBroadcast() called outside of a broadcast");
}
Object[] active = mActiveBroadcast;
if (active != null) {
final int N = mBroadcastCount;
for (int i=0; i<N; i++) {
active[i] = null;
}
}
mBroadcastCount = -1;
| public java.lang.Object | getBroadcastCookie(int index)Retrieve the cookie associated with the item
returned by {@link #getBroadcastItem(int)}.
return ((Callback)mActiveBroadcast[index]).mCookie;
| public E | getBroadcastItem(int index)Retrieve an item in the active broadcast that was previously started
with {@link #beginBroadcast}. This can only be called after
the broadcast is started, and its data is no longer valid after
calling {@link #finishBroadcast}.
Note that it is possible for the process of one of the returned
callbacks to go away before you call it, so you will need to catch
{@link RemoteException} when calling on to the returned object.
The callback list itself, however, will take care of unregistering
these objects once it detects that it is no longer valid, so you can
handle such an exception by simply ignoring it.
return ((Callback)mActiveBroadcast[index]).mCallback;
| public int | getRegisteredCallbackCount()Returns the number of registered callbacks. Note that the number of registered
callbacks may differ from the value returned by {@link #beginBroadcast()} since
the former returns the number of callbacks registered at the time of the call
and the second the number of callback to which the broadcast will be delivered.
This function is useful to decide whether to schedule a broadcast if this
requires doing some work which otherwise would not be performed.
synchronized (mCallbacks) {
if (mKilled) {
return 0;
}
return mCallbacks.size();
}
| public void | kill()Disable this callback list. All registered callbacks are unregistered,
and the list is disabled so that future calls to {@link #register} will
fail. This should be used when a Service is stopping, to prevent clients
from registering callbacks after it is stopped.
synchronized (mCallbacks) {
for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
Callback cb = mCallbacks.valueAt(cbi);
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
}
mCallbacks.clear();
mKilled = true;
}
| public void | onCallbackDied(E callback)Old version of {@link #onCallbackDied(E, Object)} that
does not provide a cookie.
| public void | onCallbackDied(E callback, java.lang.Object cookie)Called when the process hosting a callback in the list has gone away.
The default implementation calls {@link #onCallbackDied(E)}
for backwards compatibility.
onCallbackDied(callback);
| public boolean | register(E callback)Simple version of {@link RemoteCallbackList#register(E, Object)}
that does not take a cookie object.
return register(callback, null);
| public boolean | register(E callback, java.lang.Object cookie)Add a new callback to the list. This callback will remain in the list
until a corresponding call to {@link #unregister} or its hosting process
goes away. If the callback was already registered (determined by
checking to see if the {@link IInterface#asBinder callback.asBinder()}
object is already in the list), then it will be left as-is.
Registrations are not counted; a single call to {@link #unregister}
will remove a callback after any number calls to register it.
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
| public boolean | unregister(E callback)Remove from the list a callback that was previously added with
{@link #register}. This uses the
{@link IInterface#asBinder callback.asBinder()} object to correctly
find the previous registration.
Registrations are not counted; a single unregister call will remove
a callback after any number calls to {@link #register} for it.
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
|
|