# Android SDK 接入与构建

原生 Java SDK 位于 `android/sdk`，无第三方运行依赖，最低 API 23。已经提供 Gradle 工程与无需完整 Android SDK 的 AAR 编译脚本。原生宿主示例位于 `android/demo`。

## 当前产物

`android/dist/starlight-sdk-0.1.0.aar` 包含 Java 字节码、Manifest、混淆规则。仅请求网络权限，不申请跨应用悬浮权限；平台悬浮球附着在宿主 Activity 中。

## 接入代码

```java
StarlightSdk sdk = new StarlightSdk(
    new StarlightSdk.Config("starlight-android", "https://your-account-origin.example", false),
    new StarlightSdk.Listener() {
        @Override public void onAuthorization(StarlightSdk.Authorization authorization) {
            // 将 authorization.toJson() 发送到自己的游戏服务端。
            // 游戏服务端使用只存于服务端的凭据兑换一次性 code。
            // 异步取得 SessionResponse 后在主线程调用：
            // sdk.completeGameLogin(authorization, responseJson);
        }
        @Override public void onSessionChanged(JSONObject session) { /* 建立游戏会话后进入游戏 */ }
        @Override public void onLogout() { /* 清理角色、连接和旧会话 */ }
        @Override public void onError(String code, String message) { /* 展示或记录脱敏诊断 */ }
    }
);
sdk.showFloat(activity);
sdk.login(activity);
```

必须使用 `completeGameLogin(authorization, responseJson)` 绑定原始授权事务。已弃用的单参数入口会安全拒绝，因为它无法区分已取消的旧登录。方法涉及界面时由主线程调用。

首款接入推荐与示例一致：每个 Activity 创建自己的 SDK，`onDestroy()` 调用 `sdk.destroy()` 并将宿主引用置空；新 Activity 创建新 SDK 并重新登录。该策略优先保证生命周期清晰，当前 SDK 不提供 Activity listener 的自动重绑定。

`detachActivity()` 只拆除浮球、关闭对话框并使旧界面事务失效，**不会替换或释放构造时的 final Listener**。若 Listener 就是旧 Activity，不能仅调用 detach 后将 SDK 保存在单例中跨旋转复用，否则仍可能保留旧 Activity 并向其回调。`destroy()` 会关闭线程、回调队列和会话，但也不会把 final Listener 设为 null；销毁后仍需释放外部持有的 SDK 引用。

确需跨 Activity 保留会话的宿主，应另行设计不强引用 Activity 的生命周期路由监听器，主动解除旧宿主并验证重新附着；这不是当前示例已实现或已验收的能力。

## 本地演示

先在根目录执行 `npm run local`。Android 模拟器使用 `http://10.0.2.2:18787`。通过 USB 连接真机时可执行：

```text
adb reverse tcp:18787 tcp:18787
```

然后在示例 App 输入 `http://127.0.0.1:18787`。这是显式演示模式，仅允许 loopback / 模拟器主机 HTTP；正式配置要求无用户信息、无路径查询片段的 HTTPS origin，不允许忽略证书失败。

示例中的 `exchangeWithDemoBackend` 只用于本地验证。正式游戏通过自己的可信后端兑换，不能把 `GAME_SERVER_KEY` 放在 APK、Java、JavaScript、资源文件或远程公开配置中。

## 构建

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\android\build-aar.ps1
```

脚本从 Maven Central 下载固定版本的 Android API 编译 jar，验证远端发布摘要后使用 JDK 17 编译 Java 8 字节码，打包资源为空的标准 AAR，并编译宿主示例 Java。下载缓存放在 `.local/android-tools`。这验证编译和 AAR 结构，不执行 App，也不生成已经过设备验证的 APK。

安装 Android Studio / API 35 / Gradle 8.9 兼容工具链后，可以打开 `android` 工程运行 `:demo:assembleDebug` 和真实设备测试。仓库未附带 Gradle wrapper，本地便携编译不依赖它。

## 验证范围

已审查并修复授权事务、会话 generation、界面 generation、Activity 关闭与迟到响应、旧支付确认、验证码手机号变化、平台功能开关及演示支付限制。

仍需设备验证：软键盘、系统返回、横竖屏、低内存进程恢复、主流 ROM、宿主引擎冲突、渠道跳转和安装签名。编译成功不代表这些项目已经通过。

## 本轮公开接口与恢复补齐

新增 authorize(JSONObject)/cancelLogin()、getConfig/sendSms/resetPassword/changePassword、getCenter、订单创建/查询/支付/关闭/退款、礼包/券/每日福利/消息、客服工单分页/详情/创建/回复/关闭。原生 UI API 保持兼容，完整签名见 apps/docs/v0.1.0/android/reference.md。没有独立注册路由；短信首登自动注册。

exportSession() 导出带版本、App ID、API origin 的有效会话副本；restoreSession(saved, callback) 先用候选调用 /api/me，验证成功才公开身份。SDK 不写 Preferences，宿主负责加密保存并在退出时删除。网络失败不修改保存候选；scope 不符、到期和明确失效需按错误处理。候选恢复不能复用 completeGameLogin 的旧授权对象。每 Activity 独立实例策略仍推荐，新实例可显式验证恢复，final Listener 释放要求不变。

修复带 token 的所有401均退出：现在只对明确会话失效错误清理；错误旧密码保留会话，并且失效也完成 Callback.failure。完成授权还检查 expiresAt/user.id/role.id。headless demo=false 拒绝演示支付创建与付款；真实支付/远端发货仍未接通。

运行 powershell -NoProfile -ExecutionPolicy Bypass -File android/test-protocol.ps1 验证真实HTTP/JSON协议和会话逻辑；Handler/Looper/Base64使用JVM shim，不能视为真机验证。

终止状态还包括 HTTP 403 ACCOUNT_RESTRICTED / APP_UNAVAILABLE；普通业务403不清会话。恢复候选被这些状态拒绝时，宿主应删除其保存副本。
