Java tutorial
/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os; import android.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; import java.util.function.Consumer; /** * 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: * * <ul> * <li> 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()}. * <li> 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. * <li> 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. * </ul> * * <p>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}. * * <p>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. */ public class RemoteCallbackList<E extends IInterface> { private static final String TAG = "RemoteCallbackList"; @UnsupportedAppUsage /*package*/ ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; private StringBuilder mRecentCallers; private final class Callback implements IBinder.DeathRecipient { final E mCallback; final Object mCookie; Callback(E callback, Object cookie) { mCallback = callback; mCookie = cookie; } public void binderDied() { synchronized (mCallbacks) { mCallbacks.remove(mCallback.asBinder()); } onCallbackDied(mCallback, mCookie); } } /** * Simple version of {@link RemoteCallbackList#register(E, Object)} * that does not take a cookie object. */ public boolean register(E callback) { return register(callback, null); } /** * 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. * * @param callback The callback interface to be added to the list. Must * not be null -- passing null here will cause a NullPointerException. * Most services will want to check for null before calling this with * an object given from a client, so that clients can't crash the * service with bad data. * * @param cookie Optional additional data to be associated with this * callback. * * @return Returns true if the callback was successfully added to the list. * Returns false if it was not added, either because {@link #kill} had * previously been called or the callback's process has gone away. * * @see #unregister * @see #kill * @see #onCallbackDied */ public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } // Flag unusual case that could be caused by a leak. b/36778087 logExcessiveCallbacks(); 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; } } } /** * 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. * * @param callback The callback to be removed from the list. Passing * null here will cause a NullPointerException, so you will generally want * to check for null before calling. * * @return Returns true if the callback was found and unregistered. Returns * false if the given callback was not found on the list. * * @see #register */ public boolean unregister(E callback) { synchronized (mCallbacks) { Callback cb = mCallbacks.remove(callback.asBinder()); if (cb != null) { cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; } } /** * 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. * * @see #register */ public void kill() { 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; } } /** * Old version of {@link #onCallbackDied(E, Object)} that * does not provide a cookie. */ public void onCallbackDied(E callback) { } /** * Called when the process hosting a callback in the list has gone away. * The default implementation calls {@link #onCallbackDied(E)} * for backwards compatibility. * * @param callback The callback whose process has died. Note that, since * its process has died, you can not make any calls on to this interface. * You can, however, retrieve its IBinder and compare it with another * IBinder to see if it is the same object. * @param cookie The cookie object original provided to * {@link #register(E, Object)}. * * @see #register */ public void onCallbackDied(E callback, Object cookie) { onCallbackDied(callback); } /** * 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. * * <p>A typical loop delivering a broadcast looks like this: * * <pre> * 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();</pre> * * @return Returns the number of callbacks in the broadcast, to be used * with {@link #getBroadcastItem} to determine the range of indices you * can supply. * * @see #getBroadcastItem * @see #finishBroadcast */ public int beginBroadcast() { 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; } } /** * Retrieve an item in the active broadcast that was previously started * with {@link #beginBroadcast}. This can <em>only</em> be called after * the broadcast is started, and its data is no longer valid after * calling {@link #finishBroadcast}. * * <p>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. * * @param index Which of the registered callbacks you would like to * retrieve. Ranges from 0 to 1-{@link #beginBroadcast}. * * @return Returns the callback interface that you can call. This will * always be non-null. * * @see #beginBroadcast */ public E getBroadcastItem(int index) { return ((Callback) mActiveBroadcast[index]).mCallback; } /** * Retrieve the cookie associated with the item * returned by {@link #getBroadcastItem(int)}. * * @see #getBroadcastItem */ public Object getBroadcastCookie(int index) { return ((Callback) mActiveBroadcast[index]).mCookie; } /** * 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. * * @see #beginBroadcast */ public void finishBroadcast() { synchronized (mCallbacks) { 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; } } /** * Performs {@code action} on each callback, calling * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * * @hide */ public void broadcast(Consumer<E> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { action.accept(getBroadcastItem(i)); } } finally { finishBroadcast(); } } /** * Performs {@code action} for each cookie associated with a callback, calling * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * * @hide */ public <C> void broadcastForEachCookie(Consumer<C> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { action.accept((C) getBroadcastCookie(i)); } } finally { finishBroadcast(); } } /** * 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. * <p> * This function is useful to decide whether to schedule a broadcast if this * requires doing some work which otherwise would not be performed. * </p> * * @return The size. */ public int getRegisteredCallbackCount() { synchronized (mCallbacks) { if (mKilled) { return 0; } return mCallbacks.size(); } } /** * Return a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastItem} and should not be used * interchangeably with it. This method returns the registered callback at the given * index, not the current broadcast state. This means that it is not itself thread-safe: * any call to {@link #register} or {@link #unregister} will change these indices, so you * must do your own thread safety between these to protect from such changes. * * @param index Index of which callback registration to return, from 0 to * {@link #getRegisteredCallbackCount()} - 1. * * @return Returns whatever callback is associated with this index, or null if * {@link #kill()} has been called. */ public E getRegisteredCallbackItem(int index) { synchronized (mCallbacks) { if (mKilled) { return null; } return mCallbacks.valueAt(index).mCallback; } } /** * Return any cookie associated with a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastCookie} and should not be used * interchangeably with it. This method returns the current cookie registered at the given * index, not the current broadcast state. This means that it is not itself thread-safe: * any call to {@link #register} or {@link #unregister} will change these indices, so you * must do your own thread safety between these to protect from such changes. * * @param index Index of which registration cookie to return, from 0 to * {@link #getRegisteredCallbackCount()} - 1. * * @return Returns whatever cookie object is associated with this index, or null if * {@link #kill()} has been called. */ public Object getRegisteredCallbackCookie(int index) { synchronized (mCallbacks) { if (mKilled) { return null; } return mCallbacks.valueAt(index).mCookie; } } /** @hide */ public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size()); pw.print(prefix); pw.print("killed: "); pw.println(mKilled); pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount); } private void logExcessiveCallbacks() { final long size = mCallbacks.size(); final long TOO_MANY = 3000; final long MAX_CHARS = 1000; if (size >= TOO_MANY) { if (size == TOO_MANY && mRecentCallers == null) { mRecentCallers = new StringBuilder(); } if (mRecentCallers != null && mRecentCallers.length() < MAX_CHARS) { mRecentCallers.append(Debug.getCallers(5)); mRecentCallers.append('\n'); if (mRecentCallers.length() >= MAX_CHARS) { Slog.wtf(TAG, "More than " + TOO_MANY + " remote callbacks registered. Recent callers:\n" + mRecentCallers.toString()); mRecentCallers = null; } } } } }