fix: move ArtiNative initialization off main thread

System.loadLibrary("arti_android") runs when ArtiNative is first
accessed. Previously this happened in TorService.init (via
setLogCallback), which ran on main thread during AppModules creation.

Moved setLogCallback into start(), which runs on Dispatchers.IO.
Now all JNI calls — including the native library load — happen off
main thread.

https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
This commit is contained in:
Claude
2026-04-01 17:54:52 +00:00
parent 45f1d79410
commit 8e6d2164d7
@@ -39,6 +39,9 @@ private const val DEFAULT_SOCKS_PORT = 19050
* lifetime — its state file lock is never released until the process exits.
* The SOCKS proxy can be started/stopped independently without affecting
* the TorClient or its file locks.
*
* All JNI calls (including System.loadLibrary) run on [Dispatchers.IO]
* to avoid blocking the main thread.
*/
class TorService(
val context: Context,
@@ -50,28 +53,9 @@ class TorService(
private val _status = MutableStateFlow<TorServiceStatus>(TorServiceStatus.Off)
val status: StateFlow<TorServiceStatus> = _status.asStateFlow()
init {
ArtiNative.setLogCallback { text ->
Log.d("TorService") {
val newLine = text.indexOf('\n')
if (newLine > 1) {
"Arti: ${text.substring(0, newLine)}"
} else {
"Arti: $text"
}
}
when {
text.contains("Sufficiently bootstrapped", ignoreCase = true) -> {
_status.value = TorServiceStatus.Active(socksPort)
Log.d("TorService") { "Arti SOCKS proxy active on port $socksPort" }
}
}
}
}
/**
* Initialize the TorClient (once) and start the SOCKS proxy.
* Must be called from a coroutine on [Dispatchers.IO].
*/
suspend fun start() {
if (proxyRunning.get()) {
@@ -83,8 +67,28 @@ class TorService(
_status.value = TorServiceStatus.Connecting
withContext(Dispatchers.IO) {
// Initialize TorClient once — this bootstraps the Tor network
// Initialize TorClient once — this bootstraps the Tor network.
// setLogCallback and initialize are the first ArtiNative calls,
// which triggers System.loadLibrary on this IO thread.
if (initialized.compareAndSet(false, true)) {
ArtiNative.setLogCallback { text ->
Log.d("TorService") {
val newLine = text.indexOf('\n')
if (newLine > 1) {
"Arti: ${text.substring(0, newLine)}"
} else {
"Arti: $text"
}
}
when {
text.contains("Sufficiently bootstrapped", ignoreCase = true) -> {
_status.value = TorServiceStatus.Active(socksPort)
Log.d("TorService") { "Arti SOCKS proxy active on port $socksPort" }
}
}
}
val dataDir = File(context.filesDir, "arti").absolutePath
Log.d("TorService") { "Initializing Arti with data dir: $dataDir" }