Skip to content
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

Load Rime in Background Thread #1152

Merged
merged 27 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1b81b63
refactor: add `RimeWrapper` to deploy rime in async manner
goofyz Dec 14, 2023
5c1f7c4
refactor: wait for rime deployment completed before doing any work
goofyz Dec 14, 2023
857edba
refactor: do not call `Rime.getInsance()` during init
goofyz Dec 14, 2023
a6765b7
refactor: use `RimeWrapper` to deploy instead of using rime directly
goofyz Dec 14, 2023
31f4e1d
feat: display a loading screen as keyboard during deploying
goofyz Dec 14, 2023
d4dcd6f
refactor: display loading dialog in preference screen when deploying …
goofyz Dec 14, 2023
ca1b607
refactor: casts as a more generic `ViewGroup`
goofyz Dec 18, 2023
bb43b61
feat: add scrollbar style to candidate view
goofyz Dec 18, 2023
48e6a87
refactor: remove extra loading dialog
goofyz Dec 19, 2023
fed125d
refactor: change loading text to "deploying"
goofyz Dec 19, 2023
2863e0c
refactor: dismiss `loadingDialog` to prevent leakage
goofyz Dec 19, 2023
0c971be
fix: return default keyboard if no keyboard is matched
goofyz Dec 20, 2023
ab36ded
fix: prevent NPE
goofyz Dec 20, 2023
dd52f31
refactor: standardize deploy process with a result dialog
goofyz Dec 20, 2023
344357a
fix: set view's height so it won't be fullscreen at first
goofyz Dec 22, 2023
6d73bad
fix: bind keyboard immediately so height won't jump up and down
goofyz Dec 22, 2023
44d8e0a
refactor: add `InitialKeyboard` to display before deployment
goofyz Dec 25, 2023
3ee23b7
refactor: add `canStart` to `RimeWrapper` to prevent auto startup
goofyz Dec 25, 2023
50ca063
refactor: add `PermissionUtils` to check if all required permissions …
goofyz Dec 25, 2023
65182fa
refactor: set `RimeWrapper.canStart` if permissions granted
goofyz Dec 25, 2023
eb0e56e
refactor: display `InitialKeyboard` before deployment or lacks of per…
goofyz Dec 25, 2023
917b974
refactor: fix code style
goofyz Dec 25, 2023
48c64b8
fix: check if external storage path is empty before starting `RimeWra…
goofyz Dec 25, 2023
6c27171
fix: instantiate property as requested instead of caching it
goofyz Dec 28, 2023
b4151c4
refactor: remove `DataDirectoryChangeListener.Listener` from `DataMan…
goofyz Dec 28, 2023
d98fb61
fix: correct lock & release mutex in `deploy()` method
goofyz Dec 29, 2023
fc3f59f
fix: move `fontDir` to method so it always refers to the latest value
goofyz Dec 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions app/src/main/java/com/osfans/trime/data/AppPrefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -333,14 +333,15 @@ class AppPrefs(
const val TIMING_SYNC_TRIGGER_TIME = "profile_timing_sync_trigger_time"
const val LAST_SYNC_STATUS = "profile_last_sync_status"
const val LAST_BACKGROUND_SYNC = "profile_last_background_sync"
val EXTERNAL_PATH_PREFIX: String = PathUtils.getExternalStoragePath()

private fun getExternalPathPrefix() = PathUtils.getExternalStoragePath()
}

var sharedDataDir: String
get() = prefs.getPref(SHARED_DATA_DIR, "$EXTERNAL_PATH_PREFIX/rime")
get() = prefs.getPref(SHARED_DATA_DIR, "${getExternalPathPrefix()}/rime")
set(v) = prefs.setPref(SHARED_DATA_DIR, v)
var userDataDir: String
get() = prefs.getPref(USER_DATA_DIR, "$EXTERNAL_PATH_PREFIX/rime")
get() = prefs.getPref(USER_DATA_DIR, "${getExternalPathPrefix()}/rime")
set(v) = prefs.setPref(USER_DATA_DIR, v)
var syncBackgroundEnabled: Boolean
get() = prefs.getPref(SYNC_BACKGROUND_ENABLED, false)
Expand Down
37 changes: 15 additions & 22 deletions app/src/main/java/com/osfans/trime/data/DataManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,19 @@ import com.osfans.trime.util.Const
import timber.log.Timber
import java.io.File

object DataManager : DataDirectoryChangeListener.Listener {
object DataManager {
private const val DEFAULT_CUSTOM_FILE_NAME = "default.custom.yaml"
private val prefs get() = AppPrefs.defaultInstance()

val defaultDataDirectory = File(PathUtils.getExternalStoragePath(), "rime")

@JvmStatic
var sharedDataDir = File(prefs.profile.sharedDataDir)
val sharedDataDir
get() = File(prefs.profile.sharedDataDir)

@JvmStatic
var userDataDir = File(prefs.profile.userDataDir)
val customDefault = File(sharedDataDir, "default.custom.yaml")

private val stagingDir = File(userDataDir, "build")
private val prebuiltDataDir = File(sharedDataDir, "build")

init {
DataDirectoryChangeListener.addDirectoryChangeListener(this)
}
val userDataDir
get() = File(prefs.profile.userDataDir)

sealed class Diff {
object New : Diff()
Expand All @@ -42,6 +37,9 @@ object DataManager : DataDirectoryChangeListener.Listener {
*/
@JvmStatic
fun resolveDeployedResourcePath(resourceId: String): String {
val stagingDir = File(userDataDir, "build")
val prebuiltDataDir = File(sharedDataDir, "build")

val defaultPath = File(stagingDir, "$resourceId.yaml")
if (!defaultPath.exists()) {
val fallbackPath = File(prebuiltDataDir, "$resourceId.yaml")
Expand Down Expand Up @@ -84,19 +82,14 @@ object DataManager : DataDirectoryChangeListener.Listener {
}

// FIXME:缺失 default.custom.yaml 会导致方案列表为空
if (!customDefault.exists()) {
Timber.d("Creating empty default.custom.yaml ...")
customDefault.createNewFile()
with(File(sharedDataDir, DEFAULT_CUSTOM_FILE_NAME)) {
val customDefault = this
if (!customDefault.exists()) {
Timber.d("Creating empty default.custom.yaml ...")
customDefault.createNewFile()
}
}

Timber.i("Synced!")
}

/**
* Update sharedDataDir and userDataDir.
*/
override fun onDataDirectoryChange() {
sharedDataDir = File(prefs.profile.sharedDataDir)
userDataDir = File(prefs.profile.userDataDir)
}
}
4 changes: 2 additions & 2 deletions app/src/main/java/com/osfans/trime/data/theme/FontManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import com.osfans.trime.data.DataManager
import java.io.File

object FontManager {
val fontDir = File(DataManager.userDataDir, "fonts")

@JvmStatic
fun getTypeface(fontFileName: String): Typeface {
val fontDir = File(DataManager.userDataDir, "fonts")

val f = File(fontDir, fontFileName)
return if (f.exists()) {
Typeface.createFromFile(f)
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/osfans/trime/data/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import android.graphics.drawable.GradientDrawable
import androidx.core.math.MathUtils
import com.osfans.trime.core.Rime
import com.osfans.trime.data.AppPrefs
import com.osfans.trime.data.DataManager.sharedDataDir
import com.osfans.trime.data.DataManager.userDataDir
import com.osfans.trime.data.schema.SchemaManager
import com.osfans.trime.data.sound.SoundThemeManager
Expand Down Expand Up @@ -87,7 +86,6 @@ class Theme private constructor(isDarkMode: Boolean) {
init {
self = this
ThemeManager.init()
Rime.getInstance(!sharedDataDir.exists())
init(isDarkMode)
Timber.d("Setting sound from color ...")
SoundThemeManager.switchSound(colors.getString("sound"))
Expand Down Expand Up @@ -303,6 +301,8 @@ class Theme private constructor(isDarkMode: Boolean) {
?: throw IllegalStateException("The default keyboard definition is missing!")
if (defaultMap.containsKey("import_preset")) {
return defaultMap["import_preset"] as? String ?: "default"
} else {
return "default"
}
}
return remapped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.blankj.utilcode.util.ToastUtils
import com.osfans.trime.R
import com.osfans.trime.core.Rime
import com.osfans.trime.data.AppPrefs
import com.osfans.trime.ime.core.RimeWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
Expand All @@ -54,14 +55,14 @@ class IntentReceiver : BroadcastReceiver(), CoroutineScope by MainScope() {
COMMAND_DEPLOY ->
launch {
withContext(Dispatchers.Default) {
Rime.deploy()
RimeWrapper.deploy()
}
ToastUtils.showLong(R.string.deploy_finish)
}
COMMAND_SYNC ->
async {
Rime.syncRimeUserData()
Rime.deploy()
RimeWrapper.deploy()
}
COMMAND_TIMING_SYNC ->
async {
Expand Down Expand Up @@ -96,7 +97,7 @@ class IntentReceiver : BroadcastReceiver(), CoroutineScope by MainScope() {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
}
Rime.syncRimeUserData()
Rime.deploy()
RimeWrapper.deploy()
wakeLock.release() // 释放唤醒锁
}
else -> return
Expand Down
121 changes: 121 additions & 0 deletions app/src/main/java/com/osfans/trime/ime/core/RimeWrapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.osfans.trime.ime.core

import android.os.Looper
import androidx.core.os.HandlerCompat
import com.osfans.trime.core.Rime
import com.osfans.trime.data.DataDirectoryChangeListener
import com.osfans.trime.data.theme.Theme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import timber.log.Timber
import java.util.Collections
import kotlin.system.measureTimeMillis

object RimeWrapper {
private var mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper())
private val mutex = Mutex()
private val myThreadSafeList = Collections.synchronizedList(mutableListOf<Runnable>())
private val _statusStateFlow = MutableStateFlow(Status.UN_INIT)
val statusStateFlow = _statusStateFlow.asStateFlow()

var canStart = false

fun startup(r: Runnable? = null) {
r.let {
myThreadSafeList.add(it)
}
if (canStart) {
if (mutex.tryLock()) {
if (_statusStateFlow.value == Status.UN_INIT) {
Timber.d("Starting in a thread")
_statusStateFlow.value = Status.IN_PROGRESS
mutex.unlock()

val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
measureTimeMillis {
Rime.getInstance(false)
Theme.get()
}.also {
Timber.d("Startup completed. It takes ${it / 1000} seconds")
}

mutex.withLock {
_statusStateFlow.value = Status.READY
}

notifyUnlock()
}
} else {
mutex.unlock()
}
}
} else {
Timber.d("RimeWrapper shall not be started")
}
}

suspend fun deploy(): Boolean {
if (mutex.tryLock()) {
if (_statusStateFlow.value != Status.IN_PROGRESS) {
_statusStateFlow.value = Status.IN_PROGRESS
mutex.unlock()

DataDirectoryChangeListener.directoryChangeListeners.forEach {
it.onDataDirectoryChange()
}

Rime.deploy()

mutex.withLock {
_statusStateFlow.value = Status.READY
}
Timber.d("Rime Deployed")

return true
} else {
mutex.unlock()
}
}
return false
}

private fun notifyUnlock() {
Timber.d("notifying unlock")
while (myThreadSafeList.isNotEmpty()) {
myThreadSafeList.removeFirstOrNull()?.let {
mainThreadHandler.post(it)
}
}
Timber.d("Unlock Run Completed")
}

fun runCheck() {
if (isReady()) {
notifyUnlock()
} else if (_statusStateFlow.value == Status.UN_INIT) {
startup()
}
}

fun isReady(): Boolean {
return _statusStateFlow.value == Status.READY
}

fun runAfterStarted(r: Runnable) {
myThreadSafeList.add(r)
runCheck()
}
}

enum class Status {
UN_INIT,
IN_PROGRESS,
READY,
}
Loading
Loading