Move DownloadService to Coroutine (1/2)

This commit is contained in:
LooKeR 2021-12-04 00:28:44 +05:30
parent 0dd23c23b1
commit e7184ecccc
2 changed files with 56 additions and 47 deletions

View File

@ -24,13 +24,13 @@ import com.looker.droidify.utility.extension.android.*
import com.looker.droidify.utility.extension.resources.* import com.looker.droidify.utility.extension.resources.*
import com.looker.droidify.utility.extension.text.* import com.looker.droidify.utility.extension.text.*
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.subjects.PublishSubject
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collect
import java.io.File import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.TimeUnit
import kotlin.math.* import kotlin.math.*
class DownloadService : ConnectionService<DownloadService.Binder>() { class DownloadService : ConnectionService<DownloadService.Binder>() {
@ -41,7 +41,8 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
private const val EXTRA_CACHE_FILE_NAME = private const val EXTRA_CACHE_FILE_NAME =
"${BuildConfig.APPLICATION_ID}.intent.extra.CACHE_FILE_NAME" "${BuildConfig.APPLICATION_ID}.intent.extra.CACHE_FILE_NAME"
private val downloadingSubject = PublishSubject.create<State.Downloading>() private val mutableDownloadState = MutableSharedFlow<State.Downloading>()
private val downloadState = mutableDownloadState.asSharedFlow()
} }
val scope = CoroutineScope(Dispatchers.Default) val scope = CoroutineScope(Dispatchers.Default)
@ -81,15 +82,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
State(packageName, name) State(packageName, name)
class Success( class Success(
packageName: String, name: String, val release: Release, packageName: String, name: String, val release: Release
val consume: () -> Unit,
) : State(packageName, name) ) : State(packageName, name)
class Error(packageName: String, name: String) : State(packageName, name) class Error(packageName: String, name: String) : State(packageName, name)
class Cancel(packageName: String, name: String) : State(packageName, name) class Cancel(packageName: String, name: String) : State(packageName, name)
} }
private val stateSubject = PublishSubject.create<State>() private val mutableStateSubject = MutableSharedFlow<State>()
private class Task( private class Task(
val packageName: String, val name: String, val release: Release, val packageName: String, val name: String, val release: Release,
@ -106,9 +106,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
private var currentTask: CurrentTask? = null private var currentTask: CurrentTask? = null
inner class Binder : android.os.Binder() { inner class Binder : android.os.Binder() {
fun events(packageName: String): Observable<State> { val stateSubject = mutableStateSubject.asSharedFlow()
return stateSubject.filter { it.packageName == packageName }
}
fun enqueue(packageName: String, name: String, repository: Repository, release: Release) { fun enqueue(packageName: String, name: String, repository: Repository, release: Release) {
val task = Task( val task = Task(
@ -128,7 +126,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
if (currentTask == null) { if (currentTask == null) {
handleDownload() handleDownload()
} else { } else {
stateSubject.onNext(State.Pending(packageName, name)) scope.launch { mutableStateSubject.emit(State.Pending(packageName, name)) }
} }
} }
} }
@ -138,18 +136,11 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
cancelCurrentTask(packageName) cancelCurrentTask(packageName)
handleDownload() handleDownload()
} }
fun getState(packageName: String): State? = currentTask
?.let { if (it.task.packageName == packageName) it.lastState else null }
?: tasks.find { it.packageName == packageName }
?.let { State.Pending(it.packageName, it.name) }
} }
private val binder = Binder() private val binder = Binder()
override fun onBind(intent: Intent): Binder = binder override fun onBind(intent: Intent): Binder = binder
private var downloadingDisposable: Disposable? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -162,16 +153,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
.let(notificationManager::createNotificationChannel) .let(notificationManager::createNotificationChannel)
} }
downloadingDisposable = downloadingSubject scope.launch {
.sample(500L, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) downloadState.collect { publishForegroundState(false, it) }
.subscribe { publishForegroundState(false, it) } }
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
downloadingDisposable?.dispose()
downloadingDisposable = null
scope.cancel() scope.cancel()
cancelTasks(null) cancelTasks(null)
cancelCurrentTask(null) cancelCurrentTask(null)
@ -187,7 +176,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
private fun cancelTasks(packageName: String?) { private fun cancelTasks(packageName: String?) {
tasks.removeAll { tasks.removeAll {
(packageName == null || it.packageName == packageName) && run { (packageName == null || it.packageName == packageName) && run {
stateSubject.onNext(State.Cancel(it.packageName, it.name)) scope.launch { mutableStateSubject.emit(State.Cancel(it.packageName, it.name)) }
true true
} }
} }
@ -197,7 +186,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
currentTask?.let { currentTask?.let {
if (packageName == null || it.task.packageName == packageName) { if (packageName == null || it.task.packageName == packageName) {
currentTask = null currentTask = null
stateSubject.onNext(State.Cancel(it.task.packageName, it.task.name)) scope.launch {
mutableStateSubject.emit(
State.Cancel(
it.task.packageName,
it.task.name
)
)
}
it.disposable.dispose() it.disposable.dispose()
} }
} }
@ -307,9 +303,10 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
private fun publishSuccess(task: Task) { private fun publishSuccess(task: Task) {
var consumed = false var consumed = false
stateSubject.onNext(State.Success(task.packageName, task.name, task.release) { scope.launch {
mutableStateSubject.emit(State.Success(task.packageName, task.name, task.release))
consumed = true consumed = true
}) }
if (!consumed) { if (!consumed) {
if (rootInstallerEnabled) { if (rootInstallerEnabled) {
scope.launch { scope.launch {
@ -414,7 +411,7 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
} }
}::class }::class
}.build()) }.build())
stateSubject.onNext(state) scope.launch { mutableStateSubject.emit(state) }
} }
} }
@ -441,14 +438,16 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
task.authentication task.authentication
) { read, total -> ) { read, total ->
if (!disposable.isDisposed) { if (!disposable.isDisposed) {
downloadingSubject.onNext( scope.launch {
State.Downloading( mutableDownloadState.emit(
task.packageName, State.Downloading(
task.name, task.packageName,
read, task.name,
total read,
total
)
) )
) }
} }
} }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -460,7 +459,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
task, task,
if (result != null) ErrorType.Http else ErrorType.Network if (result != null) ErrorType.Http else ErrorType.Network
) )
stateSubject.onNext(State.Error(task.packageName, task.name)) scope.launch {
mutableStateSubject.emit(
State.Error(
task.packageName,
task.name
)
)
}
} else { } else {
val validationError = validatePackage(task, partialReleaseFile) val validationError = validatePackage(task, partialReleaseFile)
if (validationError == null) { if (validationError == null) {
@ -471,7 +477,14 @@ class DownloadService : ConnectionService<DownloadService.Binder>() {
} else { } else {
partialReleaseFile.delete() partialReleaseFile.delete()
showNotificationError(task, ErrorType.Validation(validationError)) showNotificationError(task, ErrorType.Validation(validationError))
stateSubject.onNext(State.Error(task.packageName, task.name)) scope.launch {
mutableStateSubject.emit(
State.Error(
task.packageName,
task.name
)
)
}
} }
} }
handleDownload() handleDownload()

View File

@ -35,6 +35,8 @@ import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -83,15 +85,12 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
private var recyclerView: RecyclerView? = null private var recyclerView: RecyclerView? = null
private var productDisposable: Disposable? = null private var productDisposable: Disposable? = null
private var downloadDisposable: Disposable? = null
private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder -> private val downloadConnection = Connection(DownloadService::class.java, onBind = { _, binder ->
lifecycleScope.launch { updateDownloadState(binder.getState(packageName)) } lifecycleScope.launch {
downloadDisposable = binder.events(packageName).subscribe { binder.stateSubject.filter { it.packageName == packageName }.collect {
lifecycleScope.launch { updateDownloadState(it) } updateDownloadState(it)
}
} }
}, onUnbind = { _, _ ->
downloadDisposable?.dispose()
downloadDisposable = null
}) })
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -238,8 +237,6 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
productDisposable?.dispose() productDisposable?.dispose()
productDisposable = null productDisposable = null
downloadDisposable?.dispose()
downloadDisposable = null
downloadConnection.unbind(requireContext()) downloadConnection.unbind(requireContext())
} }
@ -363,7 +360,6 @@ class AppDetailFragment() : ScreenFragment(), AppDetailAdapter.Callbacks {
(recyclerView?.adapter as? AppDetailAdapter)?.setStatus(status) (recyclerView?.adapter as? AppDetailAdapter)?.setStatus(status)
if (state is DownloadService.State.Success && isResumed) { if (state is DownloadService.State.Success && isResumed) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
state.consume()
AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName) AppInstaller.getInstance(context)?.defaultInstaller?.install(state.release.cacheFileName)
} }
} }