Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 890
Marcelo Pinheiro Montanher Avatar asked May 21 '20 12:05

Marcelo Pinheiro Montanher


2 Answers

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.

like image 142
Ben Butterworth Avatar answered Nov 16 '22 13:11

Ben Butterworth


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);
    }
}
like image 30
junaidh haneef Avatar answered Nov 16 '22 13:11

junaidh haneef