牛笔了!Android性能优化之SharedPreferences

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线程;这样真的就绝对安全吗?答案不是的。

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

首先Android四大组件的创建以及生命周期调用都是进程间通信完成的,到我们自己的进程中完成调度过渡任务的是ActivityThread,ActivityThread是我们应用进程的入口。来看下Actvity的onStop回调过程:

ActivityThread.java:

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

你没有看错又要等待,等待什么呢?

还记得我们确认提交数据使用apply操作将写入文件操作添加进线程池队列中吗?sPendingWorkFinishers就是SharedPreferencesImpl的enqueueDiskWirte方法的最后一行,当我们使用apply时就会执行如下添加到线程池中任务队列

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

QueuedWork.java:

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

假设我们apply非常多的任务。该线程池队列是串行执行,当我们关闭Activity时:会检查sPendingWorkFinishers队列中任务是否已经全部执行完成,否则一直等到全部执行完成。如果此时等待超过5s

由此得知 apply 也不是绝对安全的,试想当你 apply 提交较多的任务并且都是大型 key 或 value 时

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

三、结论

当我们首次创建 SharedPreferences 对象时,会根据文件名将文件下内容一次性加载到 mMap(SharedPreferencesImpl 成员) 容器中,每当我们 edit 都会创建一个新的 EditorImpl 对象,当修改或者添加数据时会将数据添加到 mModifiled (EditorImpl 成员)容器中,然后 commit 或 apply 操作比较 mMap 与 mModifiled 数据修正 mMap 中最后一次提交数据,然后写入到文件中。而 get 直接从 mMap 中读取。试想如果此时你存储了一些大型 key 或 value 它们会一直存储在内存中得不到释放。

四、正确使用的建议

1、不要存放大的 key 和 value 在 SharedPreferences 中,否则会一直存储在内存中得不到释放,内存使用过高会频发引发GC,导致界面丢帧甚至ANR。

2、不相关的配置选项最好不要放在一起,单个文件越大读取速度则越慢。

3、读取频繁的 key 和不频繁的 key 尽量不要放在一起(如果整个文件本身就较小则忽略,为了这点性能添加维护得不偿失)。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

推荐学习资料

  • Android进阶学习全套手册

  • Android对标阿里P7学习视频

  • BAT TMD大厂Android高频面试题

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

[外链图片转存中…(img-LhTvxfKk-1713685708659)]

  • BAT TMD大厂Android高频面试题

[外链图片转存中…(img-zcjUVPPa-1713685708660)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值