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,7 +53,24 @@ class TorService(
private val _status = MutableStateFlow<TorServiceStatus>(TorServiceStatus.Off)
val status: StateFlow<TorServiceStatus> = _status.asStateFlow()
init {
/**
* Initialize the TorClient (once) and start the SOCKS proxy.
* Must be called from a coroutine on [Dispatchers.IO].
*/
suspend fun start() {
if (proxyRunning.get()) {
if (_status.value is TorServiceStatus.Active) return
_status.value = TorServiceStatus.Connecting
return
}
_status.value = TorServiceStatus.Connecting
withContext(Dispatchers.IO) {
// 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')
@@ -68,23 +88,7 @@ class TorService(
}
}
}
}
/**
* Initialize the TorClient (once) and start the SOCKS proxy.
*/
suspend fun start() {
if (proxyRunning.get()) {
if (_status.value is TorServiceStatus.Active) return
_status.value = TorServiceStatus.Connecting
return
}
_status.value = TorServiceStatus.Connecting
withContext(Dispatchers.IO) {
// Initialize TorClient once — this bootstraps the Tor network
if (initialized.compareAndSet(false, true)) {
val dataDir = File(context.filesDir, "arti").absolutePath
Log.d("TorService") { "Initializing Arti with data dir: $dataDir" }