fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
3.61k stars 254 forks source link

Android only bug - Due to how the compiled library is being loaded - rather loaded twice (JNI load issue) #1868

Closed debanjanbasu closed 1 month ago

debanjanbasu commented 1 month ago

Describe the bug

I'm working on a project, which does require android_context to be initialized beforehand. The challenge is the I followed this guide: https://cjycode.com/flutter_rust_bridge/manual/troubleshooting#android-context-was-not-initialized-or-ndk_context-initialization

`pub mod api;
mod frb_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */

#[cfg(target_os = "android")]
use {
    jni::{sys::jint, JNIVersion, JavaVM},
    log::info,
    ndk_context::initialize_android_context,
};

#[cfg(target_os = "android")]
#[no_mangle]
pub extern "C" fn JNI_OnLoad(vm: JavaVM, res: *mut std::os::raw::c_void) -> jint {
    info!("Threads attached: {:?}", vm.threads_attached());

    // Initialize the android context
    unsafe { initialize_android_context(vm.get_java_vm_pointer() as *mut std::ffi::c_void, res) };

    JNIVersion::V6.into()
}

and obviously in the the main Kotling file:

package com.example.spareshare

import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity() {
    // this `init` block, where "foo" is the name of your library
    // ex: if it's libfoo.so, then use "foo"
    init {
        System.loadLibrary("rust_lib_spareshare")
    }
}

Also, I'm using regular await Rustlib.init()

When my application is trying to attach the current android thread, it's getting attached to different threads, as two threads are being spun up.

info!("👺 Initializing Android Context...");

        // Get a VM for executing JNI calls
        let ctx = android_context();
        let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
        let _context = unsafe { JObject::from_raw(ctx.context().cast()) };
        let env = vm.attach_current_thread_as_daemon()?;

        info!("Threads attached: {:?}", vm.threads_attached());`

The above code is getting attached to the second thread, wherein the regular other FRB functions are to the first one. Is there a way to avoid this? 🤔 I've been splitting my head over this for almost a week now. I've tried all possibly ways, as am not an avid Kotling developer, I really want to avoid customizing the Kotlin files, also that might not even solve the issue, if FRB, and the JNI_Onload are running on two separate threads.

Steps to reproduce

It's a unique problem, but I would try to reproduce the problem from an example code. I'm sure there might be a simpler workaround.

Logs

The issue is not being logged from FRB, rather when the actual flutter app is being deployed.

INFO: Precompiled binaries are disabled
INFO: Building rust_lib_spareshare for aarch64-linux-android
INFO: Building rust_lib_spareshare for i686-linux-android
INFO: Building rust_lib_spareshare for x86_64-linux-android
Running Gradle task 'assembleDebug'...                             32.3s
✓ Built build/app/outputs/flutter-apk/app-debug.apk
Installing build/app/outputs/flutter-apk/app-debug.apk...        1,774ms
E/flutter (28603): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: AnyhowException(Generic: Could not initialize the protected store.
E/flutter (28603):
E/flutter (28603): Stack backtrace:
E/flutter (28603):    0: <unknown>
E/flutter (28603):    1: <unknown>
E/flutter (28603):    2: <unknown>
E/flutter (28603):    3: <unknown>
E/flutter (28603):    4: <unknown>
E/flutter (28603):    5: <unknown>
E/flutter (28603):    6: <unknown>
E/flutter (28603):    7: <unknown>
E/flutter (28603):    8: <unknown>

The root cause is that it's running in two separate threads.

Expected behavior

Expected to have one thread so when we're trying to get the android_context, it does work as expected, unless we're actually spinning up another thread.

Generated binding code

No response

OS

MacOS

Version of flutter_rust_bridge_codegen

dev.30

Flutter info

[✓] Flutter (Channel main, 3.22.0-6.0.pre.24, on macOS 14.5 23F5049f darwin-arm64, locale en-AU)
    • Flutter version 3.22.0-6.0.pre.24 on channel main at /opt/homebrew/Caskroom/flutter/3.16.2/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 4967a94cd9 (3 hours ago), 2024-04-09 11:30:04 +0300
    • Engine revision 5a824e22de
    • Dart version 3.5.0 (build 3.5.0-36.0.dev)
    • DevTools version 2.34.1

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/debanjanbasu/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /Users/debanjanbasu/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15E204a
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)

[✓] VS Code (version 1.89.0-insider)
    • VS Code at /Applications/Visual Studio Code - Insiders.app/Contents
    • Flutter extension version 3.87.20240402

[✓] Connected device (5 available)
    • sdk gphone64 arm64 (mobile)     • emulator-5554             • android-arm64  • Android 14 (API 34) (emulator)
    • Debanjan Basu’s iPhone (mobile) • 00008110-001A44C63E10401E • ios            • iOS 17.5 21F5048f
    • macOS (desktop)                 • macos                     • darwin-arm64   • macOS 14.5 23F5049f darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad     • darwin         • macOS 14.5 23F5049f darwin-arm64
    • Chrome (web)                    • chrome                    • web-javascript • Google Chrome 123.0.6312.107

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Version of clang++

No response

Additional context

No response

welcome[bot] commented 1 month ago

Hi! Thanks for opening your first issue here! :smile:

fzyzcjy commented 1 month ago

Hi, I am also not an Android expert, and here are my two cents:

debanjanbasu commented 1 month ago

Finally got to solve it by using this code block in this way:

in MainActivity.kt

package com.example.spareshare

import android.content.Context
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(
        @NonNull flutterEngine: FlutterEngine,
    ) {
        super.configureFlutterEngine(flutterEngine)
        flutterEngine.plugins.add(MyPlugin())
    }
}

class MyPlugin : FlutterPlugin, MethodCallHandler {
    companion object {
        init {
            System.loadLibrary("rust_lib_spareshare")
        }
    }

    external fun init_android(ctx: Context)

    override fun onAttachedToEngine(
        @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding,
    ) {
        init_android(flutterPluginBinding.applicationContext)
    }

    override fun onMethodCall(
        @NonNull call: MethodCall,
        @NonNull result: Result,
    ) {
        result.notImplemented()
    }

    override fun onDetachedFromEngine(
        @NonNull binding: FlutterPlugin.FlutterPluginBinding,
    ) {
    }
}

and in lib.rs

#[cfg(target_os = "android")]
use {
    jni::{objects::JClass, objects::JObject, JNIEnv},
    mylib::setup_android,
};

#[cfg(target_os = "android")]
#[no_mangle]
pub extern "system" fn Java_com_example_spareshare_MyPlugin_init_1android(
    env: JNIEnv,
    _class: JClass,
    ctx: JObject,
) {
    setup_android(env, ctx);
}
debanjanbasu commented 1 month ago

Closing it, as has been resolved.

fzyzcjy commented 1 month ago

Happy to see it is resolved!

github-actions[bot] commented 3 weeks ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.