mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Simplify DownloadManager.Task to use external state
PiperOrigin-RevId: 223797364
This commit is contained in:
parent
f196630863
commit
87a74ee021
1 changed files with 88 additions and 176 deletions
|
|
@ -30,7 +30,6 @@ import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
|
|
@ -39,6 +38,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages multiple stream download and remove requests.
|
* Manages multiple stream download and remove requests.
|
||||||
|
|
@ -199,7 +199,7 @@ public final class DownloadManager {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
saveActions();
|
saveActions();
|
||||||
maybeStartTasks();
|
maybeStartTasks();
|
||||||
if (task.currentState == STATE_QUEUED) {
|
if (task.state == STATE_QUEUED) {
|
||||||
// Task did not change out of its initial state, and so its initial state won't have been
|
// Task did not change out of its initial state, and so its initial state won't have been
|
||||||
// reported to listeners. Do so now.
|
// reported to listeners. Do so now.
|
||||||
notifyListenersTaskStateChange(task);
|
notifyListenersTaskStateChange(task);
|
||||||
|
|
@ -231,7 +231,7 @@ public final class DownloadManager {
|
||||||
for (int i = 0; i < tasks.size(); i++) {
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
Task task = tasks.get(i);
|
Task task = tasks.get(i);
|
||||||
if (task.id == taskId) {
|
if (task.id == taskId) {
|
||||||
return task.getDownloadState();
|
return task.getTaskState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -242,7 +242,7 @@ public final class DownloadManager {
|
||||||
Assertions.checkState(!released);
|
Assertions.checkState(!released);
|
||||||
TaskState[] states = new TaskState[tasks.size()];
|
TaskState[] states = new TaskState[tasks.size()];
|
||||||
for (int i = 0; i < states.length; i++) {
|
for (int i = 0; i < states.length; i++) {
|
||||||
states[i] = tasks.get(i).getDownloadState();
|
states[i] = tasks.get(i).getTaskState();
|
||||||
}
|
}
|
||||||
return states;
|
return states;
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +260,7 @@ public final class DownloadManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < tasks.size(); i++) {
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
if (tasks.get(i).isActive()) {
|
if (tasks.get(i).isStarted()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +365,7 @@ public final class DownloadManager {
|
||||||
if (released) {
|
if (released) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean stopped = !task.isActive();
|
boolean stopped = !task.isStarted();
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
activeDownloadTasks.remove(task);
|
activeDownloadTasks.remove(task);
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +382,7 @@ public final class DownloadManager {
|
||||||
|
|
||||||
private void notifyListenersTaskStateChange(Task task) {
|
private void notifyListenersTaskStateChange(Task task) {
|
||||||
logd("Task state is changed", task);
|
logd("Task state is changed", task);
|
||||||
TaskState taskState = task.getDownloadState();
|
TaskState taskState = task.getTaskState();
|
||||||
for (Listener listener : listeners) {
|
for (Listener listener : listeners) {
|
||||||
listener.onTaskStateChanged(this, taskState);
|
listener.onTaskStateChanged(this, taskState);
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +422,7 @@ public final class DownloadManager {
|
||||||
maybeStartTasks();
|
maybeStartTasks();
|
||||||
for (int i = 0; i < tasks.size(); i++) {
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
Task task = tasks.get(i);
|
Task task = tasks.get(i);
|
||||||
if (task.currentState == STATE_QUEUED) {
|
if (task.state == STATE_QUEUED) {
|
||||||
// Task did not change out of its initial state, and so its initial state
|
// Task did not change out of its initial state, and so its initial state
|
||||||
// won't have been reported to listeners. Do so now.
|
// won't have been reported to listeners. Do so now.
|
||||||
notifyListenersTaskStateChange(task);
|
notifyListenersTaskStateChange(task);
|
||||||
|
|
@ -525,7 +525,7 @@ public final class DownloadManager {
|
||||||
public final long downloadedBytes;
|
public final long downloadedBytes;
|
||||||
|
|
||||||
/** If {@link #state} is {@link #STATE_FAILED} then this is the cause, otherwise null. */
|
/** If {@link #state} is {@link #STATE_FAILED} then this is the cause, otherwise null. */
|
||||||
public final Throwable error;
|
@Nullable public final Throwable error;
|
||||||
|
|
||||||
private TaskState(
|
private TaskState(
|
||||||
int taskId,
|
int taskId,
|
||||||
|
|
@ -533,7 +533,7 @@ public final class DownloadManager {
|
||||||
@State int state,
|
@State int state,
|
||||||
float downloadPercentage,
|
float downloadPercentage,
|
||||||
long downloadedBytes,
|
long downloadedBytes,
|
||||||
Throwable error) {
|
@Nullable Throwable error) {
|
||||||
this.taskId = taskId;
|
this.taskId = taskId;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
@ -546,52 +546,28 @@ public final class DownloadManager {
|
||||||
|
|
||||||
private static final class Task implements Runnable {
|
private static final class Task implements Runnable {
|
||||||
|
|
||||||
/**
|
/** Target states for the download thread. */
|
||||||
* Task states. One of {@link TaskState#STATE_QUEUED}, {@link TaskState#STATE_STARTED}, {@link
|
|
||||||
* TaskState#STATE_COMPLETED}, {@link TaskState#STATE_CANCELED}, {@link TaskState#STATE_FAILED},
|
|
||||||
* {@link #STATE_QUEUED_CANCELING}, {@link #STATE_STARTED_CANCELING} or {@link
|
|
||||||
* #STATE_STARTED_STOPPING}.
|
|
||||||
*
|
|
||||||
* <p>Transition diagram:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* ┌───→ q_canceling ┬→ canceled
|
|
||||||
* │ s_canceling ┘
|
|
||||||
* │ ↑
|
|
||||||
* queued → started ────┬→ completed
|
|
||||||
* ↑ ↓ └→ failed
|
|
||||||
* └──── s_stopping
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({
|
@IntDef({STATE_COMPLETED, STATE_QUEUED, STATE_CANCELED})
|
||||||
STATE_QUEUED,
|
public @interface TargetState {}
|
||||||
STATE_STARTED,
|
|
||||||
STATE_COMPLETED,
|
|
||||||
STATE_CANCELED,
|
|
||||||
STATE_FAILED,
|
|
||||||
STATE_QUEUED_CANCELING,
|
|
||||||
STATE_STARTED_CANCELING,
|
|
||||||
STATE_STARTED_STOPPING
|
|
||||||
})
|
|
||||||
public @interface InternalState {}
|
|
||||||
/** The task is about to be canceled. */
|
|
||||||
public static final int STATE_QUEUED_CANCELING = 5;
|
|
||||||
/** The task is about to be canceled. */
|
|
||||||
public static final int STATE_STARTED_CANCELING = 6;
|
|
||||||
/** The task is about to be stopped. */
|
|
||||||
public static final int STATE_STARTED_STOPPING = 7;
|
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
private final DownloadManager downloadManager;
|
private final DownloadManager downloadManager;
|
||||||
private final DownloaderFactory downloaderFactory;
|
private final DownloaderFactory downloaderFactory;
|
||||||
private final DownloadAction action;
|
private final DownloadAction action;
|
||||||
private final int minRetryCount;
|
private final int minRetryCount;
|
||||||
private volatile @InternalState int currentState;
|
/** The current state of the task. */
|
||||||
private volatile Downloader downloader;
|
@TaskState.State private int state;
|
||||||
private Thread thread;
|
/**
|
||||||
private Throwable error;
|
* When started, this is the target state that the task will transition to when the download
|
||||||
|
* thread stops.
|
||||||
|
*/
|
||||||
|
@TargetState private volatile int targetState;
|
||||||
|
|
||||||
|
@MonotonicNonNull private volatile Downloader downloader;
|
||||||
|
@MonotonicNonNull private Thread thread;
|
||||||
|
@MonotonicNonNull private Throwable error;
|
||||||
|
|
||||||
private Task(
|
private Task(
|
||||||
int id,
|
int id,
|
||||||
|
|
@ -603,150 +579,93 @@ public final class DownloadManager {
|
||||||
this.downloadManager = downloadManager;
|
this.downloadManager = downloadManager;
|
||||||
this.downloaderFactory = downloaderFactory;
|
this.downloaderFactory = downloaderFactory;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.currentState = STATE_QUEUED;
|
|
||||||
this.minRetryCount = minRetryCount;
|
this.minRetryCount = minRetryCount;
|
||||||
|
state = STATE_QUEUED;
|
||||||
|
targetState = STATE_COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TaskState getDownloadState() {
|
public TaskState getTaskState() {
|
||||||
int externalState = getExternalState();
|
float downloadPercentage = C.PERCENTAGE_UNSET;
|
||||||
return new TaskState(
|
long downloadedBytes = 0;
|
||||||
id, action, externalState, getDownloadPercentage(), getDownloadedBytes(), error);
|
if (downloader != null) {
|
||||||
|
downloadPercentage = downloader.getDownloadPercentage();
|
||||||
|
downloadedBytes = downloader.getDownloadedBytes();
|
||||||
|
}
|
||||||
|
return new TaskState(id, action, state, downloadPercentage, downloadedBytes, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the task is finished. */
|
/** Returns whether the task is finished. */
|
||||||
public boolean isFinished() {
|
public boolean isFinished() {
|
||||||
return currentState == STATE_FAILED
|
return state == STATE_FAILED || state == STATE_COMPLETED || state == STATE_CANCELED;
|
||||||
|| currentState == STATE_COMPLETED
|
|
||||||
|| currentState == STATE_CANCELED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the task is started. */
|
/** Returns whether the task is started. */
|
||||||
public boolean isActive() {
|
public boolean isStarted() {
|
||||||
return currentState == STATE_QUEUED_CANCELING
|
return state == STATE_STARTED;
|
||||||
|| currentState == STATE_STARTED
|
|
||||||
|| currentState == STATE_STARTED_STOPPING
|
|
||||||
|| currentState == STATE_STARTED_CANCELING;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
public float getDownloadPercentage() {
|
|
||||||
return downloader != null ? downloader.getDownloadPercentage() : C.PERCENTAGE_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the total number of downloaded bytes. */
|
|
||||||
public long getDownloadedBytes() {
|
|
||||||
return downloader != null ? downloader.getDownloadedBytes() : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (!DEBUG) {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
return action.type
|
return action.type
|
||||||
+ ' '
|
+ ' '
|
||||||
+ (action.isRemoveAction ? "remove" : "download")
|
+ (action.isRemoveAction ? "remove" : "download")
|
||||||
+ ' '
|
+ ' '
|
||||||
+ toString(action.data)
|
+ TaskState.getStateString(state)
|
||||||
+ ' '
|
+ ' '
|
||||||
+ getStateString();
|
+ TaskState.getStateString(targetState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toString(byte[] data) {
|
public boolean canStart() {
|
||||||
if (data.length > 100) {
|
return state == STATE_QUEUED;
|
||||||
return "<data is too long>";
|
|
||||||
} else {
|
|
||||||
return '\'' + Util.fromUtf8Bytes(data) + '\'';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getStateString() {
|
public void start() {
|
||||||
switch (currentState) {
|
if (state == STATE_QUEUED) {
|
||||||
case STATE_QUEUED_CANCELING:
|
state = STATE_STARTED;
|
||||||
case STATE_STARTED_CANCELING:
|
targetState = STATE_COMPLETED;
|
||||||
return "CANCELING";
|
downloadManager.onTaskStateChange(this);
|
||||||
case STATE_STARTED_STOPPING:
|
|
||||||
return "STOPPING";
|
|
||||||
case STATE_QUEUED:
|
|
||||||
case STATE_STARTED:
|
|
||||||
case STATE_COMPLETED:
|
|
||||||
case STATE_CANCELED:
|
|
||||||
case STATE_FAILED:
|
|
||||||
default:
|
|
||||||
return TaskState.getStateString(currentState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getExternalState() {
|
|
||||||
switch (currentState) {
|
|
||||||
case STATE_QUEUED_CANCELING:
|
|
||||||
return STATE_QUEUED;
|
|
||||||
case STATE_STARTED_CANCELING:
|
|
||||||
case STATE_STARTED_STOPPING:
|
|
||||||
return STATE_STARTED;
|
|
||||||
case STATE_QUEUED:
|
|
||||||
case STATE_STARTED:
|
|
||||||
case STATE_COMPLETED:
|
|
||||||
case STATE_CANCELED:
|
|
||||||
case STATE_FAILED:
|
|
||||||
default:
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start() {
|
|
||||||
if (changeStateAndNotify(STATE_QUEUED, STATE_STARTED)) {
|
|
||||||
thread = new Thread(this);
|
thread = new Thread(this);
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canStart() {
|
public void cancel() {
|
||||||
return currentState == STATE_QUEUED;
|
if (state == STATE_STARTED) {
|
||||||
}
|
stopDownloadThread(STATE_CANCELED);
|
||||||
|
} else if (state == STATE_QUEUED) {
|
||||||
private void cancel() {
|
state = STATE_CANCELED;
|
||||||
if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) {
|
downloadManager.handler.post(() -> downloadManager.onTaskStateChange(this));
|
||||||
downloadManager.handler.post(
|
|
||||||
() -> changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED));
|
|
||||||
} else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) {
|
|
||||||
cancelDownload();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() {
|
public void stop() {
|
||||||
if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_STOPPING)) {
|
if (state == STATE_STARTED && targetState == STATE_COMPLETED) {
|
||||||
logd("Stopping", this);
|
stopDownloadThread(STATE_QUEUED);
|
||||||
cancelDownload();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean changeStateAndNotify(@InternalState int oldState, @InternalState int newState) {
|
// Internal methods running on the main thread.
|
||||||
return changeStateAndNotify(oldState, newState, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean changeStateAndNotify(
|
private void stopDownloadThread(@TargetState int targetState) {
|
||||||
@InternalState int oldState, @InternalState int newState, Throwable error) {
|
this.targetState = targetState;
|
||||||
if (currentState != oldState) {
|
// TODO: The possibility of downloader being null here may prevent the download thread from
|
||||||
return false;
|
// stopping in a timely way. Fix this.
|
||||||
}
|
|
||||||
currentState = newState;
|
|
||||||
this.error = error;
|
|
||||||
boolean isInternalState = currentState != getExternalState();
|
|
||||||
if (!isInternalState) {
|
|
||||||
downloadManager.onTaskStateChange(this);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelDownload() {
|
|
||||||
if (downloader != null) {
|
if (downloader != null) {
|
||||||
downloader.cancel();
|
downloader.cancel();
|
||||||
}
|
}
|
||||||
thread.interrupt();
|
Assertions.checkNotNull(thread).interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadThreadStopped(@Nullable Throwable finalError) {
|
||||||
|
@TaskState.State int finalState = targetState;
|
||||||
|
if (targetState == STATE_COMPLETED && finalError != null) {
|
||||||
|
finalState = STATE_FAILED;
|
||||||
|
} else {
|
||||||
|
finalError = null;
|
||||||
|
}
|
||||||
|
state = finalState;
|
||||||
|
error = finalError;
|
||||||
|
downloadManager.onTaskStateChange(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods running on download thread.
|
// Methods running on download thread.
|
||||||
|
|
@ -762,39 +681,32 @@ public final class DownloadManager {
|
||||||
} else {
|
} else {
|
||||||
int errorCount = 0;
|
int errorCount = 0;
|
||||||
long errorPosition = C.LENGTH_UNSET;
|
long errorPosition = C.LENGTH_UNSET;
|
||||||
while (!Thread.interrupted()) {
|
while (targetState == STATE_COMPLETED) {
|
||||||
try {
|
try {
|
||||||
downloader.download();
|
downloader.download();
|
||||||
break;
|
break;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
long downloadedBytes = downloader.getDownloadedBytes();
|
if (targetState == STATE_COMPLETED) {
|
||||||
if (downloadedBytes != errorPosition) {
|
long downloadedBytes = downloader.getDownloadedBytes();
|
||||||
logd("Reset error count. downloadedBytes = " + downloadedBytes, this);
|
if (downloadedBytes != errorPosition) {
|
||||||
errorPosition = downloadedBytes;
|
logd("Reset error count. downloadedBytes = " + downloadedBytes, this);
|
||||||
errorCount = 0;
|
errorPosition = downloadedBytes;
|
||||||
|
errorCount = 0;
|
||||||
|
}
|
||||||
|
if (++errorCount > minRetryCount) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
logd("Download error. Retry " + errorCount, this);
|
||||||
|
Thread.sleep(getRetryDelayMillis(errorCount));
|
||||||
}
|
}
|
||||||
if (currentState != STATE_STARTED || ++errorCount > minRetryCount) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
logd("Download error. Retry " + errorCount, this);
|
|
||||||
Thread.sleep(getRetryDelayMillis(errorCount));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e){
|
} catch (Throwable e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
final Throwable finalError = error;
|
final Throwable finalError = error;
|
||||||
downloadManager.handler.post(
|
downloadManager.handler.post(() -> onDownloadThreadStopped(finalError));
|
||||||
() -> {
|
|
||||||
if (changeStateAndNotify(
|
|
||||||
STATE_STARTED, finalError != null ? STATE_FAILED : STATE_COMPLETED, finalError)
|
|
||||||
|| changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED)
|
|
||||||
|| changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getRetryDelayMillis(int errorCount) {
|
private int getRetryDelayMillis(int errorCount) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue