Android上常见的数据存储方式有哪些呢?

SharedPreferences这种存储数据的方式我们平时用的都对吗?

怎么使用SQLiteDatabase才是安全的?

带着这些问题,我们今天来深入分析一下SharedPreferences和database这两种Android上常见的数据持久化方式。

一、SharedPreferences

1、Preference和sharedPreferences是什么

Preference在Android上是首选项的意思,主要是指FrameWork上的各种UI组件,我们看一下Preference的各个子类:

preference的子类

它们一般用在PreferenceActivity中,当使用这些组件时,设置在组件中的数据会自动进行保存。
说的更加直白一些,Preference就是应用的设置界面。

SharedPreferences是用来存取Preference中设置的数据的,它是key-value键值对的形式存在,Android 3.0后又增加了StringSet的value形式,可以说SharedPreferences就是用来为Preference做数据持久化的。我们也来看看官方对它的说明:

SharedPreferences说明

从这个官方说明里我们注意到我们平时容易忽略的两点:(1)对于任何一类的preference(实际就是同一个preference name),SharedPreferences是唯一的;(2)SharedPreferences不支持多进程(这个我们接下来也会分析到)。

2、SharedPreferenced的内部实现

对于怎么使用SharedPreferences,我们就不多讨论了,这是Android最基本的一种数据存储方式了,如果你还不知道如何使用它,那你要保持低调了,不要让人知道你是一个Android的程序员,同时赶紧去找资料学习一下吧。

(1)数据存储格式

SharedPreferences的数据是以xml格式存储的;它的存储位置在我们应用程序私有文件目录下的shared_prefs中,每个preference_name会存储一个xml文件;同时,这些数据都是明文存储的,担心数据泄漏的,记得加密后再写入哦。

具体的文件存储目录是:/data/data/${packageName}/shared_prefs/

我们看一下SharedPreferences的get方法接口:

SharedPreferences get方法

从这里可以看出SharedPreferences只支持6种数据类型,分别是boolean,float,int,long,String和StringSet,基本StringSet还是在Android3.0后才加入。我们再来看看存储在xml中是什么样子:

pref文件中的形式

可以看出,xml中的标签也是对应的几个。

(2)数据载入和缓存

SharedPreferences会在第一次打开这个Preference时,会启动一个新进程将整个文件中的内容输入到内存中,并进行缓存,以后再用到这个SharedPreferences时,都使用这一个实例。

我们看一下代码,SharedPreferences只是一个接口,它的真实代码在SharedPreferencesImpl.java中。

context中获取sharedpreferences

获取到SharedPreferences只有这一种方法,即使用PreferenceManager获取,最终也是通过这里,也只有这样,才能在context类中缓存已经载入的SharedPreferences。

获取SharedPreferences的代码

第一个红框,我们看到,context中的静态成员sSharedPrefs用来缓存SP(以后用它来简写SharedPreferences),不过它的第一层key竟然是packageName,不知道是不是为了给插件留的兼容(纯属瞎猜的);第二个红框,我们看到,如果没有缓存,则会创建一个SP的实例SharedPreferencesImpl,并放入到缓存中。

加载SP数据的过程

启动新线程加载,可能主要是为了考虑它要读文件,但如果用到get方法或edit方法时,又是要等到新线程里把文件读完的,而我们平时使用时,很多时候拿到了SP,直接就要用的,或读或写,所以这个新线程起的不是特别必要,但毕竟给了我们预加载的一个SP的可能。
还要注意一点,SPImpl加载时,默认给的Buffer是16K,所以解析一个SP文件还是比较耗内存的。
还有一点,这里try catch中的异常,并不包括所有的异常,所以,如果这个pref文件出了其它问题,后果是很严重的,基本这个应用程序只能清除数据或重装了。
最后,读到的内容被放到了mMap中加以缓存,使用时可以直接从这个map中拿数据,所以,从SP的数据载入看,它为了提高性能,也是在内存中做了一个SP文件内容的映射。

(3)数据写入

SP的数据写入方式是,通过editor将要存储的数据同步或异步的写入内存,并将整个Pref的内容写回到文件中,我们从代码中一步一步来看:

先补一下SharedPreferencesImpl的源码,大家想看全部内容,可以直接访问:http://www.grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/5.0.0_r2-robolectric-1/android/app/SharedPreferencesImpl.java#SharedPreferencesImpl.%3Cinit%3E%28java.io.File%2Cint%29

获取editor

想了想,还是把edit()的代码贴了出来,因为这里有两个点需要注意和思考,一是,获取到editor之前,必须要等数据加载完成,这个Android的程序员也想把它移除掉,我们觉得可以吗?二是,大家再思考一个问题,SP为什么要通过editor去操作数据呢?为什么不像get方法一样,在SP中加几个put方法直接就搞定了,使用起来还非常的方便?

第一个问题,我觉得可以,毕竟editor只是将要写入数据的一个缓存,等真正去commit时再确认数据是否已经加载完成也是可以的。

第二个问题,我的看法是,SP就是为Preference设计的,用户在设置首选项时,会选择多个后,点击“应用”按钮,让他们一块儿生效,这也符合首选项的操作习惯,而Editor也为了这种场景而存在;同时,Editor还有一个好处,可以攒一堆数据后批量的进行一次写入,提升效率。

Editor Put数据

Editor中put的数据,都临时存放在自己的成员变量mModified中。

Editor的两种数据提交方式

Editor可以通过apply或commit提交数据,两者的区别是,apply是异步提交,不阻塞;commit是同步提交,会阻塞当前线程直到数据写入磁盘完成。注意,apply方法在Android 3.0及以后才支持。

apply方法

apply方法中,目前我们可以忽略绿色框的那些内容,只看红色部分,commitToMemory()方法将editor数据写入内存,enqueueDiskWrite方法将写操作放入队列,由另外的线程去做写入,没有任何阻塞就返回了,这也就是apply没有阻塞的原因。

注意,绿色部分看起来没有什么用处,实际上这个地方隐藏了一个大坑,后面讲到“主线程对SP的依赖”时,会再讲到这一点。

commit方法

我们再来看commit方法,它执行完成apply相同的两步后,开始进行wait,而这个writtenToDiskLatch实际上是一个countDownLatch,写完磁盘后会countDown,这时函数才能返回,所以,commit是阻塞的,尽量不要在主线程commit哦。

commitToMemory()方法

commitToMemory方法也是有不少看点的,首先,提交到内存后返回值是要携带不少信息的,所以这里返回一个对象MemoryCommitResult,以方便把这些信息带到write线程;其次,如果当前有其它editor也在等待写磁盘(mDiskWritesInFlight > 0,大家自己去源码中看这个标志变量的作用吧),则是要把SP中所有的键值对复制一份出来的,这也是一个数据同步的技巧,大家可以学习一下,但是,如果sp中键值对过多,这里复制一份出来是很占内存的,如果写线程阻塞严重,这里复制出来的份数更多,内存占用就更严重啦;三、后面写入内存时会做一定的判断,如果value==null,表示remove,如果value的值跟原值都一样,这个editor是不需要再执行写操作的,即changesMade=false,节省开销。

这里回想一下,我们在这个分析中提到了几个SP关于内存方面需要注意的点?

排队写入的实现

从后两个红框我们看以看出,postWriteRunnable为空,表示是commit过来的,此时会直接执行runnable.run,同步完成;如果非空,表示是apply方法过来的,此时会使用一个singleThreadExecutor这样的单线程池,顺次进行文件写入。

注意,这里的线程池是QueuedWork类中的,为什么不在SP内自己定义呢?为什么SP要跟QueuedWork搅来搅去呢?留着这个疑问吧,一会儿我们仍然在对"主线程的依赖"中揭晓。

写文件

最后再来看一下写入的过程吧,从代码可以看出,Android采取的是先备份,再写入的过程。如果本次写入失败,则删除写的内容,在下次加载时,会加载备份的文件,如果写入成功,则删除备份文件,不得不说,Android考虑的还是比较周全的。

(4)多进程操作SharedPreferences

从SP的官方说明看,SP是不支持多进程的,注意这里说的是进程,不是线程。但我们再看另外一个值

MULTI_PROCESS

这是context中的一个Mode值,从它的说明看,它是为多进程而生,从deprecated看,Android又放弃了它,建议我们有多进程,还是自己用contentProvider来解决吧。有点凌乱,这个值到底还起作用吗?我们还是去看源码吧。

这段代码来自SharedPreferencesImpl中的writeToFile方法

这个是方是SharedPreferencesImpl中唯一使用到mMode的地方,也就是我们从Context中获取SP时传入的mode,从使用来看,它只是设置Pref文件的读取权限,是私有,还是开放,跟Multi_process,没什么关系。

ContextImpl中getSharedPreferences方法中的代码

再看ContextImpl中获取SP时的代码,可以看出,这个mode的作用就是,标识Pref文件可能被其它进程改写了,你再重新reload一次得一下最新数据吧,同步的安全级太低了,难怪Android官方也不建议用了。

所以结论就是,SP不支持多进程。

3、主线程对SharedPreferenced的依赖

SP的apply提供了异步线程操作,那它不应该很安全吗,怎么又跟主线程扯上关系了呢?要弄清楚这个问题,我们还是先了解一下QueuedWork:

ActivityThread中对QueuedWork的调用

从ActivityThread的调用来看,它在处理一个Service启动时会调用到QueuedWork的waitToFinish方法,实际上,我们在ActivityThread代码中搜一下会发现,在pauseActivty,stopActivity中,都有对这个方法的调用,从而我们可以猜测,QueuedWork这个队列中存放的应该是一些关键事务,ActivityThread的looper中处理的很多事件要想继续进行,都必须等待这个队列中的关键事务执行完。

QueuedWork中的waitToFinish方法

从waitToFinish方法中可以看出,它果然是把所有队列中的Runnable拿出来顺次执行一下,更加残酷的是,它还是同步的,可见QueuedWork队列中的任务有多么的关键了。

好,我们再回过头我们SP的Editor中的apply方法:

apply方法中的QueuedWork

在apply时,Editor把commit完成的这个task放入到了QueuedWork队列中,在写入磁盘完成后才移除;从这里可以看出Android对SP的重视程度,SP不写完,Activity,Service的很多操作就必须要等待,而这些操作都在主线程,这也就是为什么apply仍然跟主线程有关系的原因了。

那为什么Android这么重视SP呢?我的猜想是,SP就是为Preference这个首选项而生的,首选项对全局的影响还是很大的,所以必须等待首选项存储完成。

4、sharedPreferences使用注意事项

SP中存放的是一个个的键值对,而我们平时的很多数据就是键值对,所以我们很自然的把它作为我们存储数据的默认方式,从上面的分析来看,这样做还是有不少弊端的,在讲这些弊端前,我们先看看Android官方对SP的使用建议:

Android官方建议

注意“较小”,如果我们存的数据量大,就不要用它啦。

我们来说说SP的弊端吧:

(1)读写速度慢,使用xml格式存储,解析效率本来就低,平时修改任何一个key,都要重写整个文件。

(2)明文存储,太不安全啦。

(3)内存占用高,读写文件内存占用高;高频并发写入时,不断的复制,临时内存会飙升。

(4)如果SP文件损坏,我们无法捕获异常,可能造成应用频繁崩溃。

(5)最最关键的,它会影响到主线程,造成卡顿,甚至造成anr哦。

建议:如果我们有很多值需要保存,改数据库吧,效率会高很多。

 

更多相关文章

  1. Android与Vue的交互的方法示例
  2. Android(安卓)setContentView方法解析(一)
  3. Android系统信息获取 之九:TelephonyManager类
  4. Android(安卓)Virtual Device Manager 创建虚拟机出现SDK Manage
  5. android如何去掉标题栏
  6. 浅谈Java中Collections.sort对List排序的两种方法
  7. mybatisplus的坑 insert标签insert into select无参数问题的解决
  8. python起点网月票榜字体反爬案例
  9. Python list sort方法的具体使用

随机推荐

  1. android 获取手机中的联系人
  2. 几个Android小错误解决方法
  3. 美图秀秀自由拼图android实现
  4. Android之RatingBar
  5. Android(安卓)Widget 小部件(三) 在Activ
  6. Error:Uncaught translation error: com.
  7. Android(安卓)用sp存储登录状态以及退出
  8. what is Android?
  9. android截取屏幕图
  10. Android架构知识