说在前面:SharedPreferences是Android中几种重要的存储数据的方式,Android开发不会没有人从来没有使用过,但是却很好人会关注它是怎么实现的,确实SharedPreferences实现起来比较简单,本质是基于文件存储,格式是XML形势的,本篇文章并没有太深的技术,但绝对是不可缺少的知识。
概述
SharedPreferences可以通过Context获取到,它是一个接口,实现在frameworks\base\core\java\android\app\SharedPreferencesImpl.java
源码也仅仅只有630行,相对于庞大的Android源码,小很多。但是你真的了解它吗?
问题
- 1、不同的Activity里面Context.getSharedPreferences()获取的SharedPreferences相同吗?
- 2、支持并发吗?
- 3、SharedPreferences.putXXX()直接保存了吗?
- ……
针对上面的问题,我们一点点来分析。
获得SharedPreferences
我们获得SharedPreferences是通过Context.getSharedPreferences(),源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { // 静态的 全局唯一,缓存得到的SharedPreferences,key是app包名 if (sSharedPrefs == null) { sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); } // 通过每一个app的包名缓存不同的SharedPreferences,key是文件名 // 这里就明白前面提到的第一个问题了,一个文件对应一个SharedPreferences final String packageName = getPackageName(); ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); sSharedPrefs.put(packageName, packagePrefs); } // 略... // 如果以创建过,直接从缓存中获取返回 sp = packagePrefs.get(name); if (sp == null) { // 如果没有创建一个 文件名为data/data/packege/shared_prefs/xxx.xml File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); // 创建实例对象 packagePrefs.put(name, sp); // 放到缓存中 return sp; } } // 略... return sp; } |
创建SharedPreferences
SharedPreferences的创建很简单,我们先看看构造方法:
1 2 3 4 5 6 7 8 9 | // 仅有一个 SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); // 创建备份 mMode = mode; mLoaded = false; mMap = null; // 存放键值对的map startLoadFromDisk(); // 从本地load出来 } |
从构造函数我们不难看出,SharedPreferences实际是以Map(HashMap)存储数据,创建的时候就从本地文件中读取数据到内存中,所以,不能存储太多的数据 ,否则内存会爆掉的哦。
读取本地文件中的数据
当实例化的时候就会触发,一个读取本地已存储的数据,
private void loadFromDiskLocked() {
// 读取过就不在读取
if (mLoaded) {
return;
}
//...
Map map = null;
StructStat stat = null;
try {
// 存储文件状态
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
// 格式为xml,解析xml得到map
map = XmlUtils.readMapXml(str);
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
}
// 加载过后更改状态,如果没有创建一个HashMap
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<String, Object>();
}
notifyAll();
}
获得存储的数据
得到数据通常是getXXX(),实际很简单,就是存map中取数据。
// 已getString()为例,其他一样
@Nullable
public String getString(String key, @Nullable String defValue) {
// 同步哦
synchronized (this) {
// 确保,已从文件中读取过数据
awaitLoadedLocked();
// 从map中的得到数据
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
保存数据
大家都知道保存数据是通过Editor来存储的,事实上也确实是这样的。但是putXXX()仅仅是保存到了map中,正真的是要在commit()或apply()之后。
public Editor putString(String key, @Nullable String value) {
// 同步哦
synchronized (this) {
mModified.put(key, value);
return this;
}
}
1、
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
// 写数据
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
2、
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
// 往文件中写入数据
writeToFile(mcr);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// ...
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
3、
// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
// ...
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
// 写入到文件中
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
// 更新文件的状态
final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false);
}
总结
1、SharedPreferences有缓存
2、数据是通过map来快速访问,所以不用担心文件IO耗时
3、支持多线程并发
4、本质是基于文件xml的形式