SharedPreferences深入探讨

前言:

Android的五大存储,在平常的开发中都是经常用到的点,分别为SharedPreferences存储、文件存储、数据库存储、网络存储、ContentProvider存储; 近几日在使用SharedPreferences进行存储时,使用一个不常用的api(putStringSet)时,却出现一奇葩现象:首次sp存储之后,重启应用是可以看到存储的数据,第二次存储之后重启应用,数据却并没有保存;一开始以为是代码的问题,之后科学上网却发现原来是这个api的坑.

I 探索入口

在源码中看到SharedPreferences只是一个接口,里面除了Editor接口还有一些get/set之类的方法,那么实现类在哪里呢? android系统通常会往接口实现类的类名后缀加”impl”,例如WindowManager的接口实现类为WindowManagerImpl,所以找SharedPreferencesImpl类,果不其然,真的是实现类。那就看看吧!

II 源码分析:

分析要点,简单记录

  • mMap == 存储的键值对数据

  • 可以看到get之类的方法,都是从mMap中通过Key取出值,存储的类型有float、int、string、long、Set、boolean 6种;

  • mModified 是在Editor里初始化的,可能是暂时缓存的区域;因为是在执行commitToMemory方法时,最后数据被清空;当创建EditorImpl对象时才被创建;

  • if (existingValue != null && existingValue.equals(v)) { //程序的关键点在这个地方,如果条件成立,那么将直接跳出当前循环,则不会存储;equals里比较的是对象地址和内容是否相同;

  • putStringSet可以看到是存进了mModified,在执行commit方法的时候会调用commitToMemory方法;接着看commitTpMemory方法,当我们在putStringSet前先执行clear,可以看到mClear被置为true,那么在commitToMemory方法里,就可以看到mMap.clear(),表明mMap里的数据将被清空;这个时候,数据被放置在mModified里,mMap里对应的的数据为空,所以上面的if条件不成立,mcr.changesMade(保存的标识)标志为true;

源码的探索追求点到为止,在上面已经大概知道StringSet第一次保存成功,第二次保持失败的原因,大致是这样的

看如下截图,当第一次添加数据时,可以看到是生成了新的HashSet(代号Set001),而在第一次并未对mMap做任何有关的操作,所以这时候的mMap数据为空,Set001和mMap内容并不一样,if条件不成立,保存成功.

Editor的putStringSet方法
《SharedPreferences深入探讨》

Editor提交到内存的commitToMemory方法(只截取了该方法部分实现)

《SharedPreferences深入探讨》

HashSet的equals方法

《SharedPreferences深入探讨》

下面是项目中第二次sp提交的代码,主要看划红圈的两部分,操作是先通过getStringSet方法获取之前存在sp里的值,可以在下方截图看到该方法返回的是mMap对象里存储的值v,即arraySet对象指向值v的地址,往arraySet添加完数据,这就相当于将数据也添加到了值v,在putStringSet时生成了新的HashSet,新的HashSet存储进mModified,此时再进行commit操作,在执行commit方法的时候会调用commitToMemory方法,在commitToMemory方法里,由于mModified和mMap对应的值的内容是相同的,导致了if条件成立,所以无法保存;

项目的代码(备注:对sp类进行工具类封装,所以getArraySet就是getStringSet方法的封装函数)
《SharedPreferences深入探讨》

SharedPreferencesImpl类的getStringSet方法
《SharedPreferences深入探讨》

III 解决方法

1.如下截图,可以在putStringSet方法执行之前或之后先进行clear操作,clear操作会将mClear标识置为true,在commitToMemory方法,可以看到会对mMap进行clear清空操作和设置文件保持的标识为true,即可成功保持

《SharedPreferences深入探讨》

《SharedPreferences深入探讨》

2.另一方法则是在getStringSet,重新生成另一对象,避免返回的对象指向mMap里的v值;

new HashSet<>(SharedPreferencesUtil.getInstance().getArraySet(Constant.COLLECT_URL, null));

总结:

  • 1.sp进行putStringSet操作的值不能是getStringSet返回的值,不然sp保存失败;
  • 2.sp进行putStringSet操作可先进行clear操作,或者put的Set对象是重新生成的,而不是从sp中get的;
  • 3.上面的分析只针对Set数据类型的保存,不包括其余的类型;

参考博文
http://blog.csdn.net/crazyman2010/article/details/51187817

附言:本人才疏学浅,欢迎多多讨论.

    原文作者:巨头之路
    原文地址: https://blog.csdn.net/yk377657321/article/details/53261209#t2
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞