Files
DadaoTask/android/README.md
T
dadao 0a3b0f3ca0
CI / backend (push) Canceled after 0s
fix(android): 真机登录不上——明文流量被网络配置拦住,且地址写死在模拟器网段
用户反馈"app 端登录不上去"。查下来是两个叠在一起的问题,都不是"后台没开":

1) 主清单设了 android:networkSecurityConfig(白名单只有 10.0.2.2 / 127.0.0.1 / localhost),
   而它的优先级高于 debug 清单里的 android:usesCleartextTraffic="true"。
   所以真机连 http://192.168.8.x:8100 会被系统拦成 CLEARTEXT communication not permitted,
   表现成登录无反应;模拟器因为地址在白名单里,一切正常,很容易误判。
   → 新增 app/src/debug/res/xml/network_security_config.xml,debug 放开明文;
     release 仍只有白名单,线上必须 HTTPS。

2) 后端地址编译期写死 BuildConfig.API_BASE = http://10.0.2.2:8100,那是模拟器专用地址。
   → 登录页加「服务器:… / 改地址」:可改、可「测试连接」、存在 TokenStore 里,
     改一次就记住,不用重新打包。地址缺失协议时自动补 http://。

顺带把网络异常翻成人话:以前 OkHttp 抛 IOException 原样冒到界面,
现在统一成「连不上 <地址> —— 具体该去查什么」,并区分了
"10.0.2.2 是模拟器地址"、"127.0.0.1 在手机上指的是手机自己"两种情况;
连不上时自动展开地址面板。

后端这边没有代码改动,但真机要用 --host 0.0.0.0 启动(原来只监听 127.0.0.1),
已写进 DEVELOPMENT.md 与 android/README.md,附防火墙放行命令。

实测:模拟器内把地址改成 http://192.168.8.151:8100 → 测试连接「连得通」→
取码(开发环境验证码回显)→ 登录成功进入工作区列表。
2026-10-28 23:38:21 +08:00

130 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 安卓端
Kotlin + Jetpack Compose。功能与 Web 端同构:登录 → 工作区 → 任务 / 工单 / 知识。
## 它已经能编译出 APK 了(本机实测)
```
BUILD SUCCESSFUL in 38s
app/build/outputs/apk/debug/app-debug.apk 9.94 MB
```
而且**真的在模拟器上跑通了**:登录 → 建工作区 → 任务列表(中文标题、状态、截止日、勾选完成)→ 工单列表 → 知识列表,全部打到真实后端。
| 登录 | 任务列表 |
|---|---|
| ![登录](../2.Design/screens/android-login.png) | ![任务](../2.Design/screens/android-tasks.png) |
一键起模拟器 + 装包(环境变量都替你设好了):
```bash
pwsh -File android/scripts/emulator.ps1 -Install
```
本机从零把编译链搭起来踩过的坑都固化在仓库里了,别再走一遍:
| 现象 | 原因 | 仓库里的解法 |
|---|---|---|
| `Failed to load native library 'native-platform.dll'` | 旧 `~/.gradle` 缓存坏了 | 用独立 `GRADLE_USER_HOME`(我用的 `.local/gradle-home`) |
| `validateSigningDebug: AccessDeniedException debug.keystore.lock` | 沙箱/受限用户对 `~/.android` 无写权限 | 工程内 keystore:`android/keystore/debug.keystore`,`app/build.gradle.kts` 显式指向 |
| `compileDebugKotlin: AccessDeniedException %TEMP%\kotlin-compiler-*.alive` | `%TEMP%` 不可写 | 构建前把 `TEMP`/`TMP` 指到工程内可写目录 |
| `Could not connect to Kotlin compile daemon` | 受限环境里 Kotlin 编译守护进程连不回来 | `gradle.properties` 里 `kotlin.compiler.execution.strategy=in-process` |
| 依赖下载极慢(5 分钟 2MB) | Google Maven 直连慢 | `settings.gradle.kts` 先走阿里云镜像,再回落 google/mavenCentral |
需要:**JDK 17**(`JAVA_HOME`)、Android SDK **platform 35** + build-tools。
```bash
cd android
gradle :app:assembleDebug # 或 ./gradlew(wrapper 需 Android Studio 补齐)
```
## 连不上后端?先看这三件事
1. **后端要监听所有网卡**:`uvicorn app.main:app --host 0.0.0.0 --port 8100`。
默认的 `127.0.0.1` 只有本机可见,手机连不上。
2. **地址要填对**:包里的默认值是模拟器地址 `http://10.0.2.2:8100`。
真机在登录页点「改地址」填电脑的局域网 IP(如 `http://192.168.8.151:8100`),
点「测试连接」看到「连得通」再登录。改一次会记住,不用重新打包。
3. **防火墙**:Windows 没弹过「允许访问」的话,手机包会被丢。管理员 PowerShell 执行
`New-NetFirewallRule -DisplayName "DadaoTask backend 8100" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8100 -Profile Private`。
还有一个容易查半天的点:主清单配了 `network_security_config`(白名单只有
`10.0.2.2/127.0.0.1/localhost`),这种情况下 `android:usesCleartextTraffic="true"` **不生效**,
真机连 `http://192.168.8.x` 会被系统拦成 "CLEARTEXT communication not permitted",
表现就是"点了登录没反应/登录不上去",而模拟器一切正常。
所以 `android/app/src/debug/res/xml/network_security_config.xml` 单独给 debug 放开了明文,
release 依旧只走 HTTPS 白名单。
## 连哪个后端
`android/local.properties`(不入库):
```properties
dadao.apiBase=http://10.0.2.2:8100 # 模拟器访问宿主机
# dadao.apiBase=http://192.168.8.160:8100 # 真机走局域网
```
真机调试时后端必须监听 `0.0.0.0` 而不是 `127.0.0.1`:
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8100
```
`app/src/debug/AndroidManifest.xml` 对 debug 变体放开了明文 HTTP——
release 仍然只允许 `network_security_config.xml` 里列出的域,别把这条挪到 main。
## 安装到手机
```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
首次打开 → 手机号登录(开发环境验证码在接口响应里回显,形如 `开发环境验证码:123456`)。
## 结构
```
app/src/main/java/com/dadao/task/
├── MainActivity.kt 入口 + Splash/Login/Home/Tasks/Knowledge 路由
├── data/
│ ├── Api.kt OkHttp + kotlinx.serialization;信封解包、业务码抛 ApiException
│ └── TokenStore.kt 令牌本地存储(TODO:换 EncryptedSharedPreferences)
└── ui/
├── Theme.kt 与 Web 一致的墨青 + 陶土色板
├── LoginScreen.kt 手机号 + 验证码
├── HomeScreen.kt 工作区列表 / 新建 / 生成邀请码
├── TaskListScreen.kt 任务视图切换、快速新建、勾选完成、详情与评论
└── KnowledgeScreen.kt 知识四态 tab、手记、按状态给动作(发布需 owner/admin)
```
## 模拟器(本机搭建记录,别重复踩)
`sdkmanager` 在这台机器上拉不到远端仓库索引(`Failed to download any source lists!`,但直连
`dl.google.com` 的 zip 是通的),所以系统镜像是**手动装的**:
1. 下 `https://dl.google.com/android/repository/sys-img/google_apis/x86_64-35_r09.zip`(1.66 GB),
解压到 `%LOCALAPPDATA%\Android\Sdk\system-images\android-35\google_apis\x86_64`
—— 注意压缩包里还有一层 `x86_64/`,要**把内容提上一层**,否则 AGP 找不到 `system.img`。
2. 建 AVD 时把用户目录指到工程内(`~/.android` 在受限用户下不可写):
```powershell
$env:ANDROID_USER_HOME="$repo\.local\android-user"
$env:ANDROID_AVD_HOME="$repo\.local\android-avd"
avdmanager create avd -n dadao -k "system-images;android-35;google_apis;x86_64" -d pixel_6 --force
```
3. 启动必须带 `-no-window -gpu swiftshader_indirect`(无头环境下 GL 走软件渲染);
`emulator -accel-check` 应显示 `WHPX ... installed and usable`。
驱动模拟器点按不要凭截图猜坐标——`adb shell input tap` 用的是**物理像素**,而截图常被降采样。
用 `adb shell uiautomator dump` 拿 `bounds` 最稳。另外 `adb shell input text` 打不了中文(会 NPE),
中文数据走接口造。
## 已知待办
- 工单页还没做(Web 已有):需要"我指派的 / 我发起的"两栏 + 接收/开工/提交/验收按钮;
接口在 `Api.kt` 里已经齐了(`tickets` / `ticketAction` / `verifyTicket` / `promote`)。
- 明文令牌存储(`TokenStore` 里已标 TODO)。
- 无离线缓存,弱网直接报错——与产品 D7「在线优先」一致。
- 邀请链接 `https://dadaotask.com/join/<wsId>/<code>` 已在 Manifest 注册,落地处理尚未实现。
- 未在本机跑过模拟器(无 system-image、无连接设备),端到端只验证到"能编译、能装"。