【免费下载链接】Once
A small Android library to manage one-off operations.
Once 是一个管理 Android 一次性操作的轻量级开源库:引导页只弹一次、升级说明每个版本只提示一次、接口每小时最多上报一次。本文基于源码和官方示例,整理出 6 个最常见的错误与排查方法,帮你快速定位 Once 不生效或行为异常的问题。
快速上手:2步完成 Once 初始化
安装依赖(需mavenCentral()仓库,注意 1.3.1 起已迁移出 jcenter):
dependencies { implementation 'com.jonathanfinerty.once:once:1.3.1' }然后在 Application 类的onCreate()中初始化,并记得在 Manifest 中注册:
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); Once.initialise(this); } }参考官方示例 ExampleApplication.java 和 AndroidManifest.xml(android:name=".ExampleApplication")。
坑1:忘记调用 Once.initialise(),触发 NullPointerException ⚠️
initialise(this)必须在任何beenDone、markDone调用之前执行。它负责创建持久化存储并读取 App 更新时间:
public static void initialise(Context context) { tagLastSeenMap = new PersistedMap(context, "TagLastSeenMap"); toDoSet = new PersistedSet(context, "ToDoSet"); ... }源码见 Once.java。
- 现象:App 启动即崩溃,堆栈指向
tagLastSeenMap.get(tag)处的 NPE。 - 排查:确认初始化代码是否真的执行了——最常见原因是 Application 子类没在
AndroidManifest.xml中注册,onCreate()根本没被调用。
坑2:scope 选错——THIS_APP_INSTALL 与 THIS_APP_VERSION 混用
Once 提供三种作用域常量:
| 常量 | 含义 |
|---|---|
THIS_APP_INSTALL | 整个安装周期只执行一次 |
THIS_APP_VERSION | 每个 App 版本执行一次 |
THIS_APP_SESSION | 当前进程会话执行一次 |
最典型的错误:升级说明弹窗用了THIS_APP_INSTALL,导致用户升级后永远看不到新版本的 WhatsNew。正确写法见 README.md。
两个易踩的细节:
单参数
beenDone(tag)默认是THIS_APP_INSTALLscope,不是一般的"本次会话":// 实际等价于 beenDone(THIS_APP_INSTALL, tag, moreThan(0)) public static boolean beenDone(String tag)见 Once.java。
THIS_APP_SESSION只存在于内存,进程被杀后自动清零,且该 scope 不能用于toDo(scope, tag)的持久化判断。
坑3:tag 命名不规范——同一个操作变成两个"陌生人"
Once 的所有状态都由字符串 tag 索引,tag 拼错一个字母,检查与打标记就对不上了,表现为"每次都弹"或"从不弹"。官方示例的规范做法是把 tag 定义为常量:
private static final String SHOW_NEW_VERSION_DIALOG = "NewVersionDialog"; private static final String SHOW_MINUTE_DIALOG = "OncePerMinuteDialog";见 MainActivity.java。
- 排查技巧:全局搜索 tag 字符串,确认
markDone(xxx)与beenDone(xxx)用的是同一个常量。 - 避免两个不相关的功能复用同一个 tag——它们会共享完成状态。
- 建议命名风格统一(如驼峰 + 功能语义),
NewVersionDialog而不是v2、new。
坑4:static import 用错位置或漏导 Amount
README 推荐用 static import 精简代码:
import static jonathanfinerty.once.Amount.exactly; import static jonathanfinerty.once.Once.THIS_APP_INSTALL; import static jonathanfinerty.once.Once.beenDone; import static jonathanfinerty.once.Once.markDone;见 MainActivity.java。常见错误有三种:
- 漏导
Amount的方法:直接写exactly(3)会报cannot find symbol,因为它属于Amount类而非Once类(见 Amount.java)。 - 常量与方法混淆:
THIS_APP_VERSION是静态常量、beenDone是静态方法,两者都可以 static import,但别以为导入类Once就包含了Amount里的方法。 - 只导了一部分:导了
markDone却漏了beenDone,编译时才发现。要么全部 static import,要么统一用Once.xxx前缀,保持文件内一致。
坑5:markDone 时机错误——忘记调用或先标记后检查
Once 本身不做任何事,全靠你手动打标记。标准模式是"先检查,执行操作,最后 markDone":
if (!beenDone(Once.THIS_APP_VERSION, showWhatsNew)) { startActivity(new Intent(this, WhatsNewActivity.class)); Once.markDone(showWhatsNew); // 成功后再标记 }- 忘记
markDone:操作每次都执行,Once 形同虚设。 - 在
if外提前标记:判断和标记之间逻辑脱节,可能出现标记了但操作没执行。 - 另外,
markDone(tag)会自动把该 tag 从 to-do 集合中移除,所以toDo(tag)之后调用markDone(tag)即可完成任务闭环,不需要手动clearToDo。见 Once.java 和 ToDoTests.java。
坑6:exactly(n) 语义混淆——计数满 3 次后永远不再触发
想"用户用了 3 次核心功能后提示评分",很多人写成beenDone(tag, exactly(3))然后困惑为什么第 4 次点击没反应——exactly(3)表示"恰好等于 3 次",计数到 4、5 次时判断为false:
markDone("action"); if (beenDone("action", exactly(3))) { showRateTheAppDialog(); }对应源码return count -> numberOfTimes == count;(见 Amount.java)。测试用例 OnceTests.java 验证了该行为。
解决方案:
- 只需触发一次 → 改用
Amount.moreThan(2)(≥3 次); - 需要循环计数(每 3 次一次)→ 触发后调用
clearDone(tag)重置计数,官方示例即采用此做法:
String buttonPressedTag = "button pressed"; markDone(buttonPressedTag); if (beenDone(buttonPressedTag, exactly(3))) { showDialog("This dialog should only appear once every three presses"); clearDone(buttonPressedTag); // 重置计数,进入下一轮 }见 MainActivity.java。
附:调试与重置速查
| 需求 | API |
|---|---|
| 重置单个 tag | clearDone(tag)/clearToDo(tag) |
| 清空全部记录 | clearAll() |
| 查最近完成时间(未完成返回 null) | lastDone(tag) |
| 时间窗口限频 | beenDone(TimeUnit.HOURS, 1, tag) |
💡 调试时最省事的办法是示例 App 里的"Reset All"按钮:直接调用Once.clearAll()(见 MainActivity.java)。
Once 的 API 很小,出问题基本都出在初始化时机、scope 选择、tag 一致性、标记时机这四件事上。对照本清单逐项排查,绝大多数"Once 不工作"的现象都能迎刃而解。
【免费下载链接】Once
A small Android library to manage one-off operations.
相关推荐
Cloudflare TURN 避坑指南:常见错误、配额限制与故障排查实战
Cloudflare TURN 避坑指南:常见错误、配额限制与故障排查实战 本篇指南以 Cloudflare TURN 服务(Codex 技能库 cloudfl
人工智能AI 技能AI 插件JKeyboardPanelSwitch常见问题排查清单:20个开发者必知避坑技巧
JKeyboardPanelSwitch常见问题排查清单:20个开发者必知避坑技巧 Android开发中,键盘面板切换冲突是困扰众多开发者的常见痛点。JKeyb
如何快速在UE5中实现高质量高斯泼溅渲染:从零到精通的完整指南
如何快速在UE5中实现高质量高斯泼溅渲染:从零到精通的完整指南 你是不是也曾经被3D场景渲染的复杂性困扰过?想要在Unreal Engine 5中实现逼真的高斯
图形学3D渲染计算机视觉深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考