前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
在适配 OpenHarmony 之前,先看看 Android 端是怎么做的。Android 用 Chrome Custom Tabs 打开认证页面,用 CallbackActivity + Intent Filter 接收回调。这套方案是 OpenHarmony 适配的主要参照物------很多设计决策都是从 Android 映射过来的。
一、Chrome Custom Tabs 与 WebView 的区别
1.1 为什么不用 WebView

1.2 Cookie 共享的重要性
用户可能已经在 Chrome 中登录了 Google 账号。如果用 Chrome Custom Tabs,用户不需要重新输入密码------直接点击"允许"就行。这对用户体验的提升是巨大的。
WebView:用户每次都要输入用户名和密码
Chrome Custom Tabs:用户可能只需要点一下"允许"
💡 这也是 OpenHarmony 用系统浏览器(openLink)而不是 WebView 的原因------系统浏览器共享用户的登录状态。
二、CallbackActivity 的 Intent Filter 配置
2.1 AndroidManifest.xml 配置
xml
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="YOUR_CALLBACK_URL_SCHEME_HERE" />
</intent-filter>
</activity>
2.2 各字段含义
| 字段 | 值 | 作用 |
|---|---|---|
| action.VIEW | 查看操作 | 表示这个 Activity 可以处理"查看"类型的 Intent |
| category.DEFAULT | 默认类别 | 允许隐式 Intent 启动 |
| category.BROWSABLE | 可浏览 | 允许从浏览器启动 |
| data.scheme | 自定义 Scheme | 匹配特定 URL Scheme |
2.3 工作流程
1. 浏览器重定向到 myapp://callback?code=abc123
2. Android 系统查找能处理 myapp:// 的 Activity
3. 找到 CallbackActivity(因为 Intent Filter 匹配)
4. CallbackActivity 启动,获取 Intent 中的 URI
5. CallbackActivity 把 URI 传给 FlutterWebAuthPlugin
6. Plugin 通过 MethodChannel 返回给 Dart
2.4 与 OpenHarmony 的对应关系
| Android | OpenHarmony |
|---|---|
| CallbackActivity | EntryAbility(复用主 Ability) |
| Intent Filter | skills 配置 |
| android:scheme | uris.scheme |
| action.VIEW | ohos.want.action.viewData |
| category.BROWSABLE | entity.system.browsable |
三、FLAG_ACTIVITY_NO_HISTORY 与 preferEphemeral
3.1 preferEphemeral 在 Android 上的实现
kotlin
// Android 端代码(简化)
val intent = CustomTabsIntent.Builder().build()
if (preferEphemeral) {
intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
}
intent.launchUrl(context, Uri.parse(url))
3.2 FLAG_ACTIVITY_NO_HISTORY 的效果
| 有 FLAG | 无 FLAG |
|---|---|
| 认证页面不保留在任务栈中 | 认证页面保留在任务栈中 |
| 用户按返回键直接回到 App | 用户按返回键回到认证页面 |
| 不留浏览历史 | 留浏览历史 |
3.3 在 OpenHarmony 上的对应
typescript
// OpenHarmony 当前实现:没有处理 preferEphemeral
const url = call.argument('url') as string;
// preferEphemeral 参数被忽略了
context.openLink(url);
📌 当前限制:OpenHarmony 的 openLink API 不支持"不留历史"的选项。preferEphemeral 参数在 OpenHarmony 上没有效果。未来可以通过 Want 的 flags 参数来实现类似功能。
四、从浏览器回调到 Flutter 的完整数据流
4.1 Android 数据流
浏览器重定向
↓
Android 系统匹配 Intent Filter
↓
CallbackActivity.onCreate(intent)
↓
intent.data → URI 字符串
↓
FlutterWebAuthPlugin.callbacks[scheme].success(uri)
↓
MethodChannel → Dart
↓
FlutterWebAuth.authenticate() 返回 uri
4.2 OpenHarmony 数据流
浏览器重定向
↓
OpenHarmony 系统匹配 Want skills
↓
EntryAbility.onNewWant(want)
↓
FlutterWebAuthPlugin.onNewWant(want)
↓
want.uri → URI 字符串
↓
FlutterWebAuthPlugin.callbacks[scheme].success(uri)
↓
MethodChannel → Dart
↓
FlutterWebAuth.authenticate() 返回 uri
4.3 关键差异
| 差异点 | Android | OpenHarmony |
|---|---|---|
| 回调接收者 | 独立的 CallbackActivity | 复用 EntryAbility |
| 数据来源 | intent.data | want.uri |
| 触发方式 | Activity 自动启动 | onNewWant 回调 |
| 需要宿主配合 | 只需配置 Manifest | 需要写代码调用 onNewWant |
五、Android 端 dangling calls 清理
5.1 问题场景
1. 用户点击登录 → 浏览器打开
2. 用户在浏览器中按返回键 → 回到 App
3. authenticate() 的 Future 还在等待 → 永远不会完成
5.2 Android 的清理机制
kotlin
// Android 端
fun cleanUpDanglingCalls() {
callbacks.forEach { (_, result) ->
result.error("CANCELED", "User canceled login", null)
}
callbacks.clear()
}
5.3 触发时机
App 回到前台(resumed)
↓
Dart: _OnAppLifecycleResumeObserver.didChangeAppLifecycleState(resumed)
↓
Dart: _cleanUpDanglingCalls()
↓
Dart → Native: invokeMethod('cleanUpDanglingCalls')
↓
Native: 遍历 callbacks,返回 CANCELED
5.4 OpenHarmony 的实现
typescript
// OpenHarmony 端:完全相同的逻辑
private cleanUpDanglingCalls(result: MethodResult): void {
FlutterWebAuthPlugin.callbacks.forEach((danglingResult: MethodResult) => {
danglingResult.error("CANCELED", "User canceled login", null);
});
FlutterWebAuthPlugin.callbacks.clear();
result.success(null);
}
💡 这是 Dart 层驱动的清理机制 。不管原生端是 Android 还是 OpenHarmony,清理逻辑都是 Dart 层的 Observer 触发的。原生端只需要实现
cleanUpDanglingCalls方法就行。
六、Result 生命周期管理
6.1 MethodResult 的生命周期
创建:onMethodCall 中接收 result 参数
↓
存储:callbacks[scheme] = result
↓
使用:result.success(uri) 或 result.error(...)
↓
销毁:从 callbacks Map 中删除
6.2 一个 Result 只能用一次
typescript
// ✅ 正确:用完就删
const pendingResult = FlutterWebAuthPlugin.callbacks.get(scheme);
FlutterWebAuthPlugin.callbacks.delete(scheme); // 先删
pendingResult.success(uri); // 再用
// ❌ 错误:重复使用
pendingResult.success(uri);
pendingResult.success(uri); // 第二次调用会崩溃
6.3 防止 Result 泄漏
| 场景 | 处理方式 |
|---|---|
| 正常回调 | onNewWant 中 success + delete |
| 用户取消 | cleanUpDanglingCalls 中 error + clear |
| 插件销毁 | onDetachedFromEngine 中不处理(Dart 层会清理) |
七、Android 实现的设计启示
7.1 对 OpenHarmony 适配的启示
| Android 设计 | OpenHarmony 对应 | 启示 |
|---|---|---|
| CallbackActivity | EntryAbility.onNewWant | 回调入口 |
| Intent Filter | skills 配置 | 深度链接匹配 |
| static callbacks | static callbacks | 跨实例数据共享 |
| cleanUpDanglingCalls | cleanUpDanglingCalls | 用户取消处理 |
7.2 OpenHarmony 的额外挑战
- 没有独立的 CallbackActivity:必须在 EntryAbility 中手动集成
- openLink 是异步的:Android 的 startActivity 是同步的
- 需要降级方案:openLink 失败时要 fallback 到 startAbility
总结
本文分析了 Android 端的 Chrome Custom Tabs 实现:
- Chrome Custom Tabs:共享 Cookie、安全沙箱、用户信任度高
- CallbackActivity:通过 Intent Filter 捕获深度链接回调
- preferEphemeral:FLAG_ACTIVITY_NO_HISTORY 不留浏览历史
- dangling calls:Dart 层 Observer 驱动的清理机制
- Result 生命周期:一次性使用,用完即删
下一篇我们看 iOS/macOS 端的 ASWebAuthenticationSession 实现。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: