# JNI - Go Bindings for Java Native Interface and Android APIs JNI is an idiomatic Go library that provides comprehensive bindings for the Java Native Interface (JNI), enabling Go programs to interact seamlessly with Java code and the Android runtime. The library wraps the entire JNI C API with type-safe Go types and methods, handling critical concerns like thread safety, reference management, and exception checking automatically. Beyond the core JNI bindings, the library includes auto-generated wrappers for 53 Android Java API packages covering Bluetooth, Wi-Fi, Location, Notifications, Camera, Sensors, and many more system services. The core functionality centers on the `VM` (Java Virtual Machine) and `Env` (JNI Environment) types, which provide thread-safe access to Java methods and objects. The library uses a `VM.Do()` pattern that automatically handles OS thread pinning, JNI environment attachment/detachment, and ensures that all JNI operations occur on properly attached threads. For Android development, high-level wrapper packages like `bluetooth`, `wifi`, `location`, and `notification` provide Manager types that abstract away the complexity of JNI calls, making it easy to access Android system services from pure Go code. ## Core JNI Types ### VM (Java Virtual Machine) The `VM` type wraps a JNI JavaVM pointer. It is process-global and thread-safe. ```go import "github.com/AndroidGoLab/jni" // Create VM from raw pointer (e.g., from Android NDK) vm := jni.VMFromPtr(rawPtr) // Create VM from uintptr (gomobile/Gio convention) vm := jni.VMFromUintptr(uintptr(activity.vm)) // Get raw pointer for interop rawPtr := vm.Ptr() ``` ### VM.Do() - Thread-Safe JNI Execution The primary entry point for all JNI operations. Handles thread pinning and JNI environment management automatically. ```go err := vm.Do(func(env *jni.Env) error { // All JNI operations must happen inside this closure cls, err := env.FindClass("java/lang/String") if err != nil { return err } // Use cls... return nil }) ``` ### Env (JNI Environment) The `Env` type wraps a JNIEnv pointer. Only valid on the OS thread where it was obtained. ```go // Inside vm.Do(): env.FindClass("com/example/MyClass") env.GetMethodID(cls, "myMethod", "(I)V") env.CallVoidMethod(obj, methodID, jni.IntValue(42)) env.NewStringUTF("Hello from Go") env.DeleteLocalRef(localRef) env.NewGlobalRef(localRef) ``` ### Object, Class, String Types ```go // Object - base type for all JNI objects obj := &jni.Object{} ref := obj.Ref() // Get raw jobject reference // Class - represents a Java class cls, err := env.FindClass("java/util/ArrayList") superCls := env.GetSuperclass(cls) // String - Java string wrapper jstr, err := env.NewStringUTF("Hello") goStr := env.GoString(jstr) ``` ### Value Type for Method Arguments ```go // Create JNI values for method calls intVal := jni.IntValue(42) boolVal := jni.BooleanValue(true) objVal := jni.ObjectValue(someObj) longVal := jni.LongValue(int64(12345)) floatVal := jni.FloatValue(3.14) doubleVal := jni.DoubleValue(2.71828) ``` ### GlobalRef - Long-lived Object References ```go // Create global reference (survives beyond local scope) globalRef := env.NewGlobalRef(localObj) // Use global ref across multiple vm.Do() calls // ... // Delete when done (inside vm.Do) env.DeleteGlobalRef(globalRef) ``` ## Low-Level JNI Operations ### Finding Classes and Methods ```go err := vm.Do(func(env *jni.Env) error { // Find a class cls, err := env.FindClass("java/util/HashMap") if err != nil { return err } // Get constructor method ID initMethod, err := env.GetMethodID(cls, "", "()V") if err != nil { return err } // Get instance method ID putMethod, err := env.GetMethodID(cls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") if err != nil { return err } // Get static method ID staticMethod, err := env.GetStaticMethodID(cls, "valueOf", "(I)Ljava/lang/Integer;") if err != nil { return err } return nil }) ``` ### Creating Objects and Calling Methods ```go err := vm.Do(func(env *jni.Env) error { // Find ArrayList class cls, _ := env.FindClass("java/util/ArrayList") initMethod, _ := env.GetMethodID(cls, "", "()V") addMethod, _ := env.GetMethodID(cls, "add", "(Ljava/lang/Object;)Z") sizeMethod, _ := env.GetMethodID(cls, "size", "()I") // Create new ArrayList instance list, err := env.NewObject(cls, initMethod) if err != nil { return err } defer env.DeleteLocalRef(list) // Create a string to add str, _ := env.NewStringUTF("item") defer env.DeleteLocalRef(&str.Object) // Call add method (returns boolean) added, err := env.CallBooleanMethod(list, addMethod, jni.ObjectValue(&str.Object)) if err != nil { return err } fmt.Printf("Added: %v\n", added != 0) // Call size method (returns int) size, err := env.CallIntMethod(list, sizeMethod) if err != nil { return err } fmt.Printf("Size: %d\n", size) return nil }) ``` ### Working with Arrays ```go err := vm.Do(func(env *jni.Env) error { // Create byte array byteArray := env.NewByteArray(100) length := env.GetArrayLength(&byteArray.Array) // Create int array intArray := env.NewIntArray(50) // Create object array stringCls, _ := env.FindClass("java/lang/String") strArray, _ := env.NewObjectArray(10, stringCls, nil) // Set array element str, _ := env.NewStringUTF("element") env.SetObjectArrayElement(strArray, 0, &str.Object) // Get array element elem, _ := env.GetObjectArrayElement(strArray, 0) return nil }) ``` ### Exception Handling ```go err := vm.Do(func(env *jni.Env) error { // Check if exception occurred if env.ExceptionCheck() { env.ExceptionDescribe() // Print to stderr env.ExceptionClear() // Clear the exception return fmt.Errorf("Java exception occurred") } // Get exception object throwable := env.ExceptionOccurred() if throwable != nil { env.ExceptionClear() // Handle throwable... } return nil }) ``` ## Android API Wrappers ### Bluetooth Adapter ```go import ( "github.com/AndroidGoLab/jni/bluetooth" "github.com/AndroidGoLab/jni/app" ) func useBluetooth(ctx *app.Context) error { // Create Bluetooth adapter adapter, err := bluetooth.NewAdapter(ctx) if err != nil { return fmt.Errorf("bluetooth.NewAdapter: %w", err) } defer adapter.Close() // Check if Bluetooth is enabled enabled, err := adapter.IsEnabled() if err != nil { return err } fmt.Printf("Bluetooth enabled: %v\n", enabled) // Get adapter name and address name, _ := adapter.GetName() addr, _ := adapter.GetAddress() fmt.Printf("Name: %s, Address: %s\n", name, addr) // Get bonded (paired) devices bondedDevices, _ := adapter.GetBondedDevices() // Access Bluetooth constants fmt.Printf("Device types: Classic=%d, LE=%d, Dual=%d\n", bluetooth.DeviceTypeClassic, bluetooth.DeviceTypeLe, bluetooth.DeviceTypeDual) return nil } ``` ### Wi-Fi Manager ```go import "github.com/AndroidGoLab/jni/net/wifi" func useWifi(ctx *app.Context) error { // Create Wi-Fi manager mgr, err := wifi.NewManager(ctx) if err != nil { return fmt.Errorf("wifi.NewManager: %w", err) } defer mgr.Close() // Check Wi-Fi status enabled, err := mgr.IsWifiEnabled() if err != nil { return err } fmt.Printf("Wi-Fi enabled: %v\n", enabled) // Check 5GHz support has5GHz, _ := mgr.Is5GHzBandSupported() fmt.Printf("5GHz supported: %v\n", has5GHz) return nil } ``` ### Location Manager ```go import "github.com/AndroidGoLab/jni/location" func useLocation(ctx *app.Context) error { // Create location manager mgr, err := location.NewManager(ctx) if err != nil { return fmt.Errorf("location.NewManager: %w", err) } defer mgr.Close() // Check if GPS is enabled gpsEnabled, _ := mgr.IsProviderEnabled("gps") fmt.Printf("GPS enabled: %v\n", gpsEnabled) // Get all providers providers, _ := mgr.GetAllProviders() return nil } ``` ### Notification Manager ```go import "github.com/AndroidGoLab/jni/app/notification" func useNotifications(ctx *app.Context) error { // Create notification manager mgr, err := notification.NewManager(ctx) if err != nil { return fmt.Errorf("notification.NewManager: %w", err) } defer mgr.Close() // Check if notifications are enabled enabled, _ := mgr.AreNotificationsEnabled() fmt.Printf("Notifications enabled: %v\n", enabled) // Check bubble support bubblesAllowed, _ := mgr.AreBubblesAllowed() fmt.Printf("Bubbles allowed: %v\n", bubblesAllowed) // Get notification importance importance, _ := mgr.GetImportance() fmt.Printf("Importance: %d\n", importance) // Cancel a notification mgr.Cancel1(notificationId) // Cancel all notifications mgr.CancelAll() return nil } ``` ### Android Context ```go import "github.com/AndroidGoLab/jni/app" func useContext(vm *jni.VM, activityObj *jni.Object) error { // Create context wrapper ctx := &app.Context{ VM: vm, Obj: activityObj, } // Get system service svc, err := ctx.GetSystemService("notification") if err != nil { return err } // Get package name pkgName, _ := ctx.GetPackageName() fmt.Printf("Package: %s\n", pkgName) // Get application info appInfo, _ := ctx.GetApplicationInfo() // Check permissions result, _ := ctx.CheckSelfPermission("android.permission.CAMERA") // Start an activity ctx.StartActivity1(intent) // Start a service ctx.StartService(serviceIntent) // Get files directory filesDir, _ := ctx.GetFilesDir() // Get cache directory cacheDir, _ := ctx.GetCacheDir() return nil } ``` ## Complete Android Application Example ```go //go:build android package main /* #include */ import "C" import ( "fmt" "unsafe" "github.com/AndroidGoLab/jni" "github.com/AndroidGoLab/jni/app" "github.com/AndroidGoLab/jni/bluetooth" "github.com/AndroidGoLab/jni/net/wifi" ) //export ANativeActivity_onCreate func ANativeActivity_onCreate(activity *C.ANativeActivity, savedState unsafe.Pointer, savedStateSize C.size_t) { // Get VM and activity object from NDK vm := jni.VMFromUintptr(uintptr(activity.vm)) activityObj := jni.ObjectFromUintptr(uintptr(activity.clazz)) // Run main logic go func() { if err := runApp(vm, activityObj); err != nil { fmt.Printf("Error: %v\n", err) } }() } func runApp(vm *jni.VM, activityObj *jni.Object) error { // Create global reference to activity (survives across vm.Do calls) var globalActivity *jni.GlobalRef err := vm.Do(func(env *jni.Env) error { globalActivity = env.NewGlobalRef(activityObj) return nil }) if err != nil { return err } // Create context ctx := &app.Context{ VM: vm, Obj: globalActivity, } // Use Bluetooth adapter, err := bluetooth.NewAdapter(ctx) if err == nil { defer adapter.Close() enabled, _ := adapter.IsEnabled() fmt.Printf("Bluetooth: %v\n", enabled) } // Use Wi-Fi wifiMgr, err := wifi.NewManager(ctx) if err == nil { defer wifiMgr.Close() wifiEnabled, _ := wifiMgr.IsWifiEnabled() fmt.Printf("Wi-Fi: %v\n", wifiEnabled) } return nil } func main() {} ``` ## String Conversion Utilities ```go // Go string to Java String err := vm.Do(func(env *jni.Env) error { jstr, err := env.NewStringUTF("Hello from Go") if err != nil { return err } defer env.DeleteLocalRef(&jstr.Object) // Pass jstr to Java methods... return nil }) // Java String to Go string err := vm.Do(func(env *jni.Env) error { // Assuming javaStr is a *jni.String from a method call goStr := env.GoString(javaStr) fmt.Println(goStr) return nil }) ``` ## Reference Management Best Practices ```go err := vm.Do(func(env *jni.Env) error { // Local references are automatically freed when vm.Do returns, // but explicitly delete them for long-running operations for i := 0; i < 1000; i++ { str, _ := env.NewStringUTF(fmt.Sprintf("item-%d", i)) // Process string... env.DeleteLocalRef(&str.Object) // Prevent local ref table overflow } // For objects that need to survive beyond vm.Do, use global refs cls, _ := env.FindClass("java/lang/String") globalCls := env.NewGlobalRef(&cls.Object) // globalCls can be used in future vm.Do calls // Remember to delete it when done: env.DeleteGlobalRef(globalCls) return nil }) ``` ## Thread Safety and Nesting ```go // vm.Do() calls can be safely nested err := vm.Do(func(env *jni.Env) error { // Outer operation cls, _ := env.FindClass("java/lang/System") // Nested vm.Do - safe, reuses existing thread attachment return vm.Do(func(innerEnv *jni.Env) error { // Inner operation on same thread method, _ := innerEnv.GetStaticMethodID(cls, "currentTimeMillis", "()J") time, _ := innerEnv.CallStaticLongMethod(cls, method) fmt.Printf("Time: %d\n", time) return nil }) }) ``` ## Summary The JNI library provides a complete solution for Go programs that need to interact with Java code, particularly for Android application development. By abstracting away the complexities of JNI thread management, reference counting, and exception handling, it allows developers to focus on application logic rather than low-level JNI mechanics. The `VM.Do()` pattern ensures thread safety, while the high-level Android API wrappers (Bluetooth, Wi-Fi, Location, Notifications, etc.) provide clean, idiomatic Go interfaces to Android system services. For typical Android development workflows, developers will primarily use the pre-built Manager types from packages like `bluetooth`, `wifi`, `location`, and `notification`, which handle all JNI complexity internally. These wrappers follow a consistent pattern: create with `NewManager(ctx)`, use the various methods, and clean up with `Close()`. For advanced use cases or APIs not yet wrapped, the low-level `Env` methods provide full access to all JNI functionality, enabling calls to any Java method or access to any Java object.