scripts/repro_gioui_hang.sh builds the stock gioui.org/example 'hello'
app (v0.10.3, unmodified source), installs it on an emulator, recreates
the activity with a real uiMode toggle, and checks for NO_INPUT_CHANNEL
after a tap. Exit 0 = bug reproduced, 1 = clean, 2 = setup failure.
The bug: gioui v0.10.x app/os_android.go onCreateView#2 sends the
window-creation ack (mainWindow.windows <- struct{}{}) on the UI thread
with no receiver (Window.init never re-runs on Android), blocking the UI
thread forever -> no input channel, black unresponsive app. The fix
lives in our gioui fork (only send the ack on first window creation).
103 lines
4.0 KiB
Bash
Executable File
103 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Reproduce the gioui Android activity-recreation hang with a STOCK gioui
|
|
# example app (the "hello" example from gioui.org/example).
|
|
#
|
|
# What it does:
|
|
# 1. Builds gioui.org/example's hello app (unmodified) with gogio.
|
|
# 2. Installs it on an emulator (boots one if needed).
|
|
# 3. Recreates the activity via `cmd uimode night yes` (uiMode is not in
|
|
# gioui's configChanges mask, so Android destroys/recreates the
|
|
# activity in the same process).
|
|
# 4. Taps the app and checks logcat for NO_INPUT_CHANNEL.
|
|
#
|
|
# The bug (gioui v0.10.x, app/os_android.go onCreateView): on re-attach the
|
|
# second onCreateView sends the window-creation ack
|
|
# (mainWindow.windows <- struct{}{}) on the UI thread, but its only
|
|
# receiver (newWindow in Window.init) never re-runs on Android — the driver
|
|
# is never reset, so w.Event() never returns to re-init. The UI thread
|
|
# blocks forever, the activity never attaches an input channel, and the app
|
|
# is black and unresponsive (process still alive).
|
|
#
|
|
# Usage:
|
|
# scripts/repro_gioui_hang.sh [avd-name] [path-to-apk]
|
|
#
|
|
# Exit codes: 0 = bug REPRODUCED, 1 = not reproduced, 2 = setup failure.
|
|
set -euo pipefail
|
|
|
|
AVD=${1:-pad_avd}
|
|
APK=${2:-}
|
|
SDK=${ANDROID_HOME:-$HOME/android-sdk}
|
|
export PATH=$PATH:$HOME/go/bin:$SDK/platform-tools:$SDK/build-tools/35.0.0
|
|
|
|
say() { printf '\n=== %s\n' "$*"; }
|
|
die() { say "FAIL: $*"; exit 2; }
|
|
|
|
# --- 1. Build the stock hello example, unless an APK was supplied ---------
|
|
if [ -z "$APK" ]; then
|
|
SRC=$(mktemp -d)
|
|
trap 'rm -rf "$SRC"' EXIT
|
|
say "building stock gioui.org/example hello app (v0.10.3)"
|
|
go mod download gioui.org/example@v0.10.3
|
|
cp "$HOME/go/pkg/mod/gioui.org/example@v0.10.3/hello/hello.go" "$SRC/main.go"
|
|
cat > "$SRC/go.mod" <<'EOF'
|
|
module hello
|
|
|
|
go 1.23
|
|
|
|
require gioui.org v0.10.3
|
|
EOF
|
|
(cd "$SRC" && go mod tidy && gogio -target android -targetsdk 34 -arch amd64 -o /tmp/hello-gioui-repro.apk .)
|
|
APK=/tmp/hello-gioui-repro.apk
|
|
fi
|
|
[ -f "$APK" ] || die "APK not found: $APK"
|
|
|
|
# --- 2. Emulator: reuse a booted one or start $AVD ------------------------
|
|
say "waiting for emulator (avd $AVD)"
|
|
EMU=""
|
|
for _ in $(seq 1 60); do
|
|
EMU=$(adb devices | awk '$1 ~ /^emulator-/ {print $1; exit}')
|
|
[ -n "$EMU" ] && break
|
|
emulator -avd "$AVD" -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect >/dev/null 2>&1 &
|
|
sleep 5
|
|
done
|
|
[ -n "$EMU" ] || die "no emulator available"
|
|
for _ in $(seq 1 60); do
|
|
bc=$(adb -s "$EMU" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')
|
|
[ "$bc" = "1" ] && break
|
|
sleep 5
|
|
done
|
|
[ "$(adb -s "$EMU" shell getprop sys.boot_completed | tr -d '\r')" = "1" ] || die "emulator did not boot"
|
|
say "emulator: $EMU"
|
|
|
|
# --- 3. Install + launch ---------------------------------------------------
|
|
APPID=localhost.hello # gogio's default app id for module "hello"
|
|
adb -s "$EMU" install -r "$APK" >/dev/null
|
|
adb -s "$EMU" shell am force-stop "$APPID" >/dev/null 2>&1 || true
|
|
adb -s "$EMU" logcat -c
|
|
adb -s "$EMU" shell am start -n "$APPID/org.gioui.GioActivity" >/dev/null
|
|
sleep 5
|
|
adb -s "$EMU" shell pidof "$APPID" >/dev/null || die "app did not start"
|
|
say "baseline: app running"
|
|
|
|
# --- 4. Recreate the activity ----------------------------------------------
|
|
# uiMode is not in gioui's configChanges mask -> Android recreates the
|
|
# activity. Force a real state change (a no-op toggle recreates nothing).
|
|
cur=$(adb -s "$EMU" shell cmd uimode night | tr -d '\r') # "Night mode: yes/no"
|
|
case $cur in
|
|
*yes*) adb -s "$EMU" shell cmd uimode night no >/dev/null ;;
|
|
*) adb -s "$EMU" shell cmd uimode night yes >/dev/null ;;
|
|
esac
|
|
sleep 5
|
|
adb -s "$EMU" logcat -c
|
|
adb -s "$EMU" shell input tap 540 1200
|
|
sleep 2
|
|
|
|
# --- 5. Verdict -------------------------------------------------------------
|
|
adb -s "$EMU" exec-out screencap -p > /tmp/gioui-hang-repro.png
|
|
if adb -s "$EMU" logcat -d | grep -q NO_INPUT_CHANNEL; then
|
|
say "REPRODUCED: activity recreation left the window with NO_INPUT_CHANNEL (see /tmp/gioui-hang-repro.png)"
|
|
exit 0
|
|
fi
|
|
say "NOT reproduced: no NO_INPUT_CHANNEL after recreation (gioui handles re-attach correctly)"
|
|
exit 1
|