Android性能优化之SharedPreferences(1)

本文详细剖析了Android中SharedPreferences的数据存储机制,包括内存中的缓存、编辑器对象的生命周期、apply与commit的区别,以及get和put操作的工作原理。强调了大型key-value可能导致内存问题和潜在的性能风险,提供了正确使用SharedPreferences的建议。
摘要由CSDN通过智能技术生成

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

说道这想必大家已经明白:我们在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 尽量不要放在一起(如果整个文件本身就较小则忽略,为了这点性能添加维护得不偿失)。

4、不要每次都edit,因为每次都会创建一个新的EditorImpl对象,最好是批量处理统一提交。

否则 edit().commit 每次创建一个新的 EditorImpl 对象并且进行一次 I/O 操作,严重影响性能。

5、commit 发生在 UI 线程中,apply 发生在工作线程中,对于数据的提交最好是批量操作统一提交。虽然apply 发生在工作线程(不会因为IO阻塞UI线程)但是如果添加任务较多也有可能带来其他严重后果(参照ActivityThread源码中handleStopActivity方法实现)。

6、尽量不要存放 JSON 和 HTML,这种可以直接文件缓存。

7、不要指望这货能够跨进程通信 Context.PROCESS 。详情参考另外一篇《Android 之不要滥用 SharedPreferences(2)— 数据丢失

8、最好提前初始化 SharedPreferences,避免 SharedPreferences 第一次创建时读取文件线程未结束而出现等待情况。

最后该篇文章是基于较早的 Android  API Level 16 源码分析,如果想要了解最新(Level 28)请参考最新一篇文章的分析。

推荐阅读

Android 之不要滥用 SharedPreferences(2)— 数据丢失

Android 存储优化系列专题

其他系列专题

Android 之你真的了解 View.post() 原理吗?

深入 Activity 三部曲(1)View 绘制流程之 setContentView() 到底做了什么 ?

学习分享

性能优化也是面试必问知识点之一,最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的16套腾讯、字节跳动、阿里、百度等**2020面试真题解析**,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
复制石墨文档可见:https://shimo.im/docs/QdyGqGHXX8PyQ8pw

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

img

img

img

img

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

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

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

面试复习路线,梳理知识,提升储备

自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。

关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

  • 架构师筑基必备技能
  • Android高级UI与FrameWork源码
  • 360°全方面性能调优
  • 解读开源框架设计思想
  • NDK模块开发
  • 微信小程序
  • Hybrid 开发与Flutter

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结:

Android开发七大模块核心知识笔记

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

《507页Android开发相关源码解析》**

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值