Flutter JNI交互插件jni的使用
Flutter JNI交互插件jni的使用
插件简介
package:jni
是一个支持从Dart/Flutter代码访问JNI的库。它为由 jnigen
生成的绑定提供了通用的基础设施,以及一些实用的方法。
该库包含以下内容:
- 访问JNI中的
JNIEnv
和JavaVM
变量的功能,并封装了JNI提供的功能。JNIEnv
通过GlobalJniEnv
类型暴露出来,提供了一个轻量级的抽象,使其可以在多个线程中使用。 - 在桌面平台上启动JVM的功能(
Jni.spawn
)。 - 一些Android特定的帮助函数(获取应用程序上下文和当前活动引用)。
JObject
类,作为由jnigen
生成的类的基础类。- 常用的Java类如
JList
、JMap
、JInteger
等。
除了作为jnigen
生成的代码的基础库外,此库还可以用于JNI的一次性使用和调试。要从Java库生成类型安全的绑定,请使用jnigen
。
文档
test/
目录包含了带有注释的文件,解释了本模块的基本用法,而example/
目录则包含了一个Flutter示例,也涉及了一些Android特定的内容。
使用这个库假定你对JNI有一定程度的了解——它的线程模型和对象引用等。
迁移到0.8.0版本
有关迁移信息,请参阅changelog。
独立使用package:jni
(不使用package:jnigen
)的API已更改为使用JClass.forName
来查找类。通过检索到的JClass
访问方法和字段。例如,可以使用jClass.instanceMethodId
根据名称和签名查找实例方法。然后可以通过调用结果的JInstanceMethodId
来调用实例方法。
示例代码
下面是一个完整的示例demo,展示了如何在Flutter项目中使用package:jni
与JNI进行交互。
main.dart
// 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 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
import 'package:jni/jni.dart';
extension on String {
/// Returns a Utf-8 encoded `Pointer<Char>` with contents same as this string.
Pointer<Char> toNativeChars(Allocator allocator) {
return toNativeUtf8(allocator: allocator).cast<Char>();
}
}
// 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(arena),
"(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.DeleteGlobalRef(res);
env.DeleteGlobalRef(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, toJValues([n], allocator: arena));
env.DeleteGlobalRef(randomCls);
env.DeleteGlobalRef(random);
return res;
});
double randomDouble() {
final math = JClass.forName("java/lang/Math");
final random =
math.staticMethodId("random", "()D").call(math, jdouble.type, []);
math.release();
return random;
}
int uptime() {
return JClass.forName("android/os/SystemClock").use(
(systemClock) => systemClock
.staticMethodId("uptimeMillis", "()J")
.call(systemClock, jlong.type, []),
);
}
String backAndForth() {
final jstring = '🪓'.toJString();
final dartString = jstring.toDartString(releaseOriginal: true);
return dartString;
}
void quit() {
JObject.fromReference(Jni.getCurrentActivity()).use((ac) =>
ac.jClass.instanceMethodId("finish", "()V").call(ac, jvoid.type, []));
}
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
final toasterClass =
JClass.forName('com/github/dart_lang/jni_example/Toaster');
final makeText = toasterClass.staticMethodId(
'makeText',
'(Landroid/app/Activity;Landroid/content/Context;'
'Ljava/lang/CharSequence;I)'
'Lcom/github/dart_lang/jni_example/Toaster;');
final toaster = makeText.call(toasterClass, JObject.type, [
Jni.getCurrentActivity(),
Jni.getCachedApplicationContext(),
'😀'.toJString(),
0,
]);
final show = toasterClass.instanceMethodId('show', '()V');
show(toaster, jvoid.type, []);
}
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", () {
final buildClass = JClass.forName("android/os/Build");
return buildClass
.staticFieldId("DEVICE", JString.type.signature)
.get(buildClass, JString.type)
.toDartString(releaseOriginal: true);
}),
Example(
"Package name",
() => JObject.fromReference(Jni.getCurrentActivity()).use((activity) =>
activity.jClass
.instanceMethodId("getPackageName", "()Ljava/lang/String;")
.call(activity, JString.type, [])),
),
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),
]),
);
}
}
说明
- 初始化JVM:如果平台不是Android,则调用
Jni.spawn()
启动JVM。 - 定义示例功能:创建了一系列示例功能,包括字符串转换、随机数生成、设备信息获取、Toast显示等。
- UI展示:通过Flutter的
ListView.builder
展示每个示例功能的结果,并允许用户点击按钮运行或重新运行示例。
注意事项
- 线程模型:确保理解JNI的线程模型,特别是在多线程环境中使用时。
- 对象引用:正确管理JNI对象引用,以避免内存泄漏。
- 自定义Java代码:如果需要调用自定义的Java代码(如
Toaster
类),请确保在Android项目的源码中正确放置这些类,并添加适当的ProGuard规则以保留这些类。
希望这个示例能帮助你更好地理解和使用Flutter中的JNI交互插件。如果有任何问题或需要进一步的帮助,请随时提问!
更多关于Flutter JNI交互插件jni的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
1 回复