jni 0.6.1 jni: ^0.6.1 copied to clipboard
A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen.
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// ignore_for_file: library_private_types_in_public_api
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:ffi';
import 'package:jni/jni.dart';
// An example of calling JNI methods using low level primitives.
// GlobalJniEnv is a thin abstraction over JNIEnv in JNI C API.
//
// For a more ergonomic API for common use cases of calling methods and
// accessing fields, see next examples using JObject and JClass.
String toJavaStringUsingEnv(int n) => using((arena) {
final env = Jni.env;
final cls = env.FindClass("java/lang/String".toNativeChars(arena));
final mId = env.GetStaticMethodID(cls, "valueOf".toNativeChars(),
"(I)Ljava/lang/String;".toNativeChars(arena));
final i = arena<JValue>();
i.ref.i = n;
final res = env.CallStaticObjectMethodA(cls, mId, i);
final str = env.toDartString(res);
env.deleteAllRefs([res, cls]);
return str;
});
int randomUsingEnv(int n) => using((arena) {
final env = Jni.env;
final randomCls = env.FindClass("java/util/Random".toNativeChars(arena));
final ctor = env.GetMethodID(
randomCls, "<init>".toNativeChars(arena), "()V".toNativeChars(arena));
final random = env.NewObject(randomCls, ctor);
final nextInt = env.GetMethodID(randomCls, "nextInt".toNativeChars(arena),
"(I)I".toNativeChars(arena));
final res = env.CallIntMethodA(random, nextInt, Jni.jvalues([n]));
env.deleteAllRefs([randomCls, random]);
return res;
});
double randomDouble() {
final math = Jni.findJClass("java/lang/Math");
final random = math.callStaticMethodByName<double>("random", "()D", []);
math.release();
return random;
}
int uptime() {
return Jni.findJClass("android/os/SystemClock").use(
(systemClock) => systemClock.callStaticMethodByName<int>(
"uptimeMillis", "()J", [], JniCallType.longType),
);
}
String backAndForth() {
final jstring = '🪓'.toJString();
final dartString = jstring.toDartString(releaseOriginal: true);
return dartString;
}
void quit() {
JObject.fromRef(Jni.getCurrentActivity())
.use((ac) => ac.callMethodByName<void>("finish", "()V", []));
}
void showToast(String text) {
// This is example for calling your app's custom java code.
// Place the Toaster class in the app's android/ source Folder, with a Keep
// annotation or appropriate proguard rules to retain classes in release mode.
//
// In this example, Toaster class wraps android.widget.Toast so that it
// can be called from any thread. See
// android/app/src/main/java/com/github/dart_lang/jni_example/Toaster.java
Jni.invokeStaticMethod<JObject>(
"com/github/dart_lang/jni_example/Toaster",
"makeText",
"(Landroid/app/Activity;Landroid/content/Context;"
"Ljava/lang/CharSequence;I)"
"Lcom/github/dart_lang/jni_example/Toaster;",
[
Jni.getCurrentActivity(),
Jni.getCachedApplicationContext(),
"😀",
0,
]).callMethodByName<void>("show", "()V", []);
}
void main() {
if (!Platform.isAndroid) {
Jni.spawn();
}
final examples = [
Example("String.valueOf(1332)", () => toJavaStringUsingEnv(1332)),
Example("Generate random number", () => randomUsingEnv(180),
runInitially: false),
Example("Math.random()", () => randomDouble(), runInitially: false),
if (Platform.isAndroid) ...[
Example("Minutes of usage since reboot",
() => (uptime() / (60 * 1000)).floor()),
Example("Back and forth string conversion", () => backAndForth()),
Example(
"Device name",
() => Jni.retrieveStaticField<String>(
"android/os/Build", "DEVICE", "Ljava/lang/String;")),
Example(
"Package name",
() => JObject.fromRef(Jni.getCurrentActivity()).use((activity) =>
activity.callMethodByName<String>(
"getPackageName", "()Ljava/lang/String;", [])),
),
Example("Show toast", () => showToast("Hello from JNI!"),
runInitially: false),
Example(
"Quit",
quit,
runInitially: false,
),
]
];
runApp(MyApp(examples));
}
class Example {
String title;
dynamic Function() callback;
bool runInitially;
Example(this.title, this.callback, {this.runInitially = true});
}
class MyApp extends StatefulWidget {
const MyApp(this.examples, {Key? key}) : super(key: key);
final List<Example> examples;
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('JNI Examples'),
),
body: ListView.builder(
itemCount: widget.examples.length,
itemBuilder: (context, i) {
final eg = widget.examples[i];
return ExampleCard(eg);
}),
),
);
}
}
class ExampleCard extends StatefulWidget {
const ExampleCard(this.example, {Key? key}) : super(key: key);
final Example example;
@override
_ExampleCardState createState() => _ExampleCardState();
}
class _ExampleCardState extends State<ExampleCard> {
Widget _pad(Widget w, double h, double v) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: w);
}
bool _run = false;
@override
void initState() {
super.initState();
_run = widget.example.runInitially;
}
@override
Widget build(BuildContext context) {
final eg = widget.example;
var result = "";
var hasError = false;
if (_run) {
try {
result = eg.callback().toString();
} on Exception catch (e) {
hasError = true;
result = e.toString();
} on Error catch (e) {
hasError = true;
result = e.toString();
}
}
var resultStyle = const TextStyle(fontFamily: "Monospace");
if (hasError) {
resultStyle = const TextStyle(fontFamily: "Monospace", color: Colors.red);
}
return Card(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(eg.title,
softWrap: true,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
_pad(
Text(result.toString(), softWrap: true, style: resultStyle), 8, 16),
_pad(
ElevatedButton(
child: Text(_run ? "Run again" : "Run"),
onPressed: () => setState(() => _run = true),
),
8,
8),
]),
);
}
}