fix(android): stop crash when USB is first used off the main thread
On Android, our app crashed the moment it listed serial ports, because it calls available_ports from an async Tauri command (a tokio worker thread):
FATAL EXCEPTION: Thread-3
java.lang.ClassNotFoundException: app.tauri.serialplugin.UsbNative
fd_bridge::cache() looks up UsbNative with FindClass the first time it is used, on whatever thread gets there first. On a thread Rust attached with attach_current_thread, JNI uses the system class loader, which can't see app classes, so the lookup fails. The error is still pending when ? returns early, so ART kills the app when the thread detaches. It only works if the first USB call happens to come from the main thread (e.g. the sync JS commands). Any call from async commands, tokio tasks or spawned threads crashes, and so could the port IO that now runs off the main thread.
The fix: nativeInit is a static native method on UsbNative, so its jclass argument is the class itself, already resolved by the app class loader. The patch keeps a global ref to it there, next to where the JVM is stored, and drops the thread-dependent FindClass. The public API doesn't change. As before, calls that run before bind() return "JNI not initialized".
Testing
- On a Galaxy Tab A9 (Android 16), release build: before the fix, the app crashed on the first
available_portsfrom a tokio worker. After it, enumeration runs ontokio-rt-workerand returns normally, with nothing in the crash buffer. cargo fmt --all -- --checkpasses.cargo clippy -p tauri-plugin-serialplugin --target aarch64-linux-android --lib -- -D warningspasses.--all-targetsforaarch64-linux-androidfails to compile the lib tests (unresolvedcrate::AtResultFormat, etc.). That happens identically onmain, so it's unrelated to this change.
🤖 Generated with Claude Code
Summary by cubic
Fixes an Android crash when serial ports are first used from a non-main thread by caching the UsbNative JNI class in nativeInit instead of looking it up with FindClass on first use.
- On Rust-attached threads (async commands, tokio tasks),
FindClassuses the system class loader, which can't see app classes, so the lookup fails withClassNotFoundExceptionand ART kills the app at thread detach. nativeInitis a static native method onUsbNative, so itsjclassargument is already resolved by the app class loader; the fix keeps a global ref there.- Public API is unchanged; calls before
bind()still return "JNI not initialized".