once
Once是一个开源的用于管理一些只需要进行一次(或几次)的操作的库。比如说只显示一次引导页,每个版本只显示一次更新说明等等。
使用方式
添加依赖
1 2 3 | dependencies { compile 'com.jonathanfinerty.once:once:1.2.2' } |
初始化
1 | Once.initialise(this); |
使用
检测某项操作是否已进行:
1 2 3 4 5 6 7 8 | String showWhatsNew = "showWhatsNewTag"; // 检测是否需要进行操作 if (!Once.beenDone(Once.THIS_APP_VERSION, showWhatsNew)) { startActivity(new intent(this, WhatsNewActivity.class)); // 完成后进行标记 Once.markDone(showWhatsNew); } |
Once支持3种尺度:
1 2 3 | THIS_APP_INSTALL: 安装到卸载前,例如引导页只出现一次,升级也不重复出现。 THIS_APP_VERSION: 当前版本,例如更新说明,一个版本出现一次。 THIS_APP_session: 本次使用。 |
此外,也支持如果时间,在设定的时间内进行一次操作。
1 | if (!Once.beenDone(TimeUnit.HOURS, 1, phonedHome) { ... } |
Once还支持将某件事标记为”to do”,之后检查是否需要做某项操作。Once给我们举了个例子,有时你想在用户看到基础功能后,在MainActivity中显示一些高级功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // in the basic functionality activity Once.toDo(Once.THIS_APP_INSTALL, "show feature onboarding"); ... // back in the home activity if (Once.needToDo(showAppTour)) { // do some operations ... // after task has been done, mark it as done as normal Once.markDone(showAppTour); } |
除了进行一次,Once也支持第N次后进行某些操作。例如,在用户使用3次App后弹出让用户评分的dialog。
1 2 3 4 5 6 7 8 9 10 | // Once again in the basic functionality activity Once.markDone("action"); if (Once.beenDone("action", Amount.exactly(3))) { showRateTheAppDialog(); } // 支持下面这三种 Amount.exactly(int x) // 第x次时 Amount.lessThan(int x) // 小于x次时 Amount.moreThan(int x) // 大于x次时 |
具体实现
Once的功能很简单,如果不使用Once而是自己实现的话,我们必然会用SharedPreferences去记录是否进行过某些操作,Once的实现也是类似的。
persistedMap和PersistedSet
这两个类是Once中保存和处理SharedPreferences。PersistedMap对应名为PersistedMapTagLastSeenMap的sp,用于保存那些被markDone的tag的时间戳,sp中保存的key为tag,value为时间戳拼成的字符串,以逗号分隔各时间戳,在PersistedMap中保存在Map<String, List<Long>> map
对象中,map的key为tag,List即为由时间戳字符串还原成的list。PersistedSet对应名为PersistedSetToDoSet的sp,用于保存被标记为todo的tag,Android3.0以上的版本直接已Set的形式保存在sp中,3.0以下以字符串形式保存,用逗号分隔。
PersistedMap和PersistedSet的实现类似,初始化时通过AsyncTask异步去加载各自的SharedPreferences,提供put、remove等方法,调用这些方法会更新内存中的Map/Set,并更新sp。
AsyncSharedPreferenceLoader
供PersistedMap和PersistedSet加载SharedPreferences使用。内部实现了一个AsyncTask用于异步加载SharedPreferences,并提供get()方法用于获取加载后的SharedPreferences。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private final AsyncTask<String, Void, SharedPreferences> asyncTask = new AsyncTask<String, Void, SharedPreferences>() { @Override protected SharedPreferences doInBackground(String... names) { return context.getSharedPreferences(names[0], Context.MODE_PRIVATE); } }; SharedPreferences get() { try { return asyncTask.get(); } catch (InterruptedException | executionException ignored) { return null; } } |
Once
Once的主类,提供了Once能实现的所有功能。这里只介绍一些主要方法的实现。
initialise
初始化方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public static void initialise(Context context) { // 初始化PersistedMap和PersistedSet tagLastSeenMap = new PersistedMap(context, "TagLastSeenMap"); toDoSet = new PersistedSet(context, "ToDoSet"); // sessionList用于保存处理THIS_APP_SESSION级别的tag。 if (sessionList == null) { sessionList = new ArrayList<>(); } // 获取lastAppUpdatedTime,用于处理THIS_APP_VERSION级别。 PackageManager packageManager = context.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); lastAppUpdatedTime = packageInfo.lastUpdateTime; } catch (PackageManager.NamenotfoundException ignored) { } } |
markDone
用于记录某个tag对应的操作的完成。会将当前时间戳增加进PersistedMap中,向sessionList中添加该tag,并从PersistedSet中将该tag移除。
1 2 3 4 5 | public static void markDone(String tag) { tagLastSeenMap.put(tag, new Date().getTime()); sessionList.add(tag); toDoSet.remove(tag); } |
beenDone
用于判断某个tag是否已完成。有一系列重载的方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // THIS_APP_INSTALL级别,该tag是否满足moreThan(0)。 beenDone(String tag); // THIS_APP_INSTALL级别,该tag是否满足传入的numberOfTimes条件。 beenDone(String tag, CountChecker numberOfTimes); // 自定义scope,该tag是否满足moreThan(0)。 beenDone(@Scope int scope, String tag); // 自定义scope,该tag是否满足传入的numberOfTimes条件。 beenDone(@Scope int scope, String tag, CountChecker numberOfTimes); // amount * timeUnit之前至今,该tag是否满足moreThan(0)。 beenDone(TimeUnit timeUnit, long amount, String tag); // amount * timeUnit之前至今,该tag是否满足传入的numberOfTimes条件。 beenDone(TimeUnit timeUnit, long amount, String tag, CountChecker numberOfTimes); // timespanInMillis之前至今,该tag是否满足moreThan(0)。 beenDone(long timeSpanInMillis, String tag); // timeSpanInMillis之前至今,该tag是否满足传入的numberOfTimes条件。 beenDone(long timeSpanInMillis, String tag, CountChecker numberOfTimes); |
前4个方法以scope为维度,最终都会调用到beenDone(@Scope int scope, String tag, CountChecker numberOfTimes)
,后4个方法以时间为维度,最终都会调用beenDone(long timeSpanInMillis, String tag, CountChecker numberOfTimes)
,我们来看一下这2个方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public static boolean beenDone(@Scope int scope, String tag, CountChecker numberOfTimes) { // 获取该tag所有的记录。 List<Long> tagSeenDates = tagLastSeenMap.get(tag); if (tagSeenDates.isempty()) { return false; } //noinspection SimplifiableIfstatement if (scope == THIS_APP_INSTALL) { // THIS_APP_INSTALL级别,直接和记录的size比较。 return numberOfTimes.check(tagSeenDates.size()); } else if (scope == THIS_APP_SESSION) { // THIS_APP_SESSION级别,和sessionList中该tag的个数比较。 int counter = 0; for (String tagFromList : sessionList) { if (tagFromList.equals(tag)) { counter++; } } return numberOfTimes.check(counter); } else { // THIS_APP_VERSION级别,记录中大于lastAppUpdatedTime的才计算进去。 int counter = 0; for (Long seenDate : tagSeenDates) { if (seenDate > lastAppUpdatedTime) { counter++; } } return numberOfTimes.check(counter); } } public static boolean beenDone(long timeSpanInMillis, String tag, CountChecker numberOfTimes) { List<Long> tagSeenDates = tagLastSeenMap.get(tag); if (tagSeenDates.isEmpty()) { return false; } int counter = 0; for (Long seenDate : tagSeenDates) { // 计算出最小的有效时间 long sinceSinceCheckTime = new Date().getTime() - timeSpanInMillis; // 比较记录的时间和有效时间,比有效时间大的才计入。 if (seenDate > sinceSinceCheckTime) { counter++; } } return numberOfTimes.check(counter); } |
toDo/needToDo
toDo用于标记某个tag “need to do”。有toDo(@Scope int scope, String tag)
和toDo(String tag)
2个方法。需要注意的是,使用toDo(@Scope int scope, String tag)
时,如果该tag之前被markDone过,则仅当传入的scope为THIS_APP_VERSION且该tag上次被markDone的时间在lastAppUpdatedTime之前才会将该tag添加到PersistedSet中。而toDo(String tag)
则无论该tag之前有没有被markDone,都会添加到PersistedSet中。
needToDo即用于检查PersistedSet中是否存在对应的tag。
总结
- Once可以帮我们方便地管理一些操作在什么时候进行。
- 利用SharedPreferences保存记录。
- 支持
THIS_APP_INSTALL
、THIS_APP_VERSION
、THIS_APP_SESSION
三种级别。利用每次markDone的时间戳实现对三种级别的支持。
相关阅读
绝大多数使用过 Windows 操作系统的用户都不会对注册表的 Run、RunOnce 键值感到陌生,但你真的了解所有这些键值的细节吗?让我
首先描述一下问题,我在使用require_once()函数时,自认为路径配置没问题,然而网页还是会出现找不到路径的错误。具体情况如下: 我的项
注册表 Run、RunOnce 键值解析绝大多数使用过 Windows 操作系统的用户都不会对注册表的 Run、RunOnce 键值感到陌生,但你真的了解
include 和 include_once 的区别 include 会将指定的文件载入并执行里面的程序;重复引用加载多次。include_once 函数会将指定的
Stream Processing: Apache Kafka的Exactly-once的定
2018年,Apache Kafka以一种特殊的设计和方法实现了强语义的exactly-once和事务性。热泪盈眶啊!这篇文章将讲解kafka中exactly-once