Skip to content

Wrap BroadcastRecever with jnigen caused native crash sometime #2808

@yanshouwang

Description

@yanshouwang

I have written a plugin with jnigen and when impl the BroadcastReceiver, we need impl it on Android side and then bind the impl with jnigen.

Here is the key codes I used in the plugin.

package dev.zeekr.connectivity

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent

interface JBroadcastReceiver {
    fun onReceive(context: Context, intent: Intent)
}

class JBroadcastReceiverImpl(private val receiver: JBroadcastReceiver) : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (context == null) throw NullPointerException("context is null")
        if (intent == null) throw NullPointerException("intent is null")
        this.receiver.onReceive(context, intent)
    }
}

consumer-rules.pro

-keep class dev.zeekr.connectivity.** { *; }

tool/jnigen.dart

import 'dart:io';

import 'package:jnigen/jnigen.dart';
import 'package:logging/logging.dart';

void main() async {
  final root = Platform.script.resolve('../');
  final config = Config(
    outputConfig: OutputConfig(
      dartConfig: DartCodeOutputConfig(
        path: root.resolve('lib/src/jni/'),
        structure: OutputStructure.packageStructure,
      ),
    ),
    classes: [
      'dev.zeekr.connectivity.JBroadcastReceiver',
      'dev.zeekr.connectivity.JBroadcastReceiverImpl',
    ],
    sourcePath: [root.resolve('android/src/main/kotlin/')],
    androidSdkConfig: AndroidSdkConfig(
      addGradleDeps: true,
      androidExample: 'example/',
    ),
    nonNullAnnotations: ['android.annotation.NonNull'],
    nullableAnnotations: ['android.annotation.Nullable'],
    logLevel: Level.ALL,
  );
  await generateJniBindings(config);
}

wifi_manager.dart

abstract interface class WifiManager$WifiStateChangedListener {
  factory WifiManager$WifiStateChangedListener({
    required void Function() onWifiStateChanged,
  }) => jni.Build$VERSION.SDK_INT >= jni.Build$VERSION_CODES.BAKLAVA
      ? WifiManager$WifiStateChangedListenerImpl36(
          onWifiStateChanged: onWifiStateChanged,
        )
      : WifiManager$WifiStateChangedListenerImpl(
          onWifiStateChanged: onWifiStateChanged,
        );
}

abstract interface class WifiManager {
  factory WifiManager() => WifiManagerImpl();

  void addWifiStateChangedListener(
    WifiManager$WifiStateChangedListener listener,
  );
  void removeWifiStateChangedListener(
    WifiManager$WifiStateChangedListener listener,
  );
}

final class WifiManager$WifiStateChangedListenerImpl
    implements WifiManager$WifiStateChangedListener {
  final jni.BroadcastReceiver api;

  WifiManager$WifiStateChangedListenerImpl.jni(this.api);

  factory WifiManager$WifiStateChangedListenerImpl({
    required void Function() onWifiStateChanged,
  }) {
    final api = jni.JBroadcastReceiverImpl(
      jni.JBroadcastReceiver.implement(
        jni.$JBroadcastReceiver(
          onReceive: (context, intent) {
            final action = intent.getAction();
            if (action != jni.WifiManager.WIFI_STATE_CHANGED_ACTION) return;
            onWifiStateChanged();
          },
        ),
      ),
    );
    return WifiManager$WifiStateChangedListenerImpl.jni(api);
  }
}

final class WifiManagerImpl implements WifiManager {
  final jni.WifiManager api;

  WifiManagerImpl.jni(this.api);

  factory WifiManagerImpl() {
    final apiOrNull = jni.ContextCompat.getSystemService(
      jni.context,
      jni.WifiManager.type.jClass,
      T: jni.WifiManager.type,
    );
    final api = ArgumentError.checkNotNull(apiOrNull);
    return WifiManagerImpl.jni(api);
  }

  @override
  void addWifiStateChangedListener(
    WifiManager$WifiStateChangedListener listener,
  ) => jni.Build$VERSION.SDK_INT >= jni.Build$VERSION_CODES.BAKLAVA
      ? api.addWifiStateChangedListener(
          jni.context.mainExecutor,
          listener.api36,
        )
      : jni.ContextCompat.registerReceiver(
          jni.context,
          listener.api,
          jni.IntentFilter.new$2(jni.WifiManager.WIFI_STATE_CHANGED_ACTION),
          jni.ContextCompat.RECEIVER_NOT_EXPORTED,
        );

  @override
  void removeWifiStateChangedListener(
    WifiManager$WifiStateChangedListener listener,
  ) => jni.Build$VERSION.SDK_INT >= jni.Build$VERSION_CODES.BAKLAVA
      ? api.removeWifiStateChangedListener(listener.api36)
      : jni.context.unregisterReceiver(listener.api);
}

extension WifiManager$WifiStateChangedListenerX
    on WifiManager$WifiStateChangedListener {
  jni.WifiManager$WifiStateChangedListener get api36 {
    final impl = this;
    if (impl is! WifiManager$WifiStateChangedListenerImpl36) throw TypeError();
    return impl.api;
  }

  jni.BroadcastReceiver get api {
    final impl = this;
    if (impl is! WifiManager$WifiStateChangedListenerImpl) throw TypeError();
    return impl.api;
  }
}

When I ued the WifiManager to listen the wifi state, it works fine, but sometimes the app crashed when I turn off the wifi.

Here is the crash logs

E/Dart    (31269): ../../../flutter/third_party/dart/runtime/vm/runtime_entry.cc: 4637: error: Callback invoked after it has been deleted.

E/DartVM  (31269): version=3.9.2 (stable) (Wed Aug 27 03:49:40 2025 -0700) on "android_arm64"

E/DartVM  (31269): pid=31269, thread=504513725696, isolate_group=main(0xb4000074a45eb800), isolate=main(0xb4000074bf2ff480)

E/DartVM  (31269): os=android, arch=arm64, comp=yes, sim=no

E/DartVM  (31269): isolate_instructions=744ba94dc0, vm_instructions=744ba94dc0

E/DartVM  (31269): fp=7fe4f44040, sp=7fe4f44010, pc=744b8a001c

E/DartVM  (31269):   pc 0x000000744b8a001c fp 0x0000007fe4f44040 /data/app/~~fTFhIk566QS-mAuvG4Aisg==/dev.zeekr.connectivity_example-u6JoM-Urj_q77T0PrGUtCw==/base.apk!/lib/arm64-v8a/libflutter.so+0x1fb001c

E/DartVM  (31269):   pc 0x000000744b78e3f8 fp 0x0000007fe4f441e8 /data/app/~~fTFhIk566QS-mAuvG4Aisg==/dev.zeekr.connectivity_example-u6JoM-Urj_q77T0PrGUtCw==/base.apk!/lib/arm64-v8a/libflutter.so+0x1e9e3f8

E/DartVM  (31269):   pc 0x00000074294ac6ac fp 0x0000007fe4f442d0 Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke+0x2fc

E/DartVM  (31269):   pc 0x00000074294ac6ac fp 0x0000007fe4f44490 Java_com_github_dart_1lang_jni_PortProxyBuilder__1invoke+0x2fc

E/DartVM  (31269):   pc 0x00000074c7ad7648 fp 0x0000007fe4f444d0 /apex/com.android.art/lib64/libart.so+0x2d7648

E/DartVM  (31269): -- End of DumpStackTrace

F/libc    (31269): Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 31269 (ctivity_example), pid 31269 (ctivity_example)

Process name is dev.zeekr.connectivity_example, not key_process

keyProcess: 0

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

Build fingerprint: 'OnePlus/OnePlus9_CH/OnePlus9:12/RKQ1.211103.002/R.202203260223:user/release-keys'

Revision: '0'

ABI: 'arm64'

Timestamp: 2025-11-20 17:57:19.184345808+0800

Process uptime: 0s

Cmdline: dev.zeekr.connectivity_example

pid: 31269, tid: 31269, name: ctivity_example  >>> dev.zeekr.connectivity_example <<<

uid: 10280

signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------

Abort message: '../../../flutter/third_party/dart/runtime/vm/runtime_entry.cc: 4637: error: Callback invoked after it has been deleted.'

    x0  0000000000000000  x1  0000000000007a25  x2  0000000000000006  x3  0000007fe4f43fa0

    x4  0000000000000010  x5  0000000000000010  x6  0000000000000010  x7  7f7f7f7f7f7f7f7f

    x8  00000000000000f0  x9  1fe1585bfd33801b  x10 0000000000000000  x11 ffffff80fffffbdf

    x12 0000000000000001  x13 000000000000001a  x14 0000007fe4f427d0  x15 0000000034155555

    x16 000000754d10ebc0  x17 000000754d0e9680  x18 00000075772d2000  x19 0000000000007a25

    x20 0000000000007a25  x21 00000000ffffffff  x22 0000007fe4f44200  x23 0000007fe4f441f8

    x24 00000074b5700168  x25 0000007fe4f445f8  x26 0000000018380001  x27 0000000000000000

    x28 0000007fe4f444d0  x29 0000007fe4f44020

    lr  000000754d09999c  sp  0000007fe4f43f80  pc  000000754d0999c8  pst 0000000000001000

backtrace:

      #00 pc 00000000000799c8  /apex/com.android.runtime/lib64/bionic/libc.so (abort+168) (BuildId: ef9f41416d59cdf9c6d7529d11b53eff)

      #01 pc 0000000001e9e3fc  /data/app/~~fTFhIk566QS-mAuvG4Aisg==/dev.zeekr.connectivity_example-u6JoM-Urj_q77T0PrGUtCw==/base.apk!libflutter.so (BuildId: 3309f17d52486f5bf2fc84011621fdd0785efc8e)

      #02 pc 0000000001fc80d0  /data/app/~~fTFhIk566QS-mAuvG4Aisg==/dev.zeekr.connectivity_example-u6JoM-Urj_q77T0PrGUtCw==/base.apk!libflutter.so (BuildId: 3309f17d52486f5bf2fc84011621fdd0785efc8e)

      #03 pc 000000000001001c  [anon:FfiCallbackMetadata::TrampolinePage]

Lost connection to device.



Exited.

Seems the callback is deleted when Android calling it. I don't have any idea how this happened, is this a bug or something I missed in my project?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions