安卓面试题(15/30)SharedPreferences
牛客高级系列专栏:
安卓(安卓系统开发也要掌握)
- 想通关安卓面试,请看(承诺免费售后答疑):《150道安卓高频面试题目录及答案链接》
- 想通关安卓系统面试,请看:《140道安卓系统Framework面试题目录及答案链接》
- 想进阶安卓开发,请看(承诺免费售后答疑):《Android进阶知识体系解析_15大安卓进阶必备知识点》
- 想了解安卓APP完整开发流程,请看(承诺免费售后答疑):《安卓APP完整开发流程》
- 想掌握安卓App性能优化,请看(承诺免费售后答疑):《安卓性能优化讲解和实战专栏》
- 想掌握Gradle语法和配置,制作Gradle插件,请看(承诺免费售后答疑):《安卓Gradle语法解析和实践大全》
嵌入式
- 想通关嵌入式面试,请看: 《111道嵌入式面试题目录及答案链接》
- 想多掌握几个嵌入式项目,请看:《6个嵌入式项目交流分享(附源码)》
本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;
网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,承诺提供专栏内容免费技术答疑,直接咨询即可。助您提高安卓面试准备效率,为您面试保驾护航!
正文开始⬇
永久化存储是日常开发中常用的知识点,除了用数据库存储,很多开发者会使用SharedPreferences进行永久化存储,因此SharedPreferences也是一个考点,面试官可能会问:
- SharedPreference是线程安全的吗?⭐⭐⭐
- SharedPreference的apply和commit的区别?commit 一定会在主线程操作嘛?⭐⭐⭐⭐⭐
- SharedPreferences 是如何初始化的,它会阻塞主线程吗?⭐⭐⭐
- 每次获取 SP 对象真的会很慢吗?⭐⭐⭐
- 在使用时需要注意哪些问题,以及有什么优化点呢?⭐⭐⭐⭐
看完以下的解析,一定可以让面试官眼前一亮。
目录
- 1、SharedPreferences 是什么?怎么用?
- 2、源码分析
- 2.1 获取SharedPreferencesImpl实例
- 2.2 SharedPreferencesImpl实例分析
- 2.3 commit和apply
- 3、总结
1、SharedPreferences 是什么?怎么用?
从文章开头就知道SharedPreferences是Android用于永久性存储的一种方式,顺便复习下Android的永久性存储方式有哪些:
- File
- SharedPreferences
- SQlite
- 网络
- ContentProvider
SharedPreferences是基于key-value 键值对生成的一个xml文件,保存在/data/data/packageName/shared_prefs目录下,适合保存少量数据,且数据格式相对简单。
SharedPreferences sharedPreferences = context.getSharedPreferences(“xurui”, Context.MODE_PRIVATE); //1
SharedPreferences.Editor editor = sharedPreferences.edit(); //2
editor.putString("key", "value"); //3
editor.apply(); //4
//使用
String value = sharedPreferences.getString("key", "defaultValue"); //5
SharedPreferences本身是一个接口,可以通过getSharedPreferences()获取实例,原型是:
SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
其中name是最终生成的xml文件的文件名,mode代表不同的存储方式,查看源码有:
@IntDef(flag = true, prefix = { "MODE_" }, value = {
MODE_PRIVATE,
MODE_WORLD_READABLE,
MODE_WORLD_WRITEABLE,
MODE_MULTI_PROCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PreferencesMode {}
mode分成以下5种类型:
- MODE_PRIVATE:表示该 SharedPreferences数据只能被本应用读写,或者有相同用户ID的应用读写;
- MODE_WORLD_READABLE:全局读,允许所有其他应用程序对创建的文件具有读访问权限,该模式官方强烈建议不使用,因为会造成安全问题;
- MODE_WORLD_WRITEABLE:全局写,允许所有其他应用程序对创建的文件有写访问权限,官方同样建议不使用;
- MODE_MULTI_PROCESS:可以实现多进程访问SharedPreferences数据的问题,但是这种方式的多进程共享数据可能会出现数据不一致的问题,也不可靠,现在也不使用了;
- MODE_APPED:该模式不在规定的PreferencesMode模式里,但也可以使用,如果文件存在且对应的key也存在,则可以在对应的value数值追加新的内容,不同于MODE_PRIVATE,MODE_PRIVATE会把旧的内容覆盖掉。
接着,获取到的SharedPreferences实例本身仅支持获取数据,如果需要修改或者储存,需要通过SharedPreferences.Editor来实现,同样Editor也是接口,可以通过sharedPreferences.edit()获取实例,并通过putString修改内容,最后执行apply()即可(apply()下面会分析)。如此我们就完成了一对key-value 键值对的存放,到/data/data/packageName/shared_prefs下就可以找到一个叫“xurui.xml"的文件,里面就存放着刚添加的键值对。
2、源码分析
2.1 获取SharedPreferencesImpl实例
源码分析的思路,按上一节使用案例来分析: 首先看注释1:context.getSharedPreferences(“xurui”, Context.MODE_PRIVATE),查看源码发现该函数在/frameworks/base/core/java/android/app/ContextImpl.java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) { //6
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>(); //7
}
file = mSharedPrefsPaths.get(name); //8
if (file == null) { //9
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode); //10
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); //11
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
&& !getSystemService(UserManager.class)
.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
}
sp = new SharedPreferencesImpl(file, mode); //12
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>(); //13
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName); //14
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
上面总共有3个函数,环环相扣,context.getSharedPreferences(“xurui”, Context.MODE_PRIVATE)对应注释6.如上所述,这里“xurui”是指一个xml文件的名字,注释7的mSharedPrefsPaths是一个Map数据,其中key对应文件名称,value则是对应的xml文件。如果输入的文件名存在,则获取出对应文件,如注释8.如果文件不存在则创建并保存,对应注释9.最后获取到的文件传入注释10,也就是上面的第2个函数。
进入函数2,看看注释11,这一行需要重点理,其中:
- File:xml文件名;
- SharedPreferencesImpl:xml文件名在内存对应的实例,源码并非简单的对xml文件进行读写,而是每个文件会创建专门的实例来执行各种操作和判断;
所以我们看看getSharedPreferencesCacheLocked()做了什么,这也就是上面的第3个函数。首先会创建sSharedPrefsCache,这也是一个Map,其key是包名,value是packagePrefs,对应注释14,其中:
- sSharedPrefsCache:定义如下,也是在ContextImpl.java中定义,因为一个进程只有一个ContextImpl对象,一个ContextImp只定义一个sSharedPrefsCache,所以一个进程也就只有一个sSharedPrefsCache;
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
- packageName:一个进程只有一个sSharedPrefsCache,但是可能有多个应用,因此需要用包名做区分;
- SharedPreferencesImpl:xml文件名在内存对应的实例,源码并非简单的对xml文件进行读写,而是每个文件会创建专门的实例来执行各种操作和判断;
- packagePrefs: SharedPreferences 文件与 SharedPreferences 实例对象之间的映射关系;
如果getSharedPreferencesCacheLocked()中找不到输入文件名对应的SharedPreferencesImpl实例,则在注释12根据文件名和模式,新创建一个实例并返回。
综上,总结一下传入文件名获取SharedPreferencesImpl实例的过程:
首先sSharedPrefsCache会保存从磁盘加载到内
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
#提供免费售后答疑!!花一杯奶茶的钱获得安卓面试答疑服务,稳赚不赔# Android发展已经很多年,安卓资料网上千千万,本专栏免费提供专栏内容技术答疑!!私聊当天必回。在阅读过程或者其他安卓学习过程有疑问,都非常欢迎私聊交流。