Skip to content

Commit dbbcc31

Browse files
[jni] Fix the context when using multiple flutter engines
1 parent dfd5857 commit dbbcc31

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+860
-2716
lines changed

pkgs/jni/CHANGELOG.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
## 0.14.3-wip
1+
## 0.15.0-wip
22

3+
- **Breaking Change**: Removed `Jni.getApplicationClassLoader()`,
4+
`Jni.getCurrentActivity()`, and `Jni.getCachedApplicationContext()`. Instead
5+
use `Jni.applicationContext(engineId)` to access the application context and
6+
listen to `Jni.activityStream(engineId)` to acccess the activity.
37
- Update to the latest lints.
48

59
## 0.14.2
@@ -10,12 +14,13 @@
1014

1115
## 0.14.1
1216

13-
- Updated `bin/setup.dart` to use Gradle instead of Maven for building Java sources. Added gradle executables
14-
and bootstrap jars [#2003](https://github.com/dart-lang/native/issues/2003)
15-
- Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance
17+
- Updated `bin/setup.dart` to use Gradle instead of Maven for building Java
18+
sources. Added gradle executables and bootstrap jars
19+
[#2003](https://github.com/dart-lang/native/issues/2003)
20+
- Added `JObject.isInstanceOf` which checks whether a `JObject` is an instance
1621
of a java class.
17-
- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where
18-
Java interfaces implemented in on the main thread in Dart could deadlock when
22+
- Fixed a [bug](https://github.com/dart-lang/native/issues/1908) where Java
23+
interfaces implemented in on the main thread in Dart could deadlock when
1924
invoked from the main thread outside the context of a Dart isolate.
2025

2126
## 0.14.0

pkgs/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,88 @@
1010
import io.flutter.embedding.engine.plugins.FlutterPlugin;
1111
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
1212
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.Objects;
16+
import java.util.concurrent.ConcurrentHashMap;
1317

1418
public class JniPlugin implements FlutterPlugin, ActivityAware {
19+
private static final ConcurrentHashMap<Long, JniPlugin> pluginMap = new ConcurrentHashMap<>();
20+
21+
private long engineId;
22+
private Context context;
23+
private Activity activity;
24+
private final List<ActivityListener> activityListeners = new ArrayList<>();
25+
26+
public static @NonNull Context getApplicationContext(long engineId) {
27+
return Objects.requireNonNull(pluginMap.get(engineId)).context;
28+
}
29+
30+
public interface ActivityListener {
31+
void onActivityChanged(Activity activity);
32+
}
33+
34+
public static void addActivityListener(long engineId, @NonNull ActivityListener listener) {
35+
var plugin = Objects.requireNonNull(pluginMap.get(engineId));
36+
plugin.activityListeners.add(listener);
37+
listener.onActivityChanged(plugin.activity);
38+
}
39+
40+
public static void removeActivityListener(long engineId, @NonNull ActivityListener listener) {
41+
var plugin = Objects.requireNonNull(pluginMap.get(engineId));
42+
plugin.activityListeners.remove(listener);
43+
}
1544

1645
@Override
1746
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
18-
setup(binding.getApplicationContext());
47+
//noinspection deprecation
48+
engineId = binding.getFlutterEngine().getEngineId();
49+
context = binding.getApplicationContext();
50+
pluginMap.put(engineId, this);
1951
}
2052

21-
private void setup(Context context) {
22-
initializeJni(context, getClass().getClassLoader());
53+
@Override
54+
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
55+
context = null;
56+
activity = null;
57+
pluginMap.remove(engineId);
2358
}
2459

25-
@Override
26-
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
60+
private void notifyActivityListeners() {
61+
for (var listener : activityListeners) {
62+
listener.onActivityChanged(activity);
63+
}
64+
}
65+
66+
private void setActivity(Activity newActivity) {
67+
activity = newActivity;
68+
notifyActivityListeners();
69+
}
2770

28-
// Activity handling methods
2971
@Override
3072
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
31-
Activity activity = binding.getActivity();
32-
setJniActivity(activity, activity.getApplicationContext());
73+
setActivity(binding.getActivity());
3374
}
3475

3576
@Override
36-
public void onDetachedFromActivityForConfigChanges() {}
77+
public void onDetachedFromActivityForConfigChanges() {
78+
setActivity(null);
79+
}
3780

3881
@Override
3982
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
40-
Activity activity = binding.getActivity();
41-
setJniActivity(activity, activity.getApplicationContext());
83+
setActivity(binding.getActivity());
4284
}
4385

4486
@Override
45-
public void onDetachedFromActivity() {}
46-
47-
native void initializeJni(Context context, ClassLoader classLoader);
87+
public void onDetachedFromActivity() {
88+
setActivity(null);
89+
}
4890

49-
native void setJniActivity(Activity activity, Context context);
91+
static native void setClassLoader(ClassLoader classLoader);
5092

5193
static {
5294
System.loadLibrary("dartjni");
95+
setClassLoader(JniPlugin.class.getClassLoader());
5396
}
5497
}

pkgs/jni/example/lib/main.dart

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import 'dart:ffi';
88
import 'dart:io';
9+
import 'dart:ui';
910

1011
import 'package:ffi/ffi.dart';
1112
import 'package:flutter/material.dart';
@@ -73,12 +74,13 @@ String backAndForth() {
7374
return dartString;
7475
}
7576

76-
void quit() {
77-
JObject.fromReference(Jni.getCurrentActivity()).use((ac) =>
78-
ac.jClass.instanceMethodId("finish", "()V").call(ac, jvoid.type, []));
77+
void quit(JObject activity) {
78+
activity.jClass
79+
.instanceMethodId("finish", "()V")
80+
.call(activity, jvoid.type, []);
7981
}
8082

81-
void showToast(String text) {
83+
void showToast(String text, JObject activity) {
8284
// This is example for calling your app's custom java code.
8385
// Place the Toaster class in the app's android/ source Folder, with a Keep
8486
// annotation or appropriate proguard rules to retain classes in release mode.
@@ -93,9 +95,9 @@ void showToast(String text) {
9395
'(Landroid/app/Activity;Landroid/content/Context;'
9496
'Ljava/lang/CharSequence;I)'
9597
'Lcom/github/dart_lang/jni_example/Toaster;');
96-
final toaster = makeText.call(toasterClass, JObject.type, [
97-
Jni.getCurrentActivity(),
98-
Jni.getCachedApplicationContext(),
98+
final toaster = makeText(toasterClass, JObject.type, [
99+
activity,
100+
Jni.androidApplicationContext(PlatformDispatcher.instance.engineId!),
99101
'😀'.toJString(),
100102
0,
101103
]);
@@ -104,19 +106,20 @@ void showToast(String text) {
104106
}
105107

106108
void main() {
109+
WidgetsFlutterBinding.ensureInitialized();
107110
if (!Platform.isAndroid) {
108111
Jni.spawn();
109112
}
110113
final examples = [
111-
Example("String.valueOf(1332)", () => toJavaStringUsingEnv(1332)),
112-
Example("Generate random number", () => randomUsingEnv(180),
114+
Example("String.valueOf(1332)", (_) => toJavaStringUsingEnv(1332)),
115+
Example("Generate random number", (_) => randomUsingEnv(180),
113116
runInitially: false),
114-
Example("Math.random()", () => randomDouble(), runInitially: false),
117+
Example("Math.random()", (_) => randomDouble(), runInitially: false),
115118
if (Platform.isAndroid) ...[
116119
Example("Minutes of usage since reboot",
117-
() => (uptime() / (60 * 1000)).floor()),
118-
Example("Back and forth string conversion", () => backAndForth()),
119-
Example("Device name", () {
120+
(_) => (uptime() / (60 * 1000)).floor()),
121+
Example("Back and forth string conversion", (_) => backAndForth()),
122+
Example("Device name", (_) {
120123
final buildClass = JClass.forName("android/os/Build");
121124
return buildClass
122125
.staticFieldId("DEVICE", JString.type.signature)
@@ -125,13 +128,15 @@ void main() {
125128
}),
126129
Example(
127130
"Package name",
128-
() => JObject.fromReference(Jni.getCurrentActivity()).use((activity) =>
129-
activity.jClass
130-
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
131-
.call(activity, JString.type, [])),
131+
(activity) => activity.jClass
132+
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
133+
.call(activity, JString.type, []),
134+
),
135+
Example(
136+
"Show toast",
137+
(activity) => showToast("Hello from JNI!", activity),
138+
runInitially: false,
132139
),
133-
Example("Show toast", () => showToast("Hello from JNI!"),
134-
runInitially: false),
135140
Example(
136141
"Quit",
137142
quit,
@@ -144,7 +149,7 @@ void main() {
144149

145150
class Example {
146151
String title;
147-
dynamic Function() callback;
152+
dynamic Function(JObject activity) callback;
148153
bool runInitially;
149154
Example(this.title, this.callback, {this.runInitially = true});
150155
}
@@ -158,8 +163,12 @@ class MyApp extends StatefulWidget {
158163
}
159164

160165
class _MyAppState extends State<MyApp> {
166+
late Stream<JObject?> activityStream;
167+
161168
@override
162169
void initState() {
170+
activityStream =
171+
Jni.androidActivities(PlatformDispatcher.instance.engineId!);
163172
super.initState();
164173
}
165174

@@ -170,20 +179,29 @@ class _MyAppState extends State<MyApp> {
170179
appBar: AppBar(
171180
title: const Text('JNI Examples'),
172181
),
173-
body: ListView.builder(
174-
itemCount: widget.examples.length,
175-
itemBuilder: (context, i) {
176-
final eg = widget.examples[i];
177-
return ExampleCard(eg);
182+
body: StreamBuilder(
183+
stream: activityStream,
184+
builder: (_, snapshot) {
185+
if (!snapshot.hasData || snapshot.data == null) {
186+
return Container();
187+
}
188+
return ListView.builder(
189+
itemCount: widget.examples.length,
190+
itemBuilder: (context, i) {
191+
final eg = widget.examples[i];
192+
return ExampleCard(eg, snapshot.data!);
193+
},
194+
);
178195
}),
179196
),
180197
);
181198
}
182199
}
183200

184201
class ExampleCard extends StatefulWidget {
185-
const ExampleCard(this.example, {super.key});
202+
const ExampleCard(this.example, this.activity, {super.key});
186203
final Example example;
204+
final JObject activity;
187205

188206
@override
189207
_ExampleCardState createState() => _ExampleCardState();
@@ -210,7 +228,7 @@ class _ExampleCardState extends State<ExampleCard> {
210228
var hasError = false;
211229
if (_run) {
212230
try {
213-
result = eg.callback().toString();
231+
result = eg.callback(widget.activity).toString();
214232
} on Exception catch (e) {
215233
hasError = true;
216234
result = e.toString();

0 commit comments

Comments
 (0)