ContiguousFIFOAggregatorpublic class ContiguousFIFOAggregator extends Object An implementation of a task aggregator that executes tasks in the order that they are expected
. All tasks that is given to {@link #execute(Object, Runnable)} must correspond to a key. This
key must have been previously declared with {@link #expect(Object, Callback)}.
The task will be scheduled to run when its corresponding key becomes the first expected key
amongst the other keys in this aggregator.
If on {@link #execute(Object, Runnable)} the key is not expected, the task will be executed
immediately as an edge case.
A characteristic scenario is as follows:
- Expect keys A, B, C, and Z, in that order. Key A is now
the first expected key.
- Execute task 2 for key B. The first expected key is A,
which has no task associated with it, so we store task 2.
- Execute task 4 for key Z. We store task 4.
- Execute task 1 for key A. We run task 1 because its key A is
the first expected, then we remove key A from the list of keys that we expect.
- This causes key B to be the first expected key, and we see that we have previously
stored task 2 for that key. We run task 2 and remove key B.
- Key C is now the first expected key, but it has no task,
so nothing happens. Task 4, which was previously stored,
cannot run until its corresponding key Z becomes the first expected key. This can
happen if a task comes in for key C or if forget is called on key C.
ContiguousFIFOAggregator is not thread safe. |
Fields Summary |
---|
private final Queue | mExpected | private final android.util.SparseArray | mTasks | private static final String | TAG | private static final boolean | DEBUG |
Constructors Summary |
---|
public ContiguousFIFOAggregator()Create a new ContiguousFIFOAggregator.
The nature of the prioritization means that the number of stored keys and tasks may grow
unbounded. This may happen, for instance, if the top priority key is never given a task to
{@link #execute(Object, Runnable)}. However, in practice, if you are generating tasks in
response to UI elements appearing on the screen, you will only have a bounded set of keys.
UI elements that scroll off the screen will call {@link #forget(Object)} while new elements
will call {@link #expect(Object, Callback)}. This means that the expected
number of keys and tasks is
the maximum number of UI elements that you expect to show on the screen at any time.
mExpected = new ArrayDeque<T>();
mTasks = new SparseArray<Value>();
|
Methods Summary |
---|
private boolean | contains(T key)
return mTasks.get(key.hashCode()) != null;
| public void | execute(T key, java.lang.Runnable task)Attempt to execute a task corresponding to a previously declared key. If the key is the
first expected key, the task will be executed immediately. Otherwise, the task is stored
until its key becomes the first expected key. Execution of a task results in the removal
of that key, which potentially allows another key to now become the first expected key,
and may cause one or more other tasks to be executed.
If the key is not expected, the task will be executed immediately as an edge case.
Trace.beginSection("pool execute");
final int hash = key.hashCode();
final Value value = mTasks.get(hash);
if (value == null || task == null) {
if (task != null) {
task.run();
}
Trace.endSection();
return;
}
value.task = task;
if (DEBUG) {
Log.d(TAG, String.format("ContiguousFIFOAggregator ++ tasks: %s", prettyPrint()));
}
maybeExecuteNow();
Trace.endSection();
| public void | expect(T key, com.android.bitmap.ContiguousFIFOAggregator$Callback callback)Declare a key to be expected in the future. The order in which you expect keys is very
important. Keys that are declared first are guaranteed to have their tasks run first. You
must call either {@link #forget(Object)} or {@link #execute(Object, Runnable)}
with this key in the future, or you will leak the key.
If you call expect with a previously expected key, the key will be placed at the back of
the expected queue and its callback will be replaced with this one.
if (key == null) {
throw new IllegalArgumentException("Do not use null keys.");
}
Trace.beginSection("pool expect");
final int hash = key.hashCode();
if (contains(key)) {
mExpected.remove(key);
mTasks.remove(hash);
}
final boolean isFirst = mExpected.isEmpty();
mExpected.offer(key);
mTasks.put(hash, new Value(callback, null));
if (DEBUG) {
Log.d(TAG, String.format("ContiguousFIFOAggregator >> tasks: %s", prettyPrint()));
}
if (isFirst) {
onFirstExpectedChanged(key);
}
Trace.endSection();
| public void | forget(T key)Remove a previously declared key that we no longer expect to execute a task for. This
potentially allows another key to now become the first expected key,
and so this may trigger one or more tasks to be executed.
if (key == null) {
throw new IllegalArgumentException("Do not use null keys.");
}
if (!contains(key)) {
return;
}
Trace.beginSection("pool forget");
final boolean removedFirst = key.equals(mExpected.peek());
mExpected.remove(key);
mTasks.delete(key.hashCode());
if (DEBUG) {
Log.d(TAG, String.format("ContiguousFIFOAggregator < tasks: %s", prettyPrint()));
}
final T second;
if (removedFirst && (second = mExpected.peek()) != null) {
// We removed the first key. The second key is now first.
onFirstExpectedChanged(second);
}
maybeExecuteNow();
Trace.endSection();
| private void | maybeExecuteNow()Triggered by {@link #execute(Object, Runnable)} and {@link #forget(Object)}. The keys or
tasks have changed, which may cause one or more tasks to be executed. This method will
continue to execute tasks associated with the first expected key to the last expected key,
stopping when it finds that the first expected key has not yet been assigned a task.
T first;
int count = 0;
while (!mExpected.isEmpty()) {
Trace.beginSection("pool maybeExecuteNow loop");
first = mExpected.peek();
if (count > 0) {
// When count == 0, the key is already first.
onFirstExpectedChanged(first);
}
final int hash = first.hashCode();
final Value value = mTasks.get(hash);
if (value.task == null) {
Trace.endSection();
break;
}
mExpected.poll();
mTasks.delete(hash);
if (DEBUG) {
Log.d(TAG, String.format("ContiguousFIFOAggregator - tasks: %s", prettyPrint()));
}
value.task.run();
count++;
Trace.endSection();
}
| private void | onFirstExpectedChanged(T key)This method should only be called once for any key.
final int hash = key.hashCode();
final Value value = mTasks.get(hash);
if (value == null) {
return;
}
final Callback<T> callback = value.callback;
if (callback == null) {
return;
}
if (DEBUG) {
Log.d(TAG, String.format("ContiguousFIFOAggregator first: %d", hash));
}
callback.onBecomeFirstExpected(key);
| private java.lang.String | prettyPrint()Get a pretty string representing the internal data.
if (mExpected.isEmpty()) {
return "{}";
}
StringBuilder buffer = new StringBuilder(mExpected.size() * 28);
buffer.append('{");
Iterator<T> it = mExpected.iterator();
while (it.hasNext()) {
final T key = it.next();
final int hash = key.hashCode();
buffer.append(hash);
buffer.append('=");
final Value value = mTasks.get(hash);
buffer.append(value);
if (it.hasNext()) {
buffer.append(", ");
}
}
buffer.append('}");
if (mExpected.size() != mTasks.size()) {
buffer.append(" error");
}
return buffer.toString();
|
|