-
Notifications
You must be signed in to change notification settings - Fork 12
Initial JNI support #11
Changes from 14 commits
7551ff3
8bbeda4
c0b1a49
686ae25
852691f
2a1e7e0
f09ddda
ab793ae
01aef56
e4e3fd0
2fa0c92
7e443a2
9b85ac5
f820996
dd7dfef
ea680d5
5b0ed0c
f92678a
a72f63b
87f60e2
87f0f41
849901e
36fee46
6a61e92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,27 +72,61 @@ jobs: | |
uses: coverallsapp/github-action@v1.1.2 | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
flag-name: jni_gen_tests | ||
parallel: true | ||
path-to-lcov: ./jni_gen/coverage/lcov.info | ||
|
||
build_jni_example_linux: | ||
## TODO: More minimal test on windows after fixing dev dependency. | ||
## i.e do not rerun analyze and format steps, and do not require flutter. | ||
|
||
test_jni: | ||
runs-on: ubuntu-latest | ||
defaults: | ||
run: | ||
working-directory: ./jni/example | ||
working-directory: ./jni | ||
steps: | ||
- uses: actions/checkout@v3 | ||
## Requires flutter to analyze example. | ||
## Using dart alone doesn't work. | ||
- uses: subosito/flutter-action@v2 | ||
with: | ||
channel: 'stable' | ||
- uses: actions/setup-java@v2 | ||
with: | ||
distribution: 'zulu' | ||
java-version: '11' | ||
- run: | | ||
sudo apt-get update -y | ||
sudo apt-get install -y ninja-build libgtk-3-dev | ||
- run: flutter config --enable-linux-desktop | ||
- run: | | ||
cd src/ | ||
mkdir build | ||
cd build | ||
cmake .. && cmake --build . | ||
cd ../.. | ||
- run: flutter pub get | ||
- run: flutter build linux | ||
- name: Check formatting | ||
run: flutter format --output=none --set-exit-if-changed . | ||
- name: Run lints | ||
run: flutter analyze --fatal-infos | ||
- name: Get dependencies | ||
run: dart pub get | ||
- name: Run tests | ||
run: dart test | ||
- name: Install coverage | ||
run: dart pub global activate coverage | ||
- name: Collect coverage | ||
run: dart pub global run coverage:test_with_coverage | ||
- name: Upload coverage | ||
uses: coverallsapp/github-action@v1.1.2 | ||
with: | ||
github-token: ${{ secrets.GITHUB_TOKEN }} | ||
flag-name: jni_tests | ||
parallel: true | ||
path-to-lcov: ./jni/coverage/lcov.info | ||
|
||
build_jni_example_windows: | ||
runs-on: windows-latest | ||
build_jni_example_linux: | ||
runs-on: ubuntu-latest | ||
defaults: | ||
run: | ||
working-directory: ./jni/example | ||
|
@@ -101,12 +135,26 @@ jobs: | |
- uses: subosito/flutter-action@v2 | ||
with: | ||
channel: 'stable' | ||
- run: flutter config --enable-windows-desktop | ||
- uses: actions/setup-java@v2 | ||
with: | ||
distribution: 'zulu' | ||
java-version: '11' | ||
- run: | | ||
sudo apt-get update -y | ||
sudo apt-get install -y ninja-build libgtk-3-dev | ||
- run: | | ||
cd ../src/ | ||
mkdir build | ||
cd build | ||
cmake .. && cmake --build . | ||
cd ../../example | ||
- run: flutter config --enable-linux-desktop | ||
- run: flutter pub get | ||
- run: flutter build windows | ||
- run: flutter test | ||
- run: flutter build linux | ||
|
||
build_jni_example_macos: | ||
runs-on: macos-latest | ||
build_jni_example_windows: | ||
runs-on: windows-latest | ||
defaults: | ||
run: | ||
working-directory: ./jni/example | ||
|
@@ -115,10 +163,13 @@ jobs: | |
- uses: subosito/flutter-action@v2 | ||
with: | ||
channel: 'stable' | ||
architecture: x64 | ||
- run: flutter config --enable-macos-desktop | ||
- uses: actions/setup-java@v2 | ||
with: | ||
distribution: 'zulu' | ||
java-version: '11' | ||
- run: flutter config --enable-windows-desktop | ||
- run: flutter pub get | ||
- run: flutter build macos | ||
- run: flutter build windows | ||
|
||
build_jni_example_android: | ||
runs-on: ubuntu-latest | ||
|
@@ -138,3 +189,13 @@ jobs: | |
- run: flutter build apk | ||
- run: flutter build appbundle | ||
|
||
coveralls_finish: | ||
needs: [test_jni_gen, test_jni] | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Coveralls finished | ||
uses: coverallsapp/github-action@master | ||
with: | ||
github-token: ${{ secrets.github_token }} | ||
parallel-finished: true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice! |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,92 +1,38 @@ | ||
# jni | ||
# jni (experimental module) | ||
mahesh-hegde marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
A new Flutter FFI plugin project. | ||
This is a utility library to access JNI from Dart / Flutter code, intended as a supplement for `jnigen` code generator, as well as provide the common base components (such as managing the JVM instance) to the code generated by `jni_gen`. | ||
|
||
## Getting Started | ||
This library contains: | ||
|
||
This project is a starting point for a Flutter | ||
[FFI plugin](https://docs.flutter.dev/development/platform-integration/c-interop), | ||
a specialized package that includes native code directly invoked with Dart FFI. | ||
* functions to access the JNIEnv and JavaVM variables from JNI, and wrapper functions to those provided by JNI. (`Jni.getEnv`, `Jni.getJavaVM`). | ||
|
||
## Project stucture | ||
* Functions to spawn a JVM on desktop platforms (`Jni.spawn`). | ||
|
||
This template uses the following structure: | ||
* Some utility functions to make it easier to work with JNI in Dart; eg: To convert a java string object to Dart string (mostly as extension methods on `Pointer<JniEnv>`). | ||
|
||
* `src`: Contains the native source code, and a CmakeFile.txt file for building | ||
that source code into a dynamic library. | ||
* Some Android-specific helpers (get application context and current activity references). | ||
|
||
* `lib`: Contains the Dart code that defines the API of the plugin, and which | ||
calls into the native code using `dart:ffi`. | ||
* Some helper classes and functions to simplify one-off uses (`JniObject` and `JniClass` intended for calling functions by specifying the name and arguments. It will reduce some boilerplate when you're debugging. Note: this API is slightly incomplete). | ||
|
||
* platform folders (`android`, `ios`, `windows`, etc.): Contains the build files | ||
for building and bundling the native code library with the platform application. | ||
This is intended for one-off / debugging uses of JNI, as well as providing a base library for code generated by jni_gen. | ||
|
||
## Buidling and bundling native code | ||
__To interface a complete java library, look forward for `jni_gen`.__ | ||
|
||
The `pubspec.yaml` specifies FFI plugins as follows: | ||
## Platform support | ||
The focus of this project is Android, since it already has a JVM and JNI allows interop with existing code and Platform APIs. Experimental desktop support exists for Linux using the JVM invocation API. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The focus of this project is Flutter Android, since Flutter Android apps already have a JVM, and JNI enables interop with existing Java code and Android Platform APIs. This project also (partially) supports Linux desktop by spawning a JVM through JNI. |
||
|
||
```yaml | ||
plugin: | ||
platforms: | ||
some_platform: | ||
ffiPlugin: true | ||
``` | ||
## Version note | ||
This library is at an early stage and the public API may rapidly change based on requirements of `jni_gen`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This library is at an early stage of development and we do not provide backwards compatibility of the API at this point. |
||
|
||
This configuration invokes the native build for the various target platforms | ||
and bundles the binaries in Flutter applications using these FFI plugins. | ||
## Documentation | ||
|
||
This can be combined with dartPluginClass, such as when FFI is used for the | ||
implementation of one platform in a federated plugin: | ||
There are well-commented test files under `test/` directory and a flutter based example under `example/`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "There are" and "exists" are usually signs a sentence can be rewritten. (Inverting the sentence usually helps.) The |
||
|
||
```yaml | ||
plugin: | ||
implements: some_other_plugin | ||
platforms: | ||
some_platform: | ||
dartPluginClass: SomeClass | ||
ffiPlugin: true | ||
``` | ||
Using this library assumes some familiarity with JNI - it's threading model and object references, among other things. | ||
|
||
A plugin can have both FFI and method channels: | ||
## jni_gen | ||
|
||
```yaml | ||
plugin: | ||
platforms: | ||
some_platform: | ||
pluginClass: SomeName | ||
ffiPlugin: true | ||
``` | ||
This library is a part of `jni_gen` - a 2022 GSoC project. | ||
|
||
The native build systems that are invoked by FFI (and method channel) plugins are: | ||
|
||
* For Android: Gradle, which invokes the Android NDK for native builds. | ||
* See the documentation in android/build.gradle. | ||
* For iOS and MacOS: Xcode, via CocoaPods. | ||
* See the documentation in ios/jni.podspec. | ||
* See the documentation in macos/jni.podspec. | ||
* For Linux and Windows: CMake. | ||
* See the documentation in linux/CMakeLists.txt. | ||
* See the documentation in windows/CMakeLists.txt. | ||
|
||
## Binding to native code | ||
|
||
To use the native code, bindings in Dart are needed. | ||
To avoid writing these by hand, they are generated from the header file | ||
(`src/jni.h`) by `package:ffigen`. | ||
Regenerate the bindings by running `flutter pub run ffigen --config ffigen.yaml`. | ||
|
||
## Invoking native code | ||
|
||
Very short-running native functions can be directly invoked from any isolate. | ||
For example, see `sum` in `lib/jni.dart`. | ||
|
||
Longer-running functions should be invoked on a helper isolate to avoid | ||
dropping frames in Flutter applications. | ||
For example, see `sumAsync` in `lib/jni.dart`. | ||
|
||
## Flutter help | ||
|
||
For help getting started with Flutter, view our | ||
[online documentation](https://flutter.dev/docs), which offers tutorials, | ||
samples, guidance on mobile development, and a full API reference. | ||
The broader aim of jni_gen is making Java APIs accessible from dart in an idiomatic way. The current focus is on the Android platform. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This was already mentioned earlier in the readme. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package dev.dart.jni; | ||
|
||
import androidx.annotation.Keep; | ||
import androidx.annotation.NonNull; | ||
import android.util.Log; | ||
import android.app.Activity; | ||
import io.flutter.plugin.common.PluginRegistry.Registrar; | ||
import io.flutter.embedding.engine.plugins.FlutterPlugin; | ||
import io.flutter.embedding.engine.plugins.activity.ActivityAware; | ||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; | ||
|
||
import android.content.Context; | ||
|
||
@Keep | ||
public class JniPlugin implements FlutterPlugin, ActivityAware { | ||
|
||
@Override | ||
public void | ||
onAttachedToEngine(@NonNull FlutterPluginBinding binding) { | ||
setup(binding.getApplicationContext()); | ||
} | ||
|
||
public static void registerWith(Registrar registrar) { | ||
JniPlugin plugin = new JniPlugin(); | ||
plugin.setup(registrar.activeContext()); | ||
} | ||
|
||
private void setup(Context context) { | ||
initializeJni(context, getClass().getClassLoader()); | ||
} | ||
|
||
@Override | ||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} | ||
|
||
// Activity handling methods | ||
@Override | ||
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { | ||
Activity activity = binding.getActivity(); | ||
setJniActivity(activity, activity.getApplicationContext()); | ||
} | ||
|
||
@Override | ||
public void onDetachedFromActivityForConfigChanges() {} | ||
|
||
@Override | ||
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { | ||
Activity activity = binding.getActivity(); | ||
setJniActivity(activity, activity.getApplicationContext()); | ||
} | ||
|
||
@Override | ||
public void onDetachedFromActivity() {} | ||
|
||
native void initializeJni(Context context, ClassLoader classLoader); | ||
native void setJniActivity(Activity activity, Context context); | ||
|
||
static { | ||
System.loadLibrary("dartjni"); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package dev.dart.jni_example; | ||
|
||
import android.app.Activity; | ||
import android.os.Handler; | ||
import android.content.Context; | ||
import android.widget.Toast; | ||
import androidx.annotation.Keep; | ||
|
||
@Keep | ||
class AnyToast { | ||
static AnyToast makeText(Activity mainActivity, Context context, CharSequence text, int duration) { | ||
AnyToast toast = new AnyToast(); | ||
toast.mainActivity = mainActivity; | ||
toast.context = context; | ||
toast.text = text; | ||
toast.duration = duration; | ||
return toast; | ||
} | ||
|
||
void show() { | ||
mainActivity.runOnUiThread(() -> Toast.makeText(context, text, duration).show()); | ||
} | ||
|
||
Activity mainActivity; | ||
Context context; | ||
CharSequence text; | ||
int duration; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this kind of logic into
build/setup.dart
that way we can also use it locally instead of only on the CI. (Just invoke the dart script here.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed 18 comments because github folded them.
This install and configure step is specific to linux on CI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this twice in the file (both Linux).