做者介绍:鲍协浩,小米MIUI部门, MIUI基础应用组通信录开发负责人html
在 Android 业务同步的逻辑代码中,使用到了 JSONObject 来解析服务端的 JSON 数据。同时本地由于业务新增需求的缘故,在本地数据库中使用 JSONObject 缓存了包括水位等同步相关的信息,其中,水位值是 Long 型。但近期发现同步过程当中下一次同步时,传递给服务器的水位并非上一次服务器返回的新水位,而是相差一些。以 301028292893495297L 为例,服务器返回这个水位以后,下次客户端上传的水位是 301028292893495296L,差值为 -1。数据库
经过反复排查代码逻辑,发现水位从服务端返回到下次请求之间,只通过了如下转换:segmentfault
认真阅读代码不难发现,Long 型的水位值保存在 JSON 对象中的时候转成了 String 型,而在读取的时候又看成是 Long 型来处理。所以会有精度缺失的问题,参见以下 JSONObject 的文档:缓存
因而可知,在读取 JSON 对象的某个值时,若是原先是 String 型,读取的时候看成是 Long 型,是会将 String 型经过 Double 进行解析的,因此在值超过 2^52 时会有精度缺失的问题。因而,遇到的问题就能够解释了。如下是 Double 的存储格式规范:服务器
其中,Double 和 Long 的精度测试代码很简单(输入参数能够提供例如 301028292893495297L 这样超过 2^52 的 long 值,会发现其返回值不为 0):测试
Double 和 Long 的精度测试代码很简单(输入参数能够提供例如 301028292893495297L 这样超过 2^52 的 long 值):spa
知道了问题的根源,修复就一目了然了,在水位保存在 JSONObject 对象中时,应该看成 Long 型而不是 String 型来保存;亦或者在读取的时候也看成是 String 型,而后经过 Long.valueOf 等接口进行解析。设计
另外,关于 JSON 对象中的值是 Long 型仍是 String 型,其实比较容易被忽略。若是JSON 对象在使用 String 表示的时候,该值对应处有引号就是 String 型。看以下的试用例就一目了然了:htm
相似的问题在网上随意一搜,其实有许多人遇坑了,好比这个。对象
因此,尽管不能说这个库的设计是很失败的,但确定不算是一个设计良好的库。由于你没法直接从 API 名称看出其内在的潜在逻辑,容易致使使用者使用不当。所以,经验教训就是:使用第三方库的时候,能看 API 文档就看 API 文档,切不可望文生义。固然,这个问题可能也仅限在 Android 中较老的代码模块,毕竟新的代码都会使用 GSON 等类库进行 JSON 对象操做,也就不容易出现这样的不易发现的问题了。
固然,单就这个问题来看,实际上是在新增业务逻辑的时候,没有正确使用 JSONObject 对象的接口,Long 型的值不该当当作是 String 型进行保存而又当成是 Long 型来读取,若是保存和读取的接口保持对应,也就不会出现问题了。无论怎么说,该问题的教训是在使用 JSONObject 相关接口时要倍加当心谨慎。
备注:Github 上最新的 JSON-Java 库没有这个问题,能够放心使用。
知道了问题的根源,修复就一目了然了,在水位保存在 JSON 对象中时,应该看成 Long 型而不是 String 型来保存;或者在读取的时候也看成是 String 型,而后经过 Long.valueOf 等接口进行解析。
问题后话
相似的问题在网上随意一搜,其实有许多人遇坑了,好比这个。因此,尽管不能说这个库的设计是很失败的,但确定不算是一个设计良好的库。由于你没法直接从 API 名称看出内在的潜在逻辑,致使使用不当。所以,经验教训就是:使用第三方库的时候,能看 API 文档就看 API 文档,切不可望文生义。
固然,Github 上最新的 JSON-Java 库是没有这个问题的。
小米开放平台重磅推出小米账号接入有礼活动:成功接入小米账号便可得到小米开放平台免费提供的平台资源(小米应用商店、小米卡包、小米推送vip、小米账号联盟等资源),机会不容错过,咱们期待您的加入!
活动报名地址:http://dev.xiaomi.com/console...
官方QQ交流群:398616987
想要了解更多?
那就关注咱们吧!