业务背景
项目中用到了fish-redux框架,state里用到了通过DateTime.now().toIso8601String()为列表里是每个State赋值uniqueId,这个id是要求唯一性。
了解fish-redux的应该知道在列表里reduce更新state的时候需要判断state的uniqueId是否相同,只有uniqueId相等才能认为是同1个state。
具体Demo可以参考fish-redux todo_compoent下的state实现
现象
项目列表使用了第三方瀑布流库flutter_staggered_grid_view ,结果陆陆续续有反馈在相邻的位置会刷到一模一样的卡片,类似于下图这样
原因:UniqueIdState类的uniqueId生成方法引用了DateTime.now().toIso8601String(),而flutter内部的这个方法在iPhone 11 Pro Max上会有重复现象,虽然通过下图源码看,生成的字符串已经精确到微秒级,但还是有概率生成两个一模一样的String,怀疑是因为微秒取的是前三位,不得不说iphone 11 pro max的cpu性能真好,android上就没出过这样的问题。
但通过加埋点日志看,数据是有重复的,埋点的截图如下,第三个和第四个卡片的uniqueId是一样的,注意看uuid的最后六位数200307,说明微秒的值已经写入进去了。
解决: 引入第三方库 Uuid
Uuid生成随机数的方法主要有两种
1. Uuid().v1(); //引用的也是时间戳,遂放弃
var clockSeq = (options['clockSeq'] != null) ? options['clockSeq'] : _clockSeq;
// UUID timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). Time is handled internally as 'msecs' (integer
// milliseconds) and 'nsecs' (100-nanoseconds offset from msecs) since unix
// epoch, 1970-01-01 00:00.
var mSecs = (options['mSecs'] != null) ? options['mSecs'] : (DateTime.now()).millisecondsSinceEpoch;//注意这里,引用的也是时间戳,遂放弃
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
var nSecs = (options['nSecs'] != null) ? options['nSecs'] : _lastNSecs + 1;
// Time since last uuid creation (in msecs)
var dt = (mSecs - _lastMSecs) + (nSecs - _lastNSecs) / 10000;
// Per 4.2.1.2, Bump clockseq on clock regression
if (dt < 0 && options['clockSeq'] == null) {
clockSeq = clockSeq + 1 & 0x3fff;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ((dt < 0 || mSecs > _lastMSecs) && options['nSecs'] == null) {
nSecs = 0;
}
// Per 4.2.1.2 Throw error if too many uuids are requested
if (nSecs >= 10000) {
throw Exception('uuid.v1(): Can\'t create more than 10M uuids/sec');
}
_lastMSecs = mSecs;
_lastNSecs = nSecs;
_clockSeq = clockSeq;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
mSecs += 12219292800000;
// 一堆位运算
return unparse(buf);
2. Uuid().v4(); 基于随机算法实现,最后用的v4
options = (options != null) ? options : Map<String, dynamic>();
// Use the built-in RNG or a custom provided RNG
var positionalArgs = (options['positionalArgs'] != null) ? options['positionalArgs'] : [];
var namedArgs =
(options['namedArgs'] != null) ? options['namedArgs'] as Map<Symbol, dynamic> : const <Symbol, dynamic>{};
var rng = (options['rng'] != null) ? Function.apply(options['rng'], positionalArgs, namedArgs) : _globalRNG();//随机生成了16个double
// Use provided values over RNG
var rnds = (options['random'] != null) ? options['random'] : rng;
// per 4.4, set bits for version and clockSeq high and reserved
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
return unparse(rnds);
_globalRNG()的默认实现
var rand, b = List<int>(16);
var _rand = (seed == -1) ? Random() : Random(seed);
//生成了16个随机的double
for (var i = 0; i < 16; i++) {
if ((i & 0x03) == 0) {
rand = (_rand.nextDouble() * 0x100000000).floor().toInt();
}
b[i] = rand >> ((i & 0x03) << 3) & 0xff;
}
return b;
既然要使用,当然要做下性能对比
总结 测试次数在1万到十万之间可以看到平均值比较稳定,可以作为实际的参考值。uuid的性能相对于DateTime.now().toIso8601String() 大概还是有20倍的差距,但考虑到业务列表的情况,最多不会一次性创建超过30个,所以可以使用v4不会有大的性能损耗。(ps 三星s7是16年的手机,iphone6是14年的机子,但从测试结果看,同样的代码iphone6比三星s7要快好多,苹果爸爸nb)