This commit is contained in:
Lea Anthony 2026-03-01 17:17:53 +08:00 committed by GitHub
commit 4ccee28a2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 166 additions and 17 deletions

View file

@ -139,8 +139,11 @@ static void executeJavaScriptOnBridge(const char* js) {
import "C"
import (
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
@ -148,8 +151,6 @@ import (
"time"
"unsafe"
"encoding/json"
"github.com/wailsapp/wails/v3/internal/runtime"
)
@ -221,6 +222,12 @@ func (a *App) platformRun() {
globalApp = a
globalAppLock.Unlock()
// Create MessageProcessor so JNI message callbacks can route runtime calls
messageProc := NewMessageProcessor(slog.Default())
globalMessageProcLock.Lock()
globalMessageProc = messageProc
globalMessageProcLock.Unlock()
// Signal that the app is ready to serve requests
signalAppReady()
@ -259,6 +266,9 @@ func newPlatformApp(app *App) *androidApp {
func (a *androidApp) run() error {
androidLogf("info", "🤖 [application_android.go] androidApp.run() called")
// Wire platform events → common events (same as non-CGO build)
a.setupCommonEvents()
// Emit application started event
a.parent.Event.Emit("ApplicationStarted")
@ -433,7 +443,7 @@ func Java_com_wails_app_WailsBridge_nativeOnPageFinished(env *C.JNIEnv, obj C.jo
if win != nil {
androidLogf("info", "🤖 [JNI] Injecting runtime.Core() into window %d", id)
// Get the runtime core JavaScript
runtimeJS := runtime.Core()
runtimeJS := runtime.Core(app.impl.GetFlags(app.options))
androidLogf("info", "🤖 [JNI] Runtime JS length: %d bytes", len(runtimeJS))
app.windowsLock.RUnlock()
// IMPORTANT: We must bypass win.ExecJS because it queues if runtimeLoaded is false.
@ -621,16 +631,60 @@ func serveAssetForAndroid(app *App, path string) ([]byte, error) {
return body, nil
}
// Global MessageProcessor for Android JNI callbacks
var (
globalMessageProc *MessageProcessor
globalMessageProcLock sync.RWMutex
)
func handleMessageForAndroid(app *App, message string) string {
// Parse the message
var msg map[string]interface{}
if err := json.Unmarshal([]byte(message), &msg); err != nil {
// Some messages are plain strings (e.g. "wails:runtime:ready"), not JSON.
// The JS bridge sends: typeof m==='string' ? m : JSON.stringify(m)
if len(message) == 0 || message[0] != '{' {
androidLogf("debug", "🤖 [handleMessageForAndroid] Non-JSON message: %s", message)
return `{"success":true}`
}
var req RuntimeRequest
if err := json.Unmarshal([]byte(message), &req); err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Failed to parse: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
// TODO: Route to appropriate handler based on message type
// For now, return success
return `{"success":true}`
// Fill in a window ID if none was provided (Android typically has one window)
if req.WebviewWindowID == 0 && req.WebviewWindowName == "" {
windows := app.Window.GetAll()
if len(windows) > 0 {
req.WebviewWindowID = uint32(windows[0].ID())
}
}
globalMessageProcLock.RLock()
messageProc := globalMessageProc
globalMessageProcLock.RUnlock()
if messageProc == nil {
androidLogf("error", "🤖 [handleMessageForAndroid] MessageProcessor not initialized")
return `{"error":"MessageProcessor not initialized"}`
}
ctx := context.Background()
result, err := messageProc.HandleRuntimeCallWithIDs(ctx, &req)
if err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Error: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
if result == nil {
return `{"success":true}`
}
resp, err := json.Marshal(result)
if err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Marshal error: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
return string(resp)
}
func getMimeTypeForPath(path string) string {

View file

@ -3,11 +3,12 @@
package application
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"sync"
"unsafe"
"encoding/json"
)
var (
@ -38,6 +39,12 @@ func (a *App) platformRun() {
globalApp = a
globalAppLock.Unlock()
// Create MessageProcessor so message callbacks can route runtime calls
messageProc := NewMessageProcessor(slog.Default())
globalMessageProcLock.Lock()
globalMessageProc = messageProc
globalMessageProcLock.Unlock()
androidLogf("info", "🤖 [application_android.go] Waiting for Android lifecycle...")
// Block forever - Android manages the app lifecycle via JNI callbacks
@ -171,16 +178,58 @@ func serveAssetForAndroid(app *App, path string) ([]byte, error) {
return nil, fmt.Errorf("asset serving not yet implemented: %s", path)
}
// Global MessageProcessor for message callbacks
var (
globalMessageProc *MessageProcessor
globalMessageProcLock sync.RWMutex
)
func handleMessageForAndroid(app *App, message string) string {
// Parse the message
var msg map[string]interface{}
if err := json.Unmarshal([]byte(message), &msg); err != nil {
// Some messages are plain strings (e.g. "wails:runtime:ready"), not JSON.
if len(message) == 0 || message[0] != '{' {
androidLogf("debug", "🤖 [handleMessageForAndroid] Non-JSON message: %s", message)
return `{"success":true}`
}
var req RuntimeRequest
if err := json.Unmarshal([]byte(message), &req); err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Failed to parse: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
// TODO: Route to appropriate handler based on message type
// For now, return success
return `{"success":true}`
if req.WebviewWindowID == 0 && req.WebviewWindowName == "" {
windows := app.Window.GetAll()
if len(windows) > 0 {
req.WebviewWindowID = uint32(windows[0].ID())
}
}
globalMessageProcLock.RLock()
messageProc := globalMessageProc
globalMessageProcLock.RUnlock()
if messageProc == nil {
androidLogf("error", "🤖 [handleMessageForAndroid] MessageProcessor not initialized")
return `{"error":"MessageProcessor not initialized"}`
}
ctx := context.Background()
result, err := messageProc.HandleRuntimeCallWithIDs(ctx, &req)
if err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Error: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
if result == nil {
return `{"success":true}`
}
resp, err := json.Marshal(result)
if err != nil {
androidLogf("error", "🤖 [handleMessageForAndroid] Marshal error: %v", err)
return fmt.Sprintf(`{"error":"%s"}`, err.Error())
}
return string(resp)
}
func getMimeTypeForPath(path string) string {

View file

@ -523,6 +523,40 @@ func newIOSEvents() iosEvents {
}
}
var Android = newAndroidEvents()
type androidEvents struct {
ActivityCreated ApplicationEventType
ActivityStarted ApplicationEventType
ActivityResumed ApplicationEventType
ActivityPaused ApplicationEventType
ActivityStopped ApplicationEventType
ActivityDestroyed ApplicationEventType
ApplicationLowMemory ApplicationEventType
ApplicationConfigChanged ApplicationEventType
WebViewDidStartNavigation WindowEventType
WebViewDidFinishNavigation WindowEventType
WebViewDidFailNavigation WindowEventType
WebViewReceivedError WindowEventType
}
func newAndroidEvents() androidEvents {
return androidEvents{
ActivityCreated: 1259,
ActivityStarted: 1260,
ActivityResumed: 1261,
ActivityPaused: 1262,
ActivityStopped: 1263,
ActivityDestroyed: 1264,
ApplicationLowMemory: 1265,
ApplicationConfigChanged: 1266,
WebViewDidStartNavigation: 1267,
WebViewDidFinishNavigation: 1268,
WebViewDidFailNavigation: 1269,
WebViewReceivedError: 1270,
}
}
func JSEvent(event uint) string {
return eventToJS[event]
}
@ -763,4 +797,16 @@ var eventToJS = map[uint]string{
1256: "ios:WebViewDidFinishNavigation",
1257: "ios:WebViewDidFailNavigation",
1258: "ios:WebViewDecidePolicyForNavigationAction",
1259: "android:ActivityCreated",
1260: "android:ActivityStarted",
1261: "android:ActivityResumed",
1262: "android:ActivityPaused",
1263: "android:ActivityStopped",
1264: "android:ActivityDestroyed",
1265: "android:ApplicationLowMemory",
1266: "android:ApplicationConfigChanged",
1267: "android:WebViewDidStartNavigation",
1268: "android:WebViewDidFinishNavigation",
1269: "android:WebViewDidFailNavigation",
1270: "android:WebViewReceivedError",
}