Make DataIndex accessing code run on internal thread

This is the last cl of a series of cls that will convert DownloadManager
threading model as public methods post actions on an internal thread
which then do the work.

PiperOrigin-RevId: 242437229
This commit is contained in:
eguven 2019-04-08 12:00:48 +01:00 committed by Oliver Woodman
parent 3169b140fc
commit c17c722158

View File

@ -29,7 +29,9 @@ import static com.google.android.exoplayer2.offline.DownloadState.STATE_RESTARTI
import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED; import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED;
import android.content.Context; import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -111,8 +113,7 @@ public final class DownloadManager {
START_THREAD_SUCCEEDED, START_THREAD_SUCCEEDED,
START_THREAD_WAIT_REMOVAL_TO_FINISH, START_THREAD_WAIT_REMOVAL_TO_FINISH,
START_THREAD_WAIT_DOWNLOAD_CANCELLATION, START_THREAD_WAIT_DOWNLOAD_CANCELLATION,
START_THREAD_TOO_MANY_DOWNLOADS, START_THREAD_TOO_MANY_DOWNLOADS
START_THREAD_NOT_ALLOWED
}) })
private @interface StartThreadResults {} private @interface StartThreadResults {}
@ -120,7 +121,6 @@ public final class DownloadManager {
private static final int START_THREAD_WAIT_REMOVAL_TO_FINISH = 1; private static final int START_THREAD_WAIT_REMOVAL_TO_FINISH = 1;
private static final int START_THREAD_WAIT_DOWNLOAD_CANCELLATION = 2; private static final int START_THREAD_WAIT_DOWNLOAD_CANCELLATION = 2;
private static final int START_THREAD_TOO_MANY_DOWNLOADS = 3; private static final int START_THREAD_TOO_MANY_DOWNLOADS = 3;
private static final int START_THREAD_NOT_ALLOWED = 4;
private static final String TAG = "DownloadManager"; private static final String TAG = "DownloadManager";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@ -130,19 +130,27 @@ public final class DownloadManager {
private final Context context; private final Context context;
private final DefaultDownloadIndex downloadIndex; private final DefaultDownloadIndex downloadIndex;
private final DownloaderFactory downloaderFactory; private final DownloaderFactory downloaderFactory;
private final Handler mainHandler;
private final HandlerThread internalThread;
private final Handler internalHandler;
// Collections that are accessed on the main thread.
private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<String, DownloadState> downloadStates;
// Collections that are accessed on the internal thread.
private final ArrayList<Download> downloads; private final ArrayList<Download> downloads;
private final HashMap<Download, DownloadThread> activeDownloads; private final HashMap<Download, DownloadThread> activeDownloads;
private final Handler mainHandler;
/*TODO
private final HandlerThread internalThread;
private final Handler internalHandler;*/
private final CopyOnWriteArraySet<Listener> listeners;
// Mutable fields that are accessed on the main thread.
private boolean idle;
private boolean initialized; private boolean initialized;
private boolean released; private boolean released;
private RequirementsWatcher requirementsWatcher;
// Mutable fields that are accessed on the internal thread.
@Requirements.RequirementFlags private int notMetRequirements; @Requirements.RequirementFlags private int notMetRequirements;
private int manualStopReason; private int manualStopReason;
private RequirementsWatcher requirementsWatcher;
private int simultaneousDownloads; private int simultaneousDownloads;
/** /**
@ -214,6 +222,7 @@ public final class DownloadManager {
manualStopReason = MANUAL_STOP_REASON_UNDEFINED; manualStopReason = MANUAL_STOP_REASON_UNDEFINED;
downloads = new ArrayList<>(); downloads = new ArrayList<>();
downloadStates = new HashMap<>();
activeDownloads = new HashMap<>(); activeDownloads = new HashMap<>();
Looper looper = Looper.myLooper(); Looper looper = Looper.myLooper();
@ -222,15 +231,18 @@ public final class DownloadManager {
} }
mainHandler = new Handler(looper); mainHandler = new Handler(looper);
/*TODO
internalThread = new HandlerThread("DownloadManager file i/o"); internalThread = new HandlerThread("DownloadManager file i/o");
internalThread.start(); internalThread.start();
internalHandler = new Handler(internalThread.getLooper());*/ internalHandler = new Handler(internalThread.getLooper());
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
setNotMetRequirements(watchRequirements(requirements)); int notMetRequirements = watchRequirements(requirements);
loadDownloads(); runOnInternalThread(
() -> {
setNotMetRequirements(notMetRequirements);
loadDownloads();
});
logd("Created"); logd("Created");
} }
@ -243,32 +255,30 @@ public final class DownloadManager {
/** Returns whether there are no active downloads. */ /** Returns whether there are no active downloads. */
public boolean isIdle() { public boolean isIdle() {
Assertions.checkState(!released); Assertions.checkState(!released);
return initialized && activeDownloads.isEmpty(); return idle;
} }
/** Returns the used {@link DownloadIndex}. */ /** Returns the used {@link DownloadIndex}. */
public DownloadIndex getDownloadIndex() { public DownloadIndex getDownloadIndex() {
Assertions.checkState(!released);
return downloadIndex; return downloadIndex;
} }
/** Returns the number of downloads. */ /** Returns the number of downloads. */
public int getDownloadCount() { public int getDownloadCount() {
Assertions.checkState(!released); Assertions.checkState(!released);
return downloads.size(); return downloadStates.size();
} }
/** Returns the states of all current downloads. */ /** Returns the states of all current downloads. */
public DownloadState[] getAllDownloadStates() { public DownloadState[] getAllDownloadStates() {
Assertions.checkState(!released); Assertions.checkState(!released);
DownloadState[] states = new DownloadState[downloads.size()]; return downloadStates.values().toArray(new DownloadState[0]);
for (int i = 0; i < states.length; i++) {
states[i] = downloads.get(i).getUpdatedDownloadState();
}
return states;
} }
/** Returns the requirements needed to be met to start downloads. */ /** Returns the requirements needed to be met to start downloads. */
public Requirements getRequirements() { public Requirements getRequirements() {
Assertions.checkState(!released);
return requirementsWatcher.getRequirements(); return requirementsWatcher.getRequirements();
} }
@ -278,6 +288,7 @@ public final class DownloadManager {
* @param listener The listener to be added. * @param listener The listener to be added.
*/ */
public void addListener(Listener listener) { public void addListener(Listener listener) {
Assertions.checkState(!released);
listeners.add(listener); listeners.add(listener);
} }
@ -287,6 +298,7 @@ public final class DownloadManager {
* @param listener The listener to be removed. * @param listener The listener to be removed.
*/ */
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
Assertions.checkState(!released);
listeners.remove(listener); listeners.remove(listener);
} }
@ -296,8 +308,12 @@ public final class DownloadManager {
* @param requirements Need to be met to start downloads. * @param requirements Need to be met to start downloads.
*/ */
public void setRequirements(Requirements requirements) { public void setRequirements(Requirements requirements) {
Assertions.checkState(!released); if (requirements.equals(requirementsWatcher.getRequirements())) {
setRequirementsInternal(requirements); return;
}
requirementsWatcher.stop();
int notMetRequirements = watchRequirements(requirements);
onRequirementsStateChanged(notMetRequirements);
} }
/** /**
@ -305,7 +321,7 @@ public final class DownloadManager {
*/ */
public void startDownloads() { public void startDownloads() {
logd("manual stop is cancelled"); logd("manual stop is cancelled");
setManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE); runOnInternalThread(() -> setManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE));
} }
/** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */ /** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */
@ -324,7 +340,7 @@ public final class DownloadManager {
public void stopDownloads(int manualStopReason) { public void stopDownloads(int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE); Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
logd("downloads are stopped manually"); logd("downloads are stopped manually");
setManualStopReason(/* id= */ null, manualStopReason); runOnInternalThread(() -> setManualStopReason(/* id= */ null, manualStopReason));
} }
/** /**
@ -334,7 +350,7 @@ public final class DownloadManager {
* @param id The unique content id of the download to be started. * @param id The unique content id of the download to be started.
*/ */
public void startDownload(String id) { public void startDownload(String id) {
setManualStopReason(id, MANUAL_STOP_REASON_NONE); runOnInternalThread(() -> setManualStopReason(id, MANUAL_STOP_REASON_NONE));
} }
/** /**
@ -344,7 +360,8 @@ public final class DownloadManager {
* @param id The unique content id of the download to be stopped. * @param id The unique content id of the download to be stopped.
*/ */
public void stopDownload(String id) { public void stopDownload(String id) {
stopDownload(id, /* manualStopReason= */ MANUAL_STOP_REASON_UNDEFINED); runOnInternalThread(
() -> stopDownload(id, /* manualStopReason= */ MANUAL_STOP_REASON_UNDEFINED));
} }
/** /**
@ -359,7 +376,7 @@ public final class DownloadManager {
*/ */
public void stopDownload(String id, int manualStopReason) { public void stopDownload(String id, int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE); Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
setManualStopReason(id, manualStopReason); runOnInternalThread(() -> setManualStopReason(id, manualStopReason));
} }
/** /**
@ -368,8 +385,7 @@ public final class DownloadManager {
* @param action The download action. * @param action The download action.
*/ */
public void addDownload(DownloadAction action) { public void addDownload(DownloadAction action) {
Assertions.checkState(!released); runOnInternalThread(() -> addDownloadInternal(action));
addDownloadInternal(action);
} }
/** /**
@ -378,8 +394,7 @@ public final class DownloadManager {
* @param id The unique content id of the download to be started. * @param id The unique content id of the download to be started.
*/ */
public void removeDownload(String id) { public void removeDownload(String id) {
Assertions.checkState(!released); runOnInternalThread(() -> removeDownloadInternal(id));
removeDownloadInternal(id);
} }
/** /**
@ -391,34 +406,70 @@ public final class DownloadManager {
if (released) { if (released) {
return; return;
} }
/*TODO call releaseInternal on internal thread.
final ConditionVariable fileIOFinishedCondition = new ConditionVariable();
internalHandler.post(fileIOFinishedCondition::open);
fileIOFinishedCondition.block();
internalThread.quit();
*/
releaseInternal();
}
// Methods that run on internal thread.
private void releaseInternal() {
released = true; released = true;
stopAllDownloadThreads();
if (requirementsWatcher != null) { if (requirementsWatcher != null) {
requirementsWatcher.stop(); requirementsWatcher.stop();
} }
ConditionVariable fileIOFinishedCondition = new ConditionVariable();
internalHandler.post(
() -> {
releaseInternal();
fileIOFinishedCondition.open();
});
fileIOFinishedCondition.block();
logd("Released"); logd("Released");
} }
private void setRequirementsInternal(Requirements requirements) { private void runOnInternalThread(Runnable runnable) {
if (requirements.equals(requirementsWatcher.getRequirements())) { Assertions.checkState(!released);
return; internalHandler.post(runnable);
}
requirementsWatcher.stop();
onRequirementsStateChanged(watchRequirements(requirements));
} }
private void notifyListenersDownloadStateChange(DownloadState downloadState) {
if (isFinished(downloadState.state)) {
downloadStates.remove(downloadState.id);
} else {
downloadStates.put(downloadState.id, downloadState);
}
for (Listener listener : listeners) {
listener.onDownloadStateChanged(this, downloadState);
}
}
@Requirements.RequirementFlags
private int watchRequirements(Requirements requirements) {
RequirementsWatcher.Listener listener =
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements);
requirementsWatcher = new RequirementsWatcher(context, listener, requirements);
return requirementsWatcher.start();
}
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) {
Requirements requirements = requirementsWatcher.getRequirements();
for (Listener listener : listeners) {
listener.onRequirementsStateChanged(this, requirements, notMetRequirements);
}
internalHandler.post(() -> setNotMetRequirements(notMetRequirements));
}
private void onInitialized() {
initialized = true;
for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this);
}
}
private void onIdleStateChange(boolean idle) {
if (!this.idle && idle) {
for (Listener listener : listeners) {
listener.onIdle(this);
}
}
this.idle = idle;
}
// Methods that run on internal thread.
private void setManualStopReason(@Nullable String id, int manualStopReason) { private void setManualStopReason(@Nullable String id, int manualStopReason) {
if (id != null) { if (id != null) {
Download download = getDownload(id); Download download = getDownload(id);
@ -476,36 +527,14 @@ public final class DownloadManager {
} }
} }
private void maybeNotifyListenersIdle() {
if (!isIdle()) {
return;
}
logd("Notify idle state");
for (Listener listener : listeners) {
listener.onIdle(this);
}
}
private void onDownloadStateChange(Download download, DownloadState downloadState) { private void onDownloadStateChange(Download download, DownloadState downloadState) {
if (released) {
return;
}
logd("Download state is changed", download); logd("Download state is changed", download);
updateDownloadIndex(downloadState); updateDownloadIndex(downloadState);
for (Listener listener : listeners) { mainHandler.post(() -> notifyListenersDownloadStateChange(downloadState));
listener.onDownloadStateChanged(this, downloadState); int index = downloads.indexOf(download);
if (isFinished(download.state)) {
downloads.remove(index);
} }
if (download.isFinished()) {
downloads.remove(download);
}
}
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) {
Requirements requirements = requirementsWatcher.getRequirements();
for (Listener listener : listeners) {
listener.onRequirementsStateChanged(DownloadManager.this, requirements, notMetRequirements);
}
setNotMetRequirements(notMetRequirements);
} }
private void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) { private void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) {
@ -516,14 +545,6 @@ public final class DownloadManager {
} }
} }
@Requirements.RequirementFlags
private int watchRequirements(Requirements requirements) {
RequirementsWatcher.Listener listener =
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements);
requirementsWatcher = new RequirementsWatcher(context, listener, requirements);
return requirementsWatcher.start();
}
@Nullable @Nullable
private Download getDownload(String id) { private Download getDownload(String id) {
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
@ -563,19 +584,23 @@ public final class DownloadManager {
addDownloadForState(downloadState); addDownloadForState(downloadState);
} }
logd("Downloads are created."); logd("Downloads are created.");
initialized = true; mainHandler.post(this::onInitialized);
for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this);
}
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).start(); downloads.get(i).start();
} }
checkIfIdle();
}
private void checkIfIdle() {
boolean idle = activeDownloads.isEmpty();
mainHandler.post(() -> onIdleStateChange(idle));
} }
private void addDownloadForState(DownloadState downloadState) { private void addDownloadForState(DownloadState downloadState) {
Download download = new Download(this, downloadState, notMetRequirements, manualStopReason); Download download = new Download(this, downloadState, notMetRequirements, manualStopReason);
downloads.add(download); downloads.add(download);
logd("Download is added", download); logd("Download is added", download);
download.initialize();
} }
private void updateDownloadIndex(DownloadState downloadState) { private void updateDownloadIndex(DownloadState downloadState) {
@ -610,9 +635,6 @@ public final class DownloadManager {
@StartThreadResults @StartThreadResults
private int startDownloadThread(Download download) { private int startDownloadThread(Download download) {
if (released) {
return START_THREAD_NOT_ALLOWED;
}
if (activeDownloads.containsKey(download)) { if (activeDownloads.containsKey(download)) {
if (stopDownloadThread(download)) { if (stopDownloadThread(download)) {
return START_THREAD_WAIT_DOWNLOAD_CANCELLATION; return START_THREAD_WAIT_DOWNLOAD_CANCELLATION;
@ -628,6 +650,7 @@ public final class DownloadManager {
DownloadThread downloadThread = new DownloadThread(download); DownloadThread downloadThread = new DownloadThread(download);
activeDownloads.put(download, downloadThread); activeDownloads.put(download, downloadThread);
download.setCounters(downloadThread.downloader.getCounters()); download.setCounters(downloadThread.downloader.getCounters());
checkIfIdle();
logd("Download is started", download); logd("Download is started", download);
return START_THREAD_SUCCEEDED; return START_THREAD_SUCCEEDED;
} }
@ -642,19 +665,18 @@ public final class DownloadManager {
return false; return false;
} }
private void stopAllDownloadThreads() { private void releaseInternal() {
for (Download download : activeDownloads.keySet()) { for (Download download : activeDownloads.keySet()) {
stopDownloadThread(download); stopDownloadThread(download);
} }
internalThread.quit();
} }
private void onDownloadThreadStopped(DownloadThread downloadThread, Throwable finalError) { private void onDownloadThreadStopped(DownloadThread downloadThread, Throwable finalError) {
if (released) {
return;
}
Download download = downloadThread.download; Download download = downloadThread.download;
logd("Download is stopped", download); logd("Download is stopped", download);
activeDownloads.remove(download); activeDownloads.remove(download);
checkIfIdle();
boolean tryToStartDownloads = false; boolean tryToStartDownloads = false;
if (!downloadThread.isRemoveThread) { if (!downloadThread.isRemoveThread) {
// If maxSimultaneousDownloads was hit, there might be a download waiting for a slot. // If maxSimultaneousDownloads was hit, there might be a download waiting for a slot.
@ -669,7 +691,10 @@ public final class DownloadManager {
downloads.get(i).start(); downloads.get(i).start();
} }
} }
maybeNotifyListenersIdle(); }
private static boolean isFinished(@DownloadState.State int state) {
return state == STATE_FAILED || state == STATE_COMPLETED || state == STATE_REMOVED;
} }
private static final class Download { private static final class Download {
@ -690,7 +715,9 @@ public final class DownloadManager {
this.downloadState = downloadState; this.downloadState = downloadState;
this.notMetRequirements = notMetRequirements; this.notMetRequirements = notMetRequirements;
this.manualStopReason = manualStopReason; this.manualStopReason = manualStopReason;
}
private void initialize() {
initialize(downloadState.state); initialize(downloadState.state);
} }
@ -705,7 +732,7 @@ public final class DownloadManager {
Log.e(TAG, String.format(format, newAction.type, downloadState.type)); Log.e(TAG, String.format(format, newAction.type, downloadState.type));
} }
downloadState = downloadState.mergeAction(newAction); downloadState = downloadState.mergeAction(newAction);
initialize(downloadState.state); initialize();
} }
public void remove() { public void remove() {
@ -731,10 +758,6 @@ public final class DownloadManager {
return downloadState; return downloadState;
} }
public boolean isFinished() {
return state == STATE_FAILED || state == STATE_COMPLETED || state == STATE_REMOVED;
}
public boolean isIdle() { public boolean isIdle() {
return state != STATE_DOWNLOADING && state != STATE_REMOVING && state != STATE_RESTARTING; return state != STATE_DOWNLOADING && state != STATE_REMOVING && state != STATE_RESTARTING;
} }
@ -855,7 +878,6 @@ public final class DownloadManager {
} }
} }
} }
} }
private class DownloadThread extends Thread { private class DownloadThread extends Thread {
@ -915,7 +937,10 @@ public final class DownloadManager {
error = e; error = e;
} }
final Throwable finalError = error; final Throwable finalError = error;
mainHandler.post(() -> onDownloadThreadStopped(this, finalError)); internalHandler.post(
() -> {
onDownloadThreadStopped(this, finalError);
});
} }
private int getRetryDelayMillis(int errorCount) { private int getRetryDelayMillis(int errorCount) {