"Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++." After start a background service and closes the app
Asked Answered
N

7

35

I'm trying to build an app that uses some packages as Location (https://pub.dev/packages/location) and Compass (https://pub.dev/packages/flutter_compass) and keep a background service tracking user location. Everything works fine until I start the service to track the location.

With the service active the whole app never stops, for example, when I close the app without service the compass stops too, but with the service running, compass keeps running too. Actually it returns a error "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: hemanthraj/flutter_compass". The same thing happens with location: "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: lyokone/locationstream". After this even if I open again the project it don't work anymore... I'm trying to make a service totally independent from rest of project.

I'll show you the service implementation (Android)

public class CompassApplication extends FlutterApplication {

    @Override
    public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("messages", "Messages", NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = getSystemService(NotificationManager.class);
            if (manager != null) {
                manager.createNotificationChannel(channel);
            }
        }
    }
}
class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        val intent = Intent(this, LocationService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent)
        } else {
            startService(intent)
        }
    }
}
public class LocationService extends Service {

    static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10 * 60 * 1000; // 10 minutes
    static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    private LocationRequest mLocationRequest;
    private FusedLocationProviderClient mFusedLocationClient;
    private LocationCallback mLocationCallback;

    @Override
    public void onCreate() {
        super.onCreate();

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                onNewLocation(locationResult.getLastLocation());
            }
        };

        createLocationRequest();
        getLastLocation();
        requestLocationUpdates();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "messages")
                    .setSmallIcon(R.mipmap.ic_launcher_foreground);

            startForeground(101, builder.build());
        }
    }

    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    private void getLastLocation() {
        try {
            mFusedLocationClient.getLastLocation()
                    .addOnCompleteListener(task -> {
                        if (task.isSuccessful() && task.getResult() != null) {
                            onNewLocation(task.getResult());
                        }
                    });
        } catch (SecurityException ignored) {}
    }

    public void requestLocationUpdates() {
        Utils.setRequestingLocationUpdates(this, true);
        startService(new Intent(getApplicationContext(), LocationUpdatesService.class));
        try {
            mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
        } catch (SecurityException unlikely) {
            Utils.setRequestingLocationUpdates(this, false);
        }
    }

    private void onNewLocation(Location location) {
        // TODO: deal with current location
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Another problem is even when I don't close the app, it is draining a lot of battery. Thanks!

Notus answered 21/5, 2020 at 12:49 Comment(1)
use native method invoke instead.Goofball
D
4

From the plugin package developer perspective:

Since you are using a package that is causing this, I'll mention that I was causing the same issue in my package.

In my case, I was using the wrong (or old) method channel. I had saved an instance of a method channel from when the service (broadcast receiver) was running, and tried to use that to communicate with the running application.

When cleaning up the service (or Broadcast receiver), I had destroyed the Flutter Engine and therefore detached the Flutter JNI from the C++ engine. So when I went to use that method channel, which uses the detached Flutter JNI, you get the error:

Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++.

I just had to use the FlutterEngine provided by the FlutterActivity (or FlutterFragment or Application) to get a new MethodChannel.

In my case, I am working on a Flutter package plugin (on Android). What this means is inside the plugin code, I should not any caching of methodChannels accidentally:

public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    final MethodChannel methodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "example-method-channel-name");
}

Your issue

In your dependency's case, it might be that they do not support usage in a service: they do some caching / singletons and re-use method channels or Flutter JNIs that are no longer attached. They might assume that the plugin is instantiated once, and not re-instantiated when the service runs (or app starts whilst service is already running). They might register callbacks to receive data from the OS, and then not unregister the old one when a new plugin with a new Flutter Engine is created.

There is no quick fix, and the package has to be built to support this.

Deason answered 11/11, 2021 at 21:35 Comment(0)
F
2

add the follwoing code to native android mainActivity.kt

package <your.package.name>

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);
    }
}
Frascati answered 27/5, 2021 at 12:0 Comment(0)
A
1

Close the application from the background. Run "flutter clean" Run "flutter run" It worked for me.

Ambrosius answered 3/12, 2020 at 10:31 Comment(0)
C
0

This problem occurred to me while detecting objects on camera with TFLite. I'm not quite sure, but every time I've noticed the phone is overheating,

If there was an event that requires intensive matrix processing like me, the problem is that the ARM processor you run the application cannot do, but battery-related service errors are really interesting.

Cottonmouth answered 25/11, 2020 at 7:2 Comment(0)
C
0

and this is my best answer: That bug would cause Dart->Java messages to be dropped at a time when the FlutterNativeView was still alive. This issue looks like it involves Java->Dart messages.

Cottonmouth answered 25/11, 2020 at 7:6 Comment(0)
A
0

I ran into this issue due to one of the Android developer options: Don't keep activities. This should be disabled unless you want to test state restoration.

In our case this error popped up during login via web view. The instant the web view opens, the flutter activity was backgrounded and therefore killed, closing all open method channels. Once returning from the login web view, the main activity was reinstantiated, but the login callback had no where to go.

Altdorfer answered 18/4 at 23:2 Comment(0)
D
-1
**This helps me solve this problem.**

import io.flutter.embedding.engine.FlutterJNI;

  
  private MethodChannel methodChannel = null;
  private EventChannel eventChannel = null;
  private EventChannel.EventSink eventSink;
  private FlutterJNI flutterJNI = new FlutterJNI();

 @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    methodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "sariska_media_transport_flutter");
    eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "sariskaMediaTransportEvent");
    if (methodChannel != null) {
      methodChannel.setMethodCallHandler(this);
    }
    if (eventChannel != null) {
      eventChannel.setStreamHandler(this);
    }

    flutterJNI.attachToNative();

    applicationContext = flutterPluginBinding.getApplicationContext();
    connectionPlugin = new ConnectionPlugin(flutterPluginBinding.getBinaryMessenger());
    conferencePlugin = new ConferencePlugin(flutterPluginBinding.getBinaryMessenger());
    trackPlugin = new TrackPlugin(flutterPluginBinding.getBinaryMessenger());

    FlutterEngine flutterEngine = flutterPluginBinding.getFlutterEngine();
    PlatformViewRegistry registry = flutterEngine.getPlatformViewsController().getRegistry();
    registry.registerViewFactory(
        "DummyView",
        new SariskaSurfaceViewFactory(flutterPluginBinding.getBinaryMessenger()));
  }

    
  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    if (methodChannel != null) {
      methodChannel.setMethodCallHandler(null);
      methodChannel = null;
    }

    if (eventChannel != null) {
      eventChannel.setStreamHandler(null);
      eventChannel = null;
    }
    flutterJNI.attachToNative();
  }
Dode answered 25/1 at 15:37 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Agency

© 2022 - 2024 — McMap. All rights reserved.