Android性能优化之SharedPreferences

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

完整开源地址:https://docs.qq.com/doc/DSkNLaERkbnFoS0ZF

Android 存储选项之 SQLiteDatabase 创建过程源码分析

Android 存储选项之 SQLiteDatabase 源码分析

数据库连接池 SQLiteConnectionPool 源码分析

SQLiteDatabase 启用事务源码分析

SQLite 数据库 WAL 模式工作原理简介

SQLite 数据库锁机制与事务简介

SQLite 数据库优化那些事儿


前言

本文不是与大家一起探讨SharedPreferences的基本使用,而是结合源码的角度揭秘对SharedPreference使用不当引发的严重后果以及该如何正确使用。

SharedPreferences是Android平台上一个轻量级的存储辅助类,用来保存应用的一些常用配置,它提供了string,set,int,long,float,boolean六种数据类型。最终数据是以xml形式进行存储。在应用中通常做一些简单数据的持久化缓存。SharedPreferences作为一个轻量级存储,所以就限制了它的使用场景,如果对它使用不当将会带来严重的后果。

一、从源码的角度出发

1、SharedPreferences的创建过程

后面统一简称:Sp

通过Context的getSharedPreferences方法得到Sp对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里实际调用了ContextImpl的getSharedPreferences()。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从源码可以看到:首先在sSharedPrefs中获取Sp对象,那这个sSharedPrefs是个什么东西?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

sSharedPrefs实际是个Map对象,并且被声明为static final,这就意味着我们整个应用中只存在一个sSharedPrefs对象。如果第一次创建Sp对象此时肯定是获取到的是null,紧接着进入第一个if语句getSharedPrefsFile(name),参数想必大家都猜的到:就是我们创建Sp时传的的name,其实通过名字也可以看得出根据传递name创建一个File:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建name.xml文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跟踪到这里储存文件的创建我们就找到了。

紧接着new SharedPreferencesImpl(),看下SharedPreferencesImpl的构造方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实际上SharedPreferences只是个接口,而真正的实现是SharedPreferencesImpl,我们后续的get,put操作实际也是通过SharedPreferencesImpl对象完成的。

构造方法最后一行:startLoadFromDisk():

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从这里可以看出首先将mLoaded变量赋值为false,起到一个状态的变化作用,在后续我们会说到这个mLoaded变量很重要(其实主要多线程等待),然后开启一个线程loadFromDiskLocked():

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码稍微有点长,但是并不复杂。94行 - 105行都是做一些相关的检查。紧接着向下创建BufferedInputStream对象,将mFile作为参数,mFile还记得吗?它就是根据我们传递的name创建的文件。然后通过XmlUtils.readMapXml()将文件内容写入到map中并返回。在123行将mLoaded设置为true,代表已经将文件里的加载完成,存储在一个map中并且将其赋值给成员变量mMap:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

说道这想必大家已经明白:我们在Sp储存的数据会在本地生成一个.xml文件外,还会将该文件的数据缓存在一个map对象中。如果是第一次创建显然BufferedInputStream不会读取到任何数据,此时XmlUtils.readMapXml()解析返回自然为null,然后mMap = new HashMap();

然后再回到ContextImpl的getSharedPreferences方法最后:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果Sp已经存在了,会判断mode否等于Context.MODE_MULTI_PROCESS,然后如果API小于11:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

没错Context.MODE_MULTI_PROCESS仅仅是重新加载一遍数据到内存mMap,所以指望SharedPreferences实现跨进程通信可以死心了。

说到这,SharedPreference的创建过程就算是讲完了:getSharedPreferences实际返回SharedPrefenercesImpl对象,首先在sSharedPrefs容器中查找,如果未找到则创建Sp的对象并添加到sSharedPrefs。

2、put数据

通过上面的分析getSharedPreferences实际创建的是SharedPreferencesImpl对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时edit自然是调用的SharedPreferencesImpl的方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还记得我们之前提到的mLoaded变量吗:当我们第一次创建SharedPreferences时候,会将该变量置为false,然后开启线程将文件中的数据完成读取进map之后再将其置为true,读取文件的内容到map是在工作线程,此时edit方法是在主线程,如果此时工作线程读取时间过久,那edit方法将长时间处于等待状态。一旦超过5秒就会发生ANR危险

调用SharedPreferencesImpl的edit方法返回的是EditorImpl对象:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们一些列的put操作,还有clear,remove,apply,commit都是在EditorImpl对象中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从源码可以得知,我们一些列的put和remove之后是将数据添加进入mModifiled中,mModifiled是一个Map对象,其实从名字也可以看出代表为暂存的。clear仅修改mClear状态。执行操作之后必须要执行commit:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里需要注意的是:我们每次edit都会创建一个新的EditorImpl对象。接着跟踪commit操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

commitToMemory():

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码篇幅有些长,我们只关注重点部分:for循环这里,上面我们提到一系列的put和remove操作都添加进入mModified中,也就是mModified保留着我们当前的改变,通过遍历该容器,与mMap数据做一个比较,比如相同key但是value发生了变化此时修改mMap中的数据。然后mMap就是最后一次commit的数据。最后清空mModified容器。

方法的最后返回MemoryCommitResult,其实从名字也可以看出它的作用:标记本次提交的状态是否发生改变并将结果返回。

此时又回到commit方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调用enqueueDiskWirte:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

首先writeToDiskRunnable对象,在该对象的方法中执行写入文件操作(就是将最后一次提交之后mMap的数据写回到文件)。

接着向下:

由于commit方法的第二个参数Runnable传递null,故此时siFromSyncCommit为true,可以看到执行writeToDiskRunnable.run,直接在当前线程(UI线程)执行写入文件操作。此时return。

我们在修改数据之后除了选择commit提交之外,还可以使用apply进行提交:首先writeToDiskRunnable对象,在该对象的方法中执行写入文件操作(就是将最后一次提交之后mMap的数据写回到文件)。

使用apply进行提交:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此时siFromSyncCommit等于false,此时会执行enqueueDiskWrite方法的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

QueuedWork是一个线程池,而且只有一个核心线程,提交的任务到会加入到一个等待队列中按照顺序执行。

那么commit发生在UI线程而apply发生在工作线程。如果保证不阻塞UI线程我们使用apply来提交修改是否就绝对安全了呢?这里先告诉大家答案:绝对不是!!!!,后面会给大家继续分析。

接下来我们先来看下get操作。

3、get数据

我们看get操作做了哪些:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

也就是SharedPreferencesImpl的get操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其实通过上面的分析我们已经得到答案:通过SharedPreferenceImpl存储的数据都会在内存中保留一份mMap,这里也是直接在mMap中读取数据即可。

这里要着重说下awaitLoadedLocked方法,之前我们也提到过该方法主要是检查mLoaded变量状态:当我们第一次创建Sp对象时,它会开启一个工作线程将指定的文件中内容加载到mMap中,当加载完成改变mLoaed变量状态;否则awaitLoadedLocked方法会一直等待下去。这里涉及到一个优化点我们后续给大家总结。

二、apply一定安全吗?

上面我们提到过确认提交数据除了commit还可以apply,apply使写入文件操作发生在工作线程中,这样防止IO操作阻塞UI线程;这样真的就绝对安全吗?答案不是的。

我们要去跟踪另外一部分源码:
aed变量状态;否则awaitLoadedLocked方法会一直等待下去。这里涉及到一个优化点我们后续给大家总结。

二、apply一定安全吗?

上面我们提到过确认提交数据除了commit还可以apply,apply使写入文件操作发生在工作线程中,这样防止IO操作阻塞UI线程;这样真的就绝对安全吗?答案不是的。

我们要去跟踪另外一部分源码:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值