适合谁看
-
想判断鸿蒙防窥能力落点的人
-
做内容型、工具型、隐私型页面设计的人
-
不想为了展示能力而牺牲体验的人
问题背景
系统能力一旦接进项目,大家很容易出现两个极端:
-
什么页面都不接
-
什么页面都接
真正合理的做法,是让能力只出现在它真正有价值的页面。
项目中的真实场景
食界探味当前的防窥命名已经给了一个很强的信号:
// anti_peep_protection_channel.dart
static Future<void> activateCollectionProtection() async {
await _invoke('activateCollectionProtection');
}
static Future<void> deactivateCollectionProtection() async {
await _invoke('deactivateCollectionProtection');
}
方法名里的 Collection 说明它优先面向收藏页。
激活时机在 app.dart 的底部导航栏管理中:
_scheduleCollectionProtectionSync(
isLoggedIn && widget.navigationShell.currentIndex == 2, // index 2 = 收藏页
);
只有用户切到收藏页(index 2)且已登录时才激活防窥。
核心实现
一、适合接入防窥的页面
| 页面 | 为什么适合 | 防窥价值 |
|---|---|---|
| 收藏页 | 展示用户收藏的菜品,暴露个人口味偏好 | 高 |
| 心愿单页 | 展示用户"想吃"的菜品,暴露个人兴趣 | 高 |
| 个人资料页 | 展示用户名、头像、个人设置 | 中高 |
| 订单/历史页 | 展示用户消费记录 | 高 |
| AI 对话历史 | 展示用户和 AI 的对话内容 | 中高 |
这些页面的共同特点:内容和个人身份、偏好、消费行为强关联。被旁人看到可能造成真实困扰。
食界探味当前选择收藏页作为防窥对象,原因很明确:
收藏页的内容:
- 用户收藏了哪些菜品 → 暴露口味偏好
- 用户喜欢哪个国家的菜 → 暴露饮食习惯
- 用户收藏的频率和时间 → 暴露生活规律
被旁人看到的后果:
- "你怎么收藏了这么多日料" → 尴尬
- "你居然喜欢这个" → 隐私暴露
二、不适合接入防窥的页面
| 页面 | 为什么不适合 | 防窥价值 |
|---|---|---|
| 首页/探索页 | 公共推荐内容,和个人无关 | 无 |
| 搜索页 | 搜索结果是公共数据 | 无 |
| 菜品详情页 | 展示的是公共菜品信息 | 无 |
| 食材列表页 | 公共食材数据 | 无 |
| 风味地图页 | 公共地理数据 | 无 |
| 关于页/设置页 | 不涉及个人数据 | 无 |
这些页面即使不开防窥,实际风险也不高。如果强行开启,反而会:
-
打断用户浏览公共内容的体验
-
增加不必要的系统开销
-
让用户困惑"为什么看个菜谱还要防窥"
三、判断框架------3 个问题
判断一个页面是否适合接入防窥,问自己 3 个问题:
| 问题 | 是 → 适合 | 否 → 不适合 |
|---|---|---|
| 这里展示的内容是否明显带有用户偏好? | 适合 | 不适合 |
| 被旁人看到会不会造成真实困扰? | 适合 | 不适合 |
| 开启防窥后会不会明显打断主要操作? | 不适合 | 适合 |
三个条件需要同时满足前两个"是",且第三个"否",才值得接。
用这个框架检验食界探味的收藏页:
| 问题 | 收藏页 | 探索页 |
|---|---|---|
| 内容带有用户偏好? | ✅ 是 | ❌ 否 |
| 被看到有困扰? | ✅ 是 | ❌ 否 |
| 会打断操作? | ❌ 否(只是隐藏内容) | ❌ 否 |
| 结论 | ✅ 适合 | ❌ 不适合 |
四、食界探味的页面分析
食界探味当前的主要页面:
| 页面 | Tab | 个人数据 | 防窥适合度 |
|---|---|---|---|
| 探索页 | 首页 | 无 | ❌ 不适合 |
| 灵感页 | Tab 2 | 无 | ❌ 不适合 |
| 收藏页 | Tab 3 | 有(收藏的菜品) | ✅ 适合 |
| 我的页 | Tab 4 | 有(个人资料) | ⚠️ 可考虑 |
| AI 助手页 | 独立页 | 有(对话历史) | ⚠️ 可考虑 |
| 搜索页 | 独立页 | 无 | ❌ 不适合 |
| 菜品详情页 | 独立页 | 无 | ❌ 不适合 |
| 食材详情页 | 独立页 | 无 | ❌ 不适合 |
当前只在收藏页激活防窥,这是一个合理的起点。未来可以考虑扩展到 AI 助手页(对话历史可能暴露用户兴趣)。
五、为什么不应该全局开启
全局开启防窥的问题:
| 问题 | 说明 |
|---|---|
| 体验断裂 | 用户看个菜谱都要被遮罩,体验很差 |
| 系统开销 | 鸿蒙一直监听防窥事件,浪费资源 |
| 用户困惑 | "为什么这个页面也要防窥?" |
| 误触发 | 公共内容被遮罩,用户以为 app 出 bug 了 |
| 比赛扣分 | 评委看到全局防窥会质疑产品判断力 |
六、防窥的激活时机设计
食界探味的激活时机设计:
// app.dart → _ScaffoldWithNavBarState
void _scheduleCollectionProtectionSync(bool shouldProtect) {
if (_lastCollectionProtectionTarget == shouldProtect) return;
_lastCollectionProtectionTarget = shouldProtect;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (shouldProtect) {
AntiPeepProtectionChannel.activateCollectionProtection();
} else {
AntiPeepProtectionChannel.deactivateCollectionProtection();
}
});
}
调用时机:
用户切 Tab
│
├─ currentIndex == 2(收藏页)→ activate
├─ currentIndex != 2 → deactivate
│
▼
页面退出
│
└─ dispose() → deactivate(确保取消)
这个设计保证了:
-
只在收藏页时激活,其他页面不激活
-
切走时立即取消,不残留
-
退出时兜底取消,不泄漏
七、防窥和页面体验的平衡
防窥保护会增加一层"被监控感"。所以在设计时需要平衡:
| 维度 | 好的设计 | 差的设计 |
|---|---|---|
| 激活范围 | 只在敏感页面 | 全局所有页面 |
| 用户感知 | 自然,无感 | 突然出现蒙层 |
| 退出处理 | 切走即取消 | 需要手动关闭 |
| 内容处理 | 隐藏敏感内容 | 遮住整个屏幕 |
| 系统提示 | 不打扰用户 | 弹窗提醒"已开启防窥" |
食界探味当前的做法是"进入收藏页自动激活,离开自动取消",用户完全无感。这是最好的体验。
八、如果要扩展到更多页面
如果以后要在更多页面接入防窥,建议:
-
按敏感度分级 --- 收藏页 > AI 对话 > 个人资料 > 其他
-
渐进式扩展 --- 先收藏页,验证效果后再扩展
-
保持按需激活 --- 永远不要全局常驻
-
页面级控制 --- 每个页面自己决定是否需要防窥
关键代码位置
| 文件 | 作用 |
|---|---|
app/lib/core/platform/anti_peep_protection_channel.dart |
防窥通道 |
app/lib/app.dart |
页面级激活/取消防窥 |
app/ohos/entry/src/main/ets/plugins/AntiPeepProtectionPlugin.ets |
鸿蒙原生插件 |
常见坑
-
为了展示能力,对所有页面全局开启 --- 体验断裂,评委质疑
-
只从比赛展示角度判断,不从用户场景判断 --- 用户不需要看菜谱时防窥
-
页面敏感度其实不高,却引入了额外交互打扰 --- 公共内容不需要防窥
-
没有退出页面后的关闭逻辑 --- 防窥残留,影响后续页面
-
没有检查用户是否已登录 --- 未登录时没有个人数据,不需要防窥
-
防窥和页面动画冲突 --- 蒙层出现时页面正在转场,体验差
可复用模板
页面防窥判断模板
判断页面是否需要防窥:
□ 内容是否带有用户偏好/个人数据?
□ 被旁人看到是否造成真实困扰?
□ 开启后是否打断主要操作?
□ 用户是否有明确的隐私预期?
4 个"是"→ 适合
3 个"是"→ 考虑
2 个及以下 → 不适合
页面级防窥激活模板
class SensitivePage extends StatefulWidget {
@override
State<SensitivePage> createState() => _SensitivePageState();
}
class _SensitivePageState extends State<SensitivePage> {
@override
void initState() {
super.initState();
AntiPeepProtectionChannel.activateCollectionProtection();
}
@override
void dispose() {
AntiPeepProtectionChannel.deactivateCollectionProtection();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<AntiPeepVisibilityState>(
valueListenable: AntiPeepProtectionChannel.visibilityState,
builder: (context, state, child) {
if (state == AntiPeepVisibilityState.hidden) {
return const SafePlaceholder();
}
return child!;
},
child: const SensitiveContent(),
);
}
}
Tab 级防窥激活模板
// 根据当前 Tab 决定是否激活
void syncProtection(int currentIndex) {
final shouldProtect = currentIndex == SENSITIVE_TAB_INDEX;
if (shouldProtect) {
AntiPeepProtectionChannel.activateCollectionProtection();
} else {
AntiPeepProtectionChannel.deactivateCollectionProtection();
}
}
本篇总结
防窥能力应该按页面价值接入,而不是按"能接就接"。核心判断标准:
-
内容敏感度 --- 是否带有用户偏好和个人数据
-
暴露后果 --- 被旁人看到是否造成困扰
-
体验影响 --- 开启后是否打断主要操作
食界探味当前只在收藏页激活防窥,是因为收藏页是整个 app 中个人数据最集中、隐私敏感度最高的页面。探索页、搜索页、详情页都是公共内容,不需要防窥。
对系统能力做业务筛选,本身就是成熟项目的一部分。"能接"不代表"该接",选择在哪里接、不在哪里接,体现的是产品判断力。
