mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-15 15:15:51 +01:00
This commit adds comprehensive Android support for Wails v3, enabling Go applications to run as native Android apps with WebView-based UI. Key features: - Android-specific application implementation with JNI bridge - WebView integration via WebViewAssetLoader for serving assets - JavaScript runtime injection and execution via JNI callbacks - Binding call support with async result callbacks - Event system support for Android platform - Full example Android app with Gradle build system Technical details: - Uses CGO with Android NDK for cross-compilation - Implements JNI callbacks for Go <-> Java communication - Supports both ARM64 and x86_64 architectures - WebView debugging support via Chrome DevTools Protocol - Handles empty response body case in binding calls to prevent panic Files added: - v3/pkg/application/*_android.go - Android platform implementations - v3/pkg/events/events_android.go - Android event definitions - v3/internal/*/\*_android.go - Android-specific internal packages - v3/examples/android/ - Complete example Android application - v3/ANDROID_ARCHITECTURE.md - Architecture documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1025 lines
32 KiB
Markdown
1025 lines
32 KiB
Markdown
# Wails v3 Android Architecture
|
|
|
|
## Executive Summary
|
|
|
|
This document provides a comprehensive technical architecture for Android support in Wails v3. The implementation enables Go applications to run natively on Android with an Android WebView frontend, maintaining the Wails philosophy of using web technologies for UI while leveraging Go for business logic.
|
|
|
|
Unlike iOS which uses CGO with Objective-C, Android uses JNI (Java Native Interface) to bridge between Java/Kotlin and Go. The Go code is compiled as a shared library (`.so`) that is loaded by the Android application at runtime.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture Overview](#architecture-overview)
|
|
2. [Core Components](#core-components)
|
|
3. [Layer Architecture](#layer-architecture)
|
|
4. [File Structure](#file-structure)
|
|
5. [Implementation Details](#implementation-details)
|
|
6. [Build System](#build-system)
|
|
7. [JNI Bridge Details](#jni-bridge-details)
|
|
8. [Asset Serving](#asset-serving)
|
|
9. [JavaScript Bridge](#javascript-bridge)
|
|
10. [Security Considerations](#security-considerations)
|
|
11. [Configuration Options](#configuration-options)
|
|
12. [Debugging](#debugging)
|
|
13. [API Reference](#api-reference)
|
|
14. [Troubleshooting](#troubleshooting)
|
|
15. [Future Enhancements](#future-enhancements)
|
|
|
|
## Architecture Overview
|
|
|
|
### Design Principles
|
|
|
|
1. **Battery Efficiency First**: All architectural decisions prioritize battery life
|
|
2. **No Network Ports**: Asset serving happens in-process via `WebViewAssetLoader`
|
|
3. **JNI Bridge Pattern**: Java Activity hosts WebView, Go provides business logic
|
|
4. **Wails v3 Compatibility**: Maintain API compatibility with existing Wails v3 applications
|
|
5. **Follow Fyne's gomobile pattern**: Use `-buildmode=c-shared` for native library
|
|
|
|
### High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Android Application │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Java/Android Layer │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ MainActivity (Activity) │ │
|
|
│ │ ┌───────────────────────────────────────────────┐ │ │
|
|
│ │ │ Android WebView │ │ │
|
|
│ │ │ ┌─────────────────────────────────────────┐ │ │ │
|
|
│ │ │ │ Web Application (HTML/JS) │ │ │ │
|
|
│ │ │ └─────────────────────────────────────────┘ │ │ │
|
|
│ │ └───────────────────────────────────────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ WailsBridge WailsPathHandler WailsJSBridge│ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ JNI Bridge Layer │
|
|
│ System.loadLibrary("wails") │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Go Runtime (libwails.so) │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Wails Application │ │
|
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
|
|
│ │ │App Logic │ │Services │ │Asset Server │ │ │
|
|
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Comparison with iOS Architecture
|
|
|
|
| Aspect | iOS | Android |
|
|
|--------|-----|---------|
|
|
| Native Language | Objective-C | Java |
|
|
| Bridge Technology | CGO (C headers) | JNI |
|
|
| Build Mode | `-buildmode=c-archive` (.a) | `-buildmode=c-shared` (.so) |
|
|
| Entry Point | `main.m` calls `WailsIOSMain()` | `MainActivity` loads `libwails.so` |
|
|
| WebView | WKWebView | Android WebView |
|
|
| URL Scheme | `wails://localhost` | `https://wails.localhost` |
|
|
| Asset Interception | `WKURLSchemeHandler` | `WebViewAssetLoader` + `PathHandler` |
|
|
| JS → Native | `WKScriptMessageHandler` | `@JavascriptInterface` |
|
|
| Native → JS | `evaluateJavaScript:` | `evaluateJavascript()` |
|
|
| App Lifecycle | `UIApplicationDelegate` | `Activity` lifecycle methods |
|
|
|
|
## Core Components
|
|
|
|
### 1. Java Components
|
|
|
|
#### MainActivity (`MainActivity.java`)
|
|
|
|
**Purpose**: Android Activity that hosts the WebView and manages app lifecycle.
|
|
|
|
**Location**: `build/android/app/src/main/java/com/wails/app/MainActivity.java`
|
|
|
|
**Key Responsibilities**:
|
|
- Initialize the native Go library via `WailsBridge`
|
|
- Configure and manage the Android WebView
|
|
- Set up asset loading via `WebViewAssetLoader`
|
|
- Handle Android lifecycle events (onCreate, onResume, onPause, onDestroy)
|
|
- Execute JavaScript in the WebView when requested by Go
|
|
|
|
**Key Methods**:
|
|
```java
|
|
onCreate(Bundle) // Initialize bridge, setup WebView
|
|
setupWebView() // Configure WebView settings and handlers
|
|
loadApplication() // Load initial URL (https://wails.localhost/)
|
|
executeJavaScript(String) // Run JS code (called from Go via JNI)
|
|
onResume() / onPause() // Lifecycle events forwarded to Go
|
|
onDestroy() // Cleanup resources
|
|
onBackPressed() // Handle back navigation
|
|
```
|
|
|
|
#### WailsBridge (`WailsBridge.java`)
|
|
|
|
**Purpose**: Manages the JNI connection between Java and Go.
|
|
|
|
**Location**: `build/android/app/src/main/java/com/wails/app/WailsBridge.java`
|
|
|
|
**Key Responsibilities**:
|
|
- Load the native library (`System.loadLibrary("wails")`)
|
|
- Declare and call native methods
|
|
- Manage callbacks for async operations
|
|
- Forward lifecycle events to Go
|
|
|
|
**Native Method Declarations**:
|
|
```java
|
|
private static native void nativeInit(WailsBridge bridge);
|
|
private static native void nativeShutdown();
|
|
private static native void nativeOnResume();
|
|
private static native void nativeOnPause();
|
|
private static native byte[] nativeServeAsset(String path, String method, String headers);
|
|
private static native String nativeHandleMessage(String message);
|
|
private static native String nativeGetAssetMimeType(String path);
|
|
```
|
|
|
|
**Key Methods**:
|
|
```java
|
|
initialize() // Call nativeInit, set up Go runtime
|
|
shutdown() // Call nativeShutdown, cleanup
|
|
serveAsset(path, method, headers) // Get asset data from Go
|
|
handleMessage(message) // Send message to Go, get response
|
|
getAssetMimeType(path) // Get MIME type for asset
|
|
executeJavaScript(js) // Execute JS (callable from Go)
|
|
emitEvent(name, data) // Emit event to frontend
|
|
```
|
|
|
|
#### WailsPathHandler (`WailsPathHandler.java`)
|
|
|
|
**Purpose**: Implements `WebViewAssetLoader.PathHandler` to serve assets from Go.
|
|
|
|
**Location**: `build/android/app/src/main/java/com/wails/app/WailsPathHandler.java`
|
|
|
|
**Key Responsibilities**:
|
|
- Intercept all requests to `https://wails.localhost/*`
|
|
- Forward requests to Go's asset server via `WailsBridge`
|
|
- Return `WebResourceResponse` with asset data
|
|
|
|
**Key Method**:
|
|
```java
|
|
@Nullable
|
|
public WebResourceResponse handle(@NonNull String path) {
|
|
// Normalize path (/ -> /index.html)
|
|
// Call bridge.serveAsset(path, "GET", "{}")
|
|
// Get MIME type via bridge.getAssetMimeType(path)
|
|
// Return WebResourceResponse with data
|
|
}
|
|
```
|
|
|
|
#### WailsJSBridge (`WailsJSBridge.java`)
|
|
|
|
**Purpose**: JavaScript interface exposed to the WebView for Go communication.
|
|
|
|
**Location**: `build/android/app/src/main/java/com/wails/app/WailsJSBridge.java`
|
|
|
|
**Key Responsibilities**:
|
|
- Expose methods to JavaScript via `@JavascriptInterface`
|
|
- Forward messages from JavaScript to Go
|
|
- Support both sync and async message patterns
|
|
|
|
**JavaScript Interface Methods**:
|
|
```java
|
|
@JavascriptInterface
|
|
public String invoke(String message) // Sync call to Go
|
|
|
|
@JavascriptInterface
|
|
public void invokeAsync(String callbackId, String message) // Async call
|
|
|
|
@JavascriptInterface
|
|
public void log(String level, String message) // Log to Android logcat
|
|
|
|
@JavascriptInterface
|
|
public String platform() // Returns "android"
|
|
|
|
@JavascriptInterface
|
|
public boolean isDebug() // Returns BuildConfig.DEBUG
|
|
```
|
|
|
|
**Usage from JavaScript**:
|
|
```javascript
|
|
// Synchronous call
|
|
const result = wails.invoke(JSON.stringify({type: 'call', ...}));
|
|
|
|
// Asynchronous call
|
|
wails.invokeAsync('callback-123', JSON.stringify({type: 'call', ...}));
|
|
|
|
// Logging
|
|
wails.log('info', 'Hello from JavaScript');
|
|
|
|
// Platform detection
|
|
if (wails.platform() === 'android') { ... }
|
|
```
|
|
|
|
### 2. Go Components
|
|
|
|
#### Application Layer (`application_android.go`)
|
|
|
|
**Purpose**: Main Go implementation for Android platform.
|
|
|
|
**Location**: `v3/pkg/application/application_android.go`
|
|
|
|
**Build Tag**: `//go:build android`
|
|
|
|
**Key Responsibilities**:
|
|
- Export JNI functions for Java to call
|
|
- Manage global application state
|
|
- Handle lifecycle events from Android
|
|
- Serve assets and process messages
|
|
|
|
**JNI Exports**:
|
|
```go
|
|
//export Java_com_wails_app_WailsBridge_nativeInit
|
|
func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject)
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeShutdown
|
|
func Java_com_wails_app_WailsBridge_nativeShutdown(env *C.JNIEnv, obj C.jobject)
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeOnResume
|
|
func Java_com_wails_app_WailsBridge_nativeOnResume(env *C.JNIEnv, obj C.jobject)
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeOnPause
|
|
func Java_com_wails_app_WailsBridge_nativeOnPause(env *C.JNIEnv, obj C.jobject)
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeServeAsset
|
|
func Java_com_wails_app_WailsBridge_nativeServeAsset(env *C.JNIEnv, obj C.jobject, path, method, headers *C.char) *C.char
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeHandleMessage
|
|
func Java_com_wails_app_WailsBridge_nativeHandleMessage(env *C.JNIEnv, obj C.jobject, message *C.char) *C.char
|
|
|
|
//export Java_com_wails_app_WailsBridge_nativeGetAssetMimeType
|
|
func Java_com_wails_app_WailsBridge_nativeGetAssetMimeType(env *C.JNIEnv, obj C.jobject, path *C.char) *C.char
|
|
```
|
|
|
|
**Platform Functions**:
|
|
```go
|
|
func (a *App) platformRun() // Block forever, Android manages lifecycle
|
|
func (a *App) platformQuit() // Signal quit
|
|
func (a *App) isDarkMode() bool // Query Android dark mode
|
|
```
|
|
|
|
#### WebView Window (`webview_window_android.go`)
|
|
|
|
**Purpose**: Implements `webviewWindowImpl` interface for Android.
|
|
|
|
**Location**: `v3/pkg/application/webview_window_android.go`
|
|
|
|
**Build Tag**: `//go:build android`
|
|
|
|
**Key Methods**: Most methods are no-ops or return defaults since Android has a single fullscreen window.
|
|
|
|
```go
|
|
func (w *androidWebviewWindow) execJS(js string) // Execute JavaScript
|
|
func (w *androidWebviewWindow) isFullscreen() bool // Always true
|
|
func (w *androidWebviewWindow) size() (int, int) // Device dimensions
|
|
func (w *androidWebviewWindow) setBackgroundColour(col RGBA) // Set WebView bg
|
|
```
|
|
|
|
#### Asset Server (`assetserver_android.go`)
|
|
|
|
**Purpose**: Configure base URL for Android asset serving.
|
|
|
|
**Location**: `v3/internal/assetserver/assetserver_android.go`
|
|
|
|
**Build Tag**: `//go:build android`
|
|
|
|
```go
|
|
var baseURL = url.URL{
|
|
Scheme: "https",
|
|
Host: "wails.localhost",
|
|
}
|
|
```
|
|
|
|
#### Other Platform Files
|
|
|
|
All these files have the `//go:build android` tag:
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `init_android.go` | Initialization (no `runtime.LockOSThread`) |
|
|
| `clipboard_android.go` | Clipboard operations (stub) |
|
|
| `dialogs_android.go` | File/message dialogs (stub) |
|
|
| `menu_android.go` | Menu handling (no-op) |
|
|
| `menuitem_android.go` | Menu items (no-op) |
|
|
| `screen_android.go` | Screen information |
|
|
| `mainthread_android.go` | Main thread dispatch |
|
|
| `signal_handler_android.go` | Signal handling (no-op) |
|
|
| `single_instance_android.go` | Single instance (via manifest) |
|
|
| `systemtray_android.go` | System tray (no-op) |
|
|
| `keys_android.go` | Keyboard handling (stub) |
|
|
| `events_common_android.go` | Event mapping |
|
|
| `messageprocessor_android.go` | Android-specific runtime methods |
|
|
|
|
## File Structure
|
|
|
|
```
|
|
v3/
|
|
├── ANDROID_ARCHITECTURE.md # This document
|
|
├── pkg/
|
|
│ ├── application/
|
|
│ │ ├── application_android.go # Main Android implementation
|
|
│ │ ├── application_options.go # Contains AndroidOptions struct
|
|
│ │ ├── webview_window_android.go
|
|
│ │ ├── clipboard_android.go
|
|
│ │ ├── dialogs_android.go
|
|
│ │ ├── events_common_android.go
|
|
│ │ ├── init_android.go
|
|
│ │ ├── keys_android.go
|
|
│ │ ├── mainthread_android.go
|
|
│ │ ├── menu_android.go
|
|
│ │ ├── menuitem_android.go
|
|
│ │ ├── messageprocessor_android.go
|
|
│ │ ├── messageprocessor_mobile_stub.go # Stub for non-mobile
|
|
│ │ ├── screen_android.go
|
|
│ │ ├── signal_handler_android.go
|
|
│ │ ├── signal_handler_types_android.go
|
|
│ │ ├── single_instance_android.go
|
|
│ │ └── systemtray_android.go
|
|
│ └── events/
|
|
│ └── events_android.go
|
|
├── internal/
|
|
│ └── assetserver/
|
|
│ ├── assetserver_android.go
|
|
│ └── webview/
|
|
│ └── request_android.go
|
|
└── examples/
|
|
└── android/
|
|
├── main.go # Application entry point
|
|
├── greetservice.go # Example service
|
|
├── go.mod
|
|
├── go.sum
|
|
├── Taskfile.yml # Build orchestration
|
|
├── .gitignore
|
|
├── frontend/ # Web frontend (same as other platforms)
|
|
│ ├── index.html
|
|
│ ├── main.js
|
|
│ ├── package.json
|
|
│ └── ...
|
|
└── build/
|
|
├── config.yml # Build configuration
|
|
├── Taskfile.yml # Common build tasks
|
|
├── android/
|
|
│ ├── Taskfile.yml # Android-specific tasks
|
|
│ ├── build.gradle # Root Gradle build
|
|
│ ├── settings.gradle
|
|
│ ├── gradle.properties
|
|
│ ├── gradlew # Gradle wrapper script
|
|
│ ├── gradle/
|
|
│ │ └── wrapper/
|
|
│ │ └── gradle-wrapper.properties
|
|
│ ├── scripts/
|
|
│ │ └── deps/
|
|
│ │ └── install_deps.go # Dependency checker
|
|
│ └── app/
|
|
│ ├── build.gradle # App Gradle build
|
|
│ ├── proguard-rules.pro
|
|
│ └── src/
|
|
│ └── main/
|
|
│ ├── AndroidManifest.xml
|
|
│ ├── java/
|
|
│ │ └── com/
|
|
│ │ └── wails/
|
|
│ │ └── app/
|
|
│ │ ├── MainActivity.java
|
|
│ │ ├── WailsBridge.java
|
|
│ │ ├── WailsPathHandler.java
|
|
│ │ └── WailsJSBridge.java
|
|
│ ├── res/
|
|
│ │ ├── layout/
|
|
│ │ │ └── activity_main.xml
|
|
│ │ ├── values/
|
|
│ │ │ ├── strings.xml
|
|
│ │ │ ├── colors.xml
|
|
│ │ │ └── themes.xml
|
|
│ │ └── mipmap-*/ # App icons
|
|
│ ├── assets/ # Frontend assets (copied)
|
|
│ └── jniLibs/
|
|
│ ├── arm64-v8a/
|
|
│ │ └── libwails.so # Generated
|
|
│ └── x86_64/
|
|
│ └── libwails.so # Generated
|
|
├── darwin/ # macOS build files
|
|
├── linux/ # Linux build files
|
|
└── windows/ # Windows build files
|
|
```
|
|
|
|
## Implementation Details
|
|
|
|
### Application Startup Flow
|
|
|
|
```
|
|
1. Android OS launches MainActivity
|
|
│
|
|
2. MainActivity.onCreate()
|
|
│
|
|
├─> WailsBridge.initialize()
|
|
│ │
|
|
│ └─> System.loadLibrary("wails")
|
|
│ │
|
|
│ └─> Go runtime starts
|
|
│ │
|
|
│ └─> nativeInit() called
|
|
│ │
|
|
│ └─> globalApp = app (store reference)
|
|
│
|
|
├─> setupWebView()
|
|
│ │
|
|
│ ├─> Configure WebSettings
|
|
│ ├─> Create WebViewAssetLoader with WailsPathHandler
|
|
│ ├─> Set WebViewClient for request interception
|
|
│ └─> Add WailsJSBridge via addJavascriptInterface
|
|
│
|
|
└─> loadApplication()
|
|
│
|
|
└─> webView.loadUrl("https://wails.localhost/")
|
|
│
|
|
└─> WailsPathHandler.handle("/")
|
|
│
|
|
└─> WailsBridge.serveAsset("/index.html", ...)
|
|
│
|
|
└─> nativeServeAsset() (JNI to Go)
|
|
│
|
|
└─> Go AssetServer returns HTML
|
|
```
|
|
|
|
### Asset Request Flow
|
|
|
|
```
|
|
WebView requests: https://wails.localhost/main.js
|
|
│
|
|
▼
|
|
WebViewClient.shouldInterceptRequest()
|
|
│
|
|
▼
|
|
WebViewAssetLoader.shouldInterceptRequest()
|
|
│
|
|
▼
|
|
WailsPathHandler.handle("/main.js")
|
|
│
|
|
▼
|
|
WailsBridge.serveAsset("/main.js", "GET", "{}")
|
|
│
|
|
▼
|
|
JNI call: nativeServeAsset(path, method, headers)
|
|
│
|
|
▼
|
|
Go: serveAssetForAndroid(app, "/main.js")
|
|
│
|
|
▼
|
|
Go: AssetServer reads from embed.FS
|
|
│
|
|
▼
|
|
Return: byte[] data
|
|
│
|
|
▼
|
|
WailsPathHandler creates WebResourceResponse
|
|
│
|
|
▼
|
|
WebView renders content
|
|
```
|
|
|
|
### JavaScript to Go Message Flow
|
|
|
|
```
|
|
JavaScript: wails.invoke('{"type":"call","method":"Greet","args":["World"]}')
|
|
│
|
|
▼
|
|
WailsJSBridge.invoke(message) [@JavascriptInterface]
|
|
│
|
|
▼
|
|
WailsBridge.handleMessage(message)
|
|
│
|
|
▼
|
|
JNI call: nativeHandleMessage(message)
|
|
│
|
|
▼
|
|
Go: handleMessageForAndroid(app, message)
|
|
│
|
|
▼
|
|
Go: Parse JSON, route to service method
|
|
│
|
|
▼
|
|
Go: Execute GreetService.Greet("World")
|
|
│
|
|
▼
|
|
Return: '{"result":"Hello, World!"}'
|
|
│
|
|
▼
|
|
JavaScript receives result
|
|
```
|
|
|
|
### Go to JavaScript Event Flow
|
|
|
|
```
|
|
Go: app.Event.Emit("time", "Mon, 01 Jan 2024 12:00:00")
|
|
│
|
|
▼
|
|
Go: Call Java executeJavaScript via JNI callback
|
|
│
|
|
▼
|
|
WailsBridge.emitEvent("time", "\"Mon, 01 Jan 2024 12:00:00\"")
|
|
│
|
|
▼
|
|
JavaScript: window.wails._emit('time', "Mon, 01 Jan 2024 12:00:00")
|
|
│
|
|
▼
|
|
Frontend event listeners notified
|
|
```
|
|
|
|
## Build System
|
|
|
|
### Prerequisites
|
|
|
|
1. **Go 1.21+** with CGO support
|
|
2. **Android SDK** with:
|
|
- Platform Tools (adb)
|
|
- Build Tools
|
|
- Android Emulator
|
|
3. **Android NDK r19c+** (r26d recommended)
|
|
4. **Java JDK 11+**
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# Required
|
|
export ANDROID_HOME=$HOME/Library/Android/sdk # macOS
|
|
export ANDROID_HOME=$HOME/Android/Sdk # Linux
|
|
|
|
# Optional (auto-detected if not set)
|
|
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125
|
|
|
|
# Path additions
|
|
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
|
export PATH=$PATH:$ANDROID_HOME/emulator
|
|
```
|
|
|
|
### Taskfile Commands
|
|
|
|
```bash
|
|
# Check/install dependencies
|
|
task android:install:deps
|
|
|
|
# Build Go shared library (default: arm64 for device)
|
|
task android:build
|
|
|
|
# Build for emulator (x86_64)
|
|
task android:build ARCH=x86_64
|
|
|
|
# Build for all architectures (fat APK)
|
|
task android:compile:go:all-archs
|
|
|
|
# Package into APK
|
|
task android:package
|
|
|
|
# Run on emulator
|
|
task android:run
|
|
|
|
# View logs
|
|
task android:logs
|
|
|
|
# Clean build artifacts
|
|
task android:clean
|
|
```
|
|
|
|
### Build Process Details
|
|
|
|
#### 1. Go Compilation
|
|
|
|
```bash
|
|
# Environment for arm64 (device)
|
|
export GOOS=android
|
|
export GOARCH=arm64
|
|
export CGO_ENABLED=1
|
|
export CC=$NDK/toolchains/llvm/prebuilt/$HOST/bin/aarch64-linux-android21-clang
|
|
|
|
# Build command
|
|
go build -buildmode=c-shared \
|
|
-tags android \
|
|
-o build/android/app/src/main/jniLibs/arm64-v8a/libwails.so
|
|
```
|
|
|
|
#### 2. Gradle Build
|
|
|
|
```bash
|
|
cd build/android
|
|
./gradlew assembleDebug
|
|
# Output: app/build/outputs/apk/debug/app-debug.apk
|
|
```
|
|
|
|
#### 3. Installation
|
|
|
|
```bash
|
|
adb install app-debug.apk
|
|
adb shell am start -n com.wails.app/.MainActivity
|
|
```
|
|
|
|
### Architecture Support
|
|
|
|
| Architecture | GOARCH | JNI Directory | Use Case |
|
|
|--------------|--------|---------------|----------|
|
|
| arm64-v8a | arm64 | `jniLibs/arm64-v8a/` | Physical devices (most common) |
|
|
| x86_64 | amd64 | `jniLibs/x86_64/` | Emulator |
|
|
| armeabi-v7a | arm | `jniLibs/armeabi-v7a/` | Older devices (optional) |
|
|
| x86 | 386 | `jniLibs/x86/` | Older emulators (optional) |
|
|
|
|
### Minimum SDK Configuration
|
|
|
|
```gradle
|
|
// build/android/app/build.gradle
|
|
android {
|
|
defaultConfig {
|
|
minSdk 21 // Android 5.0 (Lollipop) - 99%+ coverage
|
|
targetSdk 34 // Android 14 - Required for Play Store
|
|
}
|
|
}
|
|
```
|
|
|
|
## JNI Bridge Details
|
|
|
|
### JNI Function Naming Convention
|
|
|
|
JNI functions must follow this naming pattern:
|
|
```
|
|
Java_<package>_<class>_<method>
|
|
```
|
|
|
|
Example:
|
|
```go
|
|
//export Java_com_wails_app_WailsBridge_nativeInit
|
|
func Java_com_wails_app_WailsBridge_nativeInit(env *C.JNIEnv, obj C.jobject, bridge C.jobject)
|
|
```
|
|
|
|
Corresponds to Java:
|
|
```java
|
|
package com.wails.app;
|
|
class WailsBridge {
|
|
private static native void nativeInit(WailsBridge bridge);
|
|
}
|
|
```
|
|
|
|
### JNI Type Mappings
|
|
|
|
| Java Type | JNI Type | Go CGO Type |
|
|
|-----------|----------|-------------|
|
|
| void | void | - |
|
|
| boolean | jboolean | C.jboolean |
|
|
| int | jint | C.jint |
|
|
| long | jlong | C.jlong |
|
|
| String | jstring | *C.char (via conversion) |
|
|
| byte[] | jbyteArray | *C.char (via conversion) |
|
|
| Object | jobject | C.jobject |
|
|
|
|
### String Conversion
|
|
|
|
```go
|
|
// Java String → Go string
|
|
goString := C.GoString((*C.char)(unsafe.Pointer(javaString)))
|
|
|
|
// Go string → Java String (return)
|
|
return C.CString(goString) // Must be freed by Java
|
|
```
|
|
|
|
### Thread Safety
|
|
|
|
- JNI calls must be made from the thread that owns the JNI environment
|
|
- Go goroutines cannot directly call JNI methods
|
|
- Use channels or callbacks to communicate between goroutines and JNI thread
|
|
|
|
## Asset Serving
|
|
|
|
### WebViewAssetLoader Configuration
|
|
|
|
```java
|
|
assetLoader = new WebViewAssetLoader.Builder()
|
|
.setDomain("wails.localhost") // Custom domain
|
|
.addPathHandler("/", new WailsPathHandler(bridge)) // All paths
|
|
.build();
|
|
```
|
|
|
|
### URL Scheme
|
|
|
|
- **Base URL**: `https://wails.localhost/`
|
|
- **Why HTTPS**: Android's `WebViewAssetLoader` requires HTTPS for security
|
|
- **Domain**: `wails.localhost` is arbitrary but consistent with Wails conventions
|
|
|
|
### Path Normalization
|
|
|
|
```java
|
|
// In WailsPathHandler.handle()
|
|
if (path.isEmpty() || path.equals("/")) {
|
|
path = "/index.html";
|
|
}
|
|
```
|
|
|
|
### MIME Type Detection
|
|
|
|
MIME types are determined by Go based on file extension. Fallback mapping in Java:
|
|
|
|
```java
|
|
private String getMimeType(String path) {
|
|
if (path.endsWith(".html")) return "text/html";
|
|
if (path.endsWith(".js")) return "application/javascript";
|
|
if (path.endsWith(".css")) return "text/css";
|
|
// ... etc
|
|
return "application/octet-stream";
|
|
}
|
|
```
|
|
|
|
## JavaScript Bridge
|
|
|
|
### Exposed Interface
|
|
|
|
The `WailsJSBridge` is added to the WebView as:
|
|
```java
|
|
webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails");
|
|
```
|
|
|
|
This makes `window.wails` available in JavaScript.
|
|
|
|
### Security Considerations
|
|
|
|
1. **@JavascriptInterface annotation** is required for all exposed methods (Android 4.2+)
|
|
2. Only specific methods are exposed, not the entire object
|
|
3. Input validation should be performed on all received data
|
|
|
|
### Async Pattern
|
|
|
|
For non-blocking calls:
|
|
|
|
```javascript
|
|
// JavaScript side
|
|
const callbackId = 'cb_' + Date.now();
|
|
window.wails._callbacks[callbackId] = (result, error) => {
|
|
if (error) reject(error);
|
|
else resolve(result);
|
|
};
|
|
wails.invokeAsync(callbackId, message);
|
|
|
|
// Java side sends response via:
|
|
webView.evaluateJavascript(
|
|
"window.wails._callback('" + callbackId + "', " + result + ", null);",
|
|
null
|
|
);
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
### WebView Security
|
|
|
|
```java
|
|
WebSettings settings = webView.getSettings();
|
|
settings.setAllowFileAccess(false); // No file:// access
|
|
settings.setAllowContentAccess(false); // No content:// access
|
|
settings.setMixedContentMode(MIXED_CONTENT_NEVER_ALLOW); // HTTPS only
|
|
```
|
|
|
|
### JNI Security
|
|
|
|
1. **No arbitrary code execution**: JNI methods have fixed signatures
|
|
2. **Input validation**: All strings from Java are validated in Go
|
|
3. **Memory safety**: Go's memory management prevents buffer overflows
|
|
|
|
### Asset Security
|
|
|
|
1. **Same-origin policy**: Assets only served from `wails.localhost`
|
|
2. **No external network**: All assets embedded, no remote fetching
|
|
3. **Content Security Policy**: Can be set via HTML headers
|
|
|
|
## Configuration Options
|
|
|
|
### AndroidOptions Struct
|
|
|
|
```go
|
|
type AndroidOptions struct {
|
|
// DisableScroll disables scrolling in the WebView
|
|
DisableScroll bool
|
|
|
|
// DisableOverscroll disables the overscroll bounce effect
|
|
DisableOverscroll bool
|
|
|
|
// EnableZoom allows pinch-to-zoom in the WebView (default: false)
|
|
EnableZoom bool
|
|
|
|
// UserAgent sets a custom user agent string
|
|
UserAgent string
|
|
|
|
// BackgroundColour sets the background colour of the WebView
|
|
BackgroundColour RGBA
|
|
|
|
// DisableHardwareAcceleration disables hardware acceleration
|
|
DisableHardwareAcceleration bool
|
|
}
|
|
```
|
|
|
|
### Usage
|
|
|
|
```go
|
|
app := application.New(application.Options{
|
|
Name: "My App",
|
|
Android: application.AndroidOptions{
|
|
DisableOverscroll: true,
|
|
BackgroundColour: application.NewRGB(27, 38, 54),
|
|
},
|
|
})
|
|
```
|
|
|
|
### AndroidManifest.xml Configuration
|
|
|
|
```xml
|
|
<manifest>
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
|
|
<application
|
|
android:usesCleartextTraffic="true" <!-- For localhost -->
|
|
android:hardwareAccelerated="true">
|
|
|
|
<activity
|
|
android:name=".MainActivity"
|
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
|
android:windowSoftInputMode="adjustResize">
|
|
</activity>
|
|
</application>
|
|
</manifest>
|
|
```
|
|
|
|
## Debugging
|
|
|
|
### Logcat Filtering
|
|
|
|
```bash
|
|
# All Wails logs
|
|
adb logcat -v time | grep -E "(Wails|WailsBridge|WailsActivity)"
|
|
|
|
# Using task
|
|
task android:logs
|
|
```
|
|
|
|
### WebView Debugging
|
|
|
|
Enable in debug builds:
|
|
```java
|
|
if (BuildConfig.DEBUG) {
|
|
WebView.setWebContentsDebuggingEnabled(true);
|
|
}
|
|
```
|
|
|
|
Then in Chrome: `chrome://inspect/#devices`
|
|
|
|
### Go Debugging
|
|
|
|
```go
|
|
func androidLogf(level string, format string, a ...interface{}) {
|
|
msg := fmt.Sprintf(format, a...)
|
|
println(fmt.Sprintf("[Android/%s] %s", level, msg))
|
|
}
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
1. **"UnsatisfiedLinkError"**: Library not found or wrong architecture
|
|
2. **"No implementation found"**: JNI function name mismatch
|
|
3. **Blank WebView**: Asset serving not working, check logcat
|
|
|
|
## API Reference
|
|
|
|
### Go API (Same as Desktop)
|
|
|
|
```go
|
|
// Create application
|
|
app := application.New(application.Options{
|
|
Name: "App Name",
|
|
Assets: application.AssetOptions{
|
|
Handler: application.AssetFileServerFS(assets),
|
|
},
|
|
Services: []application.Service{
|
|
application.NewService(&MyService{}),
|
|
},
|
|
Android: application.AndroidOptions{...},
|
|
})
|
|
|
|
// Run (blocks on Android)
|
|
app.Run()
|
|
|
|
// Emit events
|
|
app.Event.Emit("eventName", data)
|
|
```
|
|
|
|
### JavaScript API
|
|
|
|
```javascript
|
|
// Call Go service method
|
|
const result = await window.wails.Call.ByName('MyService.Greet', 'World');
|
|
|
|
// Platform detection
|
|
if (window.wails.System.Platform() === 'android') { ... }
|
|
|
|
// Events
|
|
window.wails.Events.On('eventName', (data) => { ... });
|
|
```
|
|
|
|
### Android-Specific Runtime Methods
|
|
|
|
```javascript
|
|
// Vibrate (haptic feedback)
|
|
window.wails.Call.ByName('Android.Haptics.Vibrate', {duration: 100});
|
|
|
|
// Show toast
|
|
window.wails.Call.ByName('Android.Toast.Show', {message: 'Hello!'});
|
|
|
|
// Get device info
|
|
const info = await window.wails.Call.ByName('Android.Device.Info');
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Build Errors
|
|
|
|
**"NDK not found"**
|
|
```bash
|
|
# Set NDK path explicitly
|
|
export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/26.1.10909125
|
|
```
|
|
|
|
**"undefined reference to JNI function"**
|
|
- Check function name matches exactly (case-sensitive)
|
|
- Ensure `//export` comment is directly above function
|
|
|
|
**"cannot find package"**
|
|
```bash
|
|
cd examples/android && go mod tidy
|
|
```
|
|
|
|
### Runtime Errors
|
|
|
|
**App crashes on startup**
|
|
1. Check logcat for stack trace
|
|
2. Verify library is in correct jniLibs directory
|
|
3. Check architecture matches device/emulator
|
|
|
|
**WebView shows blank**
|
|
1. Enable WebView debugging
|
|
2. Check Chrome DevTools for errors
|
|
3. Verify `https://wails.localhost/` resolves
|
|
|
|
**JavaScript bridge not working**
|
|
1. Check `wails` object exists: `console.log(window.wails)`
|
|
2. Verify `@JavascriptInterface` annotations present
|
|
3. Check for JavaScript errors in console
|
|
|
|
## Future Enhancements
|
|
|
|
### Phase 1: Core Stability
|
|
- [ ] Complete JNI callback implementation for Go → Java
|
|
- [ ] Full asset server integration
|
|
- [ ] Error handling and recovery
|
|
- [ ] Unit and integration tests
|
|
|
|
### Phase 2: Feature Parity
|
|
- [ ] Clipboard support
|
|
- [ ] File dialogs (via Storage Access Framework)
|
|
- [ ] Notifications
|
|
- [ ] Deep linking
|
|
|
|
### Phase 3: Android-Specific Features
|
|
- [ ] Material Design 3 theming integration
|
|
- [ ] Edge-to-edge display support
|
|
- [ ] Predictive back gesture
|
|
- [ ] Picture-in-Picture mode
|
|
- [ ] Widgets
|
|
|
|
### Phase 4: Advanced Features
|
|
- [ ] Background services
|
|
- [ ] Push notifications (FCM)
|
|
- [ ] Biometric authentication
|
|
- [ ] App Shortcuts
|
|
- [ ] Wear OS companion
|
|
|
|
## Conclusion
|
|
|
|
This architecture provides a solid foundation for Android support in Wails v3. The design prioritizes:
|
|
|
|
1. **Compatibility**: Same Go code runs on all platforms
|
|
2. **Performance**: No network overhead, native rendering
|
|
3. **Security**: Sandboxed WebView, validated inputs
|
|
4. **Maintainability**: Clear separation of concerns
|
|
|
|
The implementation follows Android best practices while maintaining the simplicity that Wails developers expect. The JNI bridge pattern, while more complex than iOS's CGO approach, provides robust interoperability between Java and Go.
|
|
|
|
### Key Implementation Status
|
|
|
|
| Component | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| Java Activity | ✅ Complete | MainActivity with WebView |
|
|
| JNI Bridge | ✅ Complete | WailsBridge with native methods |
|
|
| Asset Handler | ✅ Complete | WailsPathHandler |
|
|
| JS Bridge | ✅ Complete | WailsJSBridge |
|
|
| Go Platform Files | ✅ Complete | All *_android.go files |
|
|
| Taskfile | ✅ Complete | Build orchestration |
|
|
| Gradle Project | ✅ Complete | App structure |
|
|
| JNI Implementation | 🔄 Partial | Needs Go → Java callbacks |
|
|
| Asset Server Integration | 🔄 Partial | Needs full wiring |
|
|
| Testing | ❌ Pending | Needs emulator testing |
|
|
|
|
---
|
|
|
|
*Document Version: 1.0*
|
|
*Last Updated: November 2024*
|
|
*Wails Version: v3-alpha*
|