-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
分离 torrent 连接管理逻辑, helps #1606 #1616
base: main
Are you sure you want to change the base?
Conversation
app/shared/app-data/src/commonMain/kotlin/domain/torrent/AbstractTorrentServiceConnection.kt
Outdated
Show resolved
Hide resolved
} | ||
} | ||
|
||
class TestLifecycleOwner : LifecycleOwner { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
} | ||
|
||
private class TestLifecycle(private val owner: LifecycleOwner) : Lifecycle() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use androidx
} | ||
} | ||
|
||
@RequiresApi(34) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing this
|
||
/** | ||
* APP 已进入前台, 此时需要保证服务可用. | ||
*/ | ||
@CallSuper | ||
override fun onResume(owner: LifecycleOwner) { | ||
scope.launch(CoroutineName("TorrentServiceConnection - Lifecycle Resume")) { | ||
isAtForeground.value = true | ||
// 服务已经连接了, 不需要再次处理 | ||
if (isServiceConnected.value) return@launch | ||
|
||
lock.withLock { | ||
if (isServiceConnected.value) return@launch | ||
|
||
logger.debug { "Service is not started, starting." } | ||
startServiceWithRetry() | ||
} | ||
} | ||
} | ||
|
||
@CallSuper | ||
override fun onPause(owner: LifecycleOwner) { | ||
scope.launch(CoroutineName("TorrentServiceConnection - Lifecycle Pause")) { | ||
lock.withLock { | ||
isAtForeground.value = false | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个处理有线程安全问题
设想 onResume
被调用, 然后onPause
立即被调用分别开了一个协程.
注意到 scope 是并行的 (可以同时执行两个协程).
有三个导致逻辑错误的场景:
scope.launch
是没有顺序保证的, 也就是有可能onPause
的 launch 会先执行, 然后执行onResume
的.onPause
协程先执行了, 但是onResume
同时也在执行, 有可能onPause
抢到了 lock,onResume
只能等
isAtForeground
这个 var 非常容易出错. 建议保证只有一个地方会修改这个值. 注意多线程可见性问题.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
我的建议是, 不使用 onPause
等 callback API. 用我之前改的那样 launch 一个协程 collectLatest lifecycle flow 处理, 是没有这些问题的.
private suspend fun startServiceWithRetry(maxAttempts: Int = Int.MAX_VALUE) { | ||
var retries = 0 | ||
while (retries <= maxAttempts) { | ||
val startResult = startService() | ||
if (startResult == TorrentServiceConnection.StartResult.FAILED) { | ||
logger.warn { "[#$retries] Failed to start service." } | ||
retries++ | ||
delay(7500) | ||
} else { | ||
return | ||
} | ||
} | ||
logger.error { "Failed to start service after $maxAttempts retries." } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
启动失败应该一直等吧
7500s 太久了
override suspend fun getBinder(): T { | ||
// track cancellation of [scope] | ||
scope.async { | ||
isServiceConnected.first { it } | ||
}.await() | ||
return binderDeferred.await() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not thread safe. isServiceConnected.first { it }
访问 binderDeferred.await()
和 isServiceConnected.first { it }
未上锁. 上一行检测到 isServiceConnected
为 true, 但下一行可能 binderDeferred 变了.
* 服务已连接, 服务通信对象一定可用. | ||
* 无论当前 [Lifecycle] 什么状态都要应用新的 [binder]. | ||
*/ | ||
protected fun onServiceConnected(binder: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not safe.
继承者可能会在不适合的时机调用 onServiceConnected
. 而此函数没有检查状态是否合理.
继承者不调用此函数的情况也没处理.
* 若返回了 [StartResult.STARTED] 或 [StartResult.FAILED], | ||
* [connected] 将在未来变为 `true`, 届时 [getBinder] 将会立刻返回服务通信接口. | ||
*/ | ||
suspend fun startService(): StartResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这看起来不应该是需要暴露的接口
/** | ||
* torrent 服务与 APP 通信接口. | ||
*/ | ||
interface TorrentServiceConnection<T : Any> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
注释解释一下 T 是什么意思
// 请求 wake lock, 如果在 app 中息屏可以保证 service 正常跑 [acquireWakeLockIntent] 分钟. | ||
context.startService(acquireWakeLockIntent) | ||
} catch (ex: IllegalStateException) { | ||
// 大概率是 ServiceStartForegroundException, 服务已经终止了, 不需要再请求 wakelock. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
catch ForegroundServiceStartNotAllowedException explicitly
private val startupIntentFilter by lazy { IntentFilter(AniTorrentService.INTENT_STARTUP) } | ||
private val acquireWakeLockIntent by lazy { | ||
Intent(context, AniTorrentService.actualServiceClass).apply { | ||
putExtra("acquireWakeLock", 1.minutes.inWholeMilliseconds) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is lazy really needed here?
Lazy introduces synchornization overhead (which is on main thread).
ContextCompat.registerReceiver( | ||
context, | ||
timeExceedLimitReceiver, | ||
timeExceedLimitIntentFilter, | ||
ContextCompat.RECEIVER_NOT_EXPORTED, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个没有 unregister, 会有问题吗
No description provided.