Skip to content

Commit a3484e6

Browse files
committed
refactor: replace handler with main looper with custom RimeLifecycleScope
1 parent df6d73e commit a3484e6

File tree

5 files changed

+181
-123
lines changed

5 files changed

+181
-123
lines changed

app/src/main/java/com/osfans/trime/TrimeApplication.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import com.osfans.trime.data.db.ClipboardHelper
1010
import com.osfans.trime.data.db.CollectionHelper
1111
import com.osfans.trime.data.db.DraftHelper
1212
import com.osfans.trime.ui.main.LogActivity
13+
import kotlinx.coroutines.CoroutineName
14+
import kotlinx.coroutines.MainScope
15+
import kotlinx.coroutines.plus
1316
import timber.log.Timber
1417
import kotlin.system.exitProcess
1518

@@ -31,6 +34,8 @@ class TrimeApplication : Application() {
3134
private const val MAX_STACKTRACE_SIZE = 128000
3235
}
3336

37+
val coroutineScope = MainScope() + CoroutineName("TrimeApplication")
38+
3439
override fun onCreate() {
3540
super.onCreate()
3641
if (!BuildConfig.DEBUG) {
Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1+
// Adapted from https://github.com/fcitx5-android/fcitx5-android/blob/364afb44dcf0d9e3db3d43a21a32601b2190cbdf/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxLifecycle.kt
12
package com.osfans.trime.core
23

3-
import android.os.Handler
4-
import android.os.Looper
5-
import androidx.core.os.HandlerCompat
4+
import kotlinx.coroutines.CoroutineScope
65
import kotlinx.coroutines.Job
7-
import kotlinx.coroutines.MainScope
6+
import kotlinx.coroutines.SupervisorJob
7+
import kotlinx.coroutines.cancelChildren
88
import kotlinx.coroutines.flow.MutableStateFlow
99
import kotlinx.coroutines.flow.StateFlow
1010
import kotlinx.coroutines.flow.asStateFlow
11-
import kotlinx.coroutines.flow.launchIn
12-
import kotlinx.coroutines.flow.onEach
13-
import java.util.Collections
11+
import kotlinx.coroutines.launch
12+
import kotlin.coroutines.Continuation
13+
import kotlin.coroutines.CoroutineContext
14+
import kotlin.coroutines.resume
15+
import kotlin.coroutines.suspendCoroutine
1416

1517
class RimeLifecycleImpl : RimeLifecycle {
1618
private val _stateFlow = MutableStateFlow(RimeLifecycle.State.STOPPED)
1719
override val stateFlow = _stateFlow.asStateFlow()
1820

19-
override val handler = HandlerCompat.createAsync(Looper.getMainLooper())
20-
override val runnableList: MutableList<Runnable> = Collections.synchronizedList(mutableListOf<Runnable>())
21+
override val lifecycleScope: CoroutineScope = RimeLifecycleScope(this)
2122

2223
fun emitState(state: RimeLifecycle.State) {
2324
when (state) {
@@ -43,8 +44,7 @@ class RimeLifecycleImpl : RimeLifecycle {
4344

4445
interface RimeLifecycle {
4546
val stateFlow: StateFlow<State>
46-
val handler: Handler
47-
val runnableList: MutableList<Runnable>
47+
val lifecycleScope: CoroutineScope
4848

4949
enum class State {
5050
STARTING,
@@ -55,40 +55,59 @@ interface RimeLifecycle {
5555

5656
interface RimeLifecycleOwner {
5757
val lifecycle: RimeLifecycle
58-
val handler get() = lifecycle.handler
5958
}
6059

61-
fun RimeLifecycle.whenAtState(
60+
val RimeLifecycleOwner.lifecycleScope get() = lifecycle.lifecycleScope
61+
62+
class RimeLifecycleScope(
63+
val lifecycle: RimeLifecycle,
64+
override val coroutineContext: CoroutineContext = SupervisorJob(),
65+
) : CoroutineScope {
66+
init {
67+
launch {
68+
lifecycle.stateFlow.collect {
69+
if (it == RimeLifecycle.State.STOPPED) {
70+
coroutineContext.cancelChildren()
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
suspend fun <T> RimeLifecycle.whenAtState(
6278
state: RimeLifecycle.State,
63-
block: () -> Unit,
64-
) {
65-
runnableList.add(Runnable { block() })
66-
if (stateFlow.value == state) {
67-
handler.post(runnableList.removeFirst())
79+
block: suspend CoroutineScope.() -> T,
80+
): T {
81+
return if (stateFlow.value == state) {
82+
block(lifecycleScope)
6883
} else {
6984
StateDelegate(this, state).run(block)
7085
}
7186
}
7287

73-
inline fun RimeLifecycle.whenReady(noinline block: () -> Unit) = whenAtState(RimeLifecycle.State.READY, block)
88+
suspend inline fun <T> RimeLifecycle.whenReady(noinline block: suspend CoroutineScope.() -> T) =
89+
whenAtState(RimeLifecycle.State.READY, block)
7490

7591
private class StateDelegate(val lifecycle: RimeLifecycle, val state: RimeLifecycle.State) {
7692
private var job: Job? = null
7793

7894
init {
7995
job =
80-
lifecycle.stateFlow.onEach {
81-
if (it == state) {
82-
while (lifecycle.runnableList.isNotEmpty()) {
83-
lifecycle.handler.post(lifecycle.runnableList.removeFirst())
96+
lifecycle.lifecycleScope.launch {
97+
lifecycle.stateFlow.collect {
98+
if (it == state) {
99+
continuation?.resume(Unit)
84100
}
85101
}
86-
}.launchIn(MainScope())
102+
}
87103
}
88104

89-
fun <T> run(block: () -> T): T {
105+
private var continuation: Continuation<Unit>? = null
106+
107+
suspend fun <T> run(block: suspend CoroutineScope.() -> T): T {
108+
suspendCoroutine { continuation = it }
90109
job?.cancel()
91110
job = null
92-
return block()
111+
return block(lifecycle.lifecycleScope)
93112
}
94113
}

app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,34 @@ package com.osfans.trime.daemon
33
import androidx.core.app.NotificationCompat
44
import com.blankj.utilcode.util.NotificationUtils
55
import com.osfans.trime.R
6+
import com.osfans.trime.TrimeApplication
67
import com.osfans.trime.core.Rime
78
import com.osfans.trime.core.RimeApi
89
import com.osfans.trime.core.RimeLifecycle
10+
import com.osfans.trime.core.lifecycleScope
911
import com.osfans.trime.core.whenReady
1012
import com.osfans.trime.util.appContext
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.launch
1115
import kotlinx.coroutines.runBlocking
1216
import splitties.systemservices.notificationManager
1317
import java.util.concurrent.locks.ReentrantLock
1418
import kotlin.concurrent.withLock
1519

20+
/**
21+
* Manage the singleton instance of [Rime]
22+
*
23+
* To use rime, client should call [createSession] to obtain a [RimeSession],
24+
* and call [destroySession] on client destroyed. Client should not leak the instance of [RimeApi],
25+
* and must use [RimeSession] to access rime functionalities.
26+
*
27+
* The instance of [Rime] always exists,but whether the dispatcher runs and callback works depend on clients, i.e.
28+
* if no clients are connected, [Rime.finalize] will be called.
29+
*
30+
* Functions are thread-safe in this class.
31+
*
32+
* Adapted from [fcitx5-android/FcitxDaemon.kt](https://github.com/fcitx5-android/fcitx5-android/blob/364afb44dcf0d9e3db3d43a21a32601b2190cbdf/app/src/main/java/org/fcitx/fcitx5/android/daemon/FcitxDaemon.kt)
33+
*/
1634
object RimeDaemon {
1735
private val realRime by lazy { Rime() }
1836

@@ -36,19 +54,23 @@ object RimeDaemon {
3654
runBlocking { block(rimeImpl) }
3755
}
3856

39-
override fun runOnReady(block: RimeApi.() -> Unit) {
57+
override suspend fun <T> runOnReady(block: suspend RimeApi.() -> T): T =
4058
ensureEstablished {
4159
realRime.lifecycle.whenReady { block(rimeImpl) }
4260
}
43-
}
4461

45-
override fun runIfReady(block: RimeApi.() -> Unit) {
62+
override fun runIfReady(block: suspend RimeApi.() -> Unit) {
4663
ensureEstablished {
4764
if (realRime.isReady) {
48-
realRime.lifecycle.handler.post { block(rimeImpl) }
65+
realRime.lifecycleScope.launch {
66+
block(rimeImpl)
67+
}
4968
}
5069
}
5170
}
71+
72+
override val lifecycleScope: CoroutineScope
73+
get() = realRime.lifecycle.lifecycleScope
5274
}
5375

5476
fun createSession(name: String): RimeSession =
@@ -94,9 +116,11 @@ object RimeDaemon {
94116
}
95117
realRime.finalize()
96118
realRime.startup(fullCheck)
97-
// cancel notification on ready
98-
realRime.lifecycle.whenReady {
99-
notificationManager.cancel(CHANNEL_ID, id)
119+
TrimeApplication.getInstance().coroutineScope.launch {
120+
// cancel notification on ready
121+
realRime.lifecycle.whenReady {
122+
notificationManager.cancel(CHANNEL_ID, id)
123+
}
100124
}
101125
}
102126
}

app/src/main/java/com/osfans/trime/daemon/RimeSession.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.osfans.trime.daemon
22

33
import com.osfans.trime.core.RimeApi
4+
import kotlinx.coroutines.CoroutineScope
45

56
/**
67
* A interface to run different operations on RimeApi
@@ -17,15 +18,18 @@ interface RimeSession {
1718
/**
1819
* Run an operation immediately if rime is at ready state.
1920
* Otherwise, caller will be suspended until rime is ready and operation is done.
20-
* The [block] will be executed in main thread.
21+
* The [block] will be executed in caller's thread.
2122
* Client should use this function in most cases.
2223
*/
23-
fun runOnReady(block: RimeApi.() -> Unit)
24+
suspend fun <T> runOnReady(block: suspend RimeApi.() -> T): T
2425

2526
/**
2627
* Run an operation if rime is at ready state.
2728
* Otherwise, do nothing.
28-
* The [block] will be executed in main thread.
29+
* The [block] will be executed in executed in thread pool.
30+
* This function does not block or suspend the caller.
2931
*/
30-
fun runIfReady(block: RimeApi.() -> Unit)
32+
fun runIfReady(block: suspend RimeApi.() -> Unit)
33+
34+
val lifecycleScope: CoroutineScope
3135
}

0 commit comments

Comments
 (0)