Android提供了轻量级的数据存储方式,那就是SharePreference数据存储。其实质也就是文件存储,只不过是符合XML标准的文件存储而已,是Android中比较常用的简易型数据存储解决方案。下面简单分析下源码实现,源码都是一些独立的东西,实现也比较符合大家的编码习惯,相比其他模块源码比较简单一些
留个小问题:
1,这样存储基本类型数据有问题吗?
SharedPreferences sp = getSharedPreferences("sp_demo", Context.MODE_PRIVATE);
sp.edit().putString("name", "小张");
sp.edit().putInt("age", 11);
sp.edit().commit();
2,这样存储基本基本类型数据有什么不好的地方?
SharedPreferences sp = getSharedPreferences("sp_demo", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.editor();
editor.putString("name", "小张");
editor.commit();
//SharedPreferences.Editor editor = sp.editor();
editor.putInt("age", 11);
editor.commit();
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
(mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//字段存储在xml文件中,而xml文件是放在ArrayMap中
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
原来这货就是创建xml文件,结合getSharedPreferences(String name, int mode),那么可以理解在第一次存储数据放在某个xml时候,如果没有xml文件,去创建这个名称的xml文件,那么我们继续往下看如何获取SharedPreference对象
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
return sp;
}
要看sp对象尽然还要进一层,为啥这么艰难,nm
private ArrayMap getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
原来,这家伙是把File对象和SharedPreferencesImpl关联到一块了,存在一个ArrayMap中,那么我们小结一下,我们命名一个fileName的xml文件,那么会生成一个File,所以有了fileName-File对象关联在一块,紧接着,File--SharedPreferencesImpl关联在一块,那么我猜测最终所有的存储逻辑是在SharedPreferencesImpl这个实现类中实现,请继续看
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
//Editor编辑接口提供的方法概览
public interface Editor {
Editor putString(String key, String value);
Editor putStringSet(String key, Set values);
Editor putInt(String key, int value);
Editor putLong(String key, long value);
Editor putFloat(String key, float value);
Editor putBoolean(String key, boolean value);
Editor remove(String key);
Editor clear();
boolean commit();
void apply();
}
public final class EditorImpl implements Editor {
//mModified对的是一个String--Object的Map,其实就是存储所有put过来的key--value,放在内存中,内存中,内存中(重要事情说三遍)
//突然间我发现getXXX时候可以从mModified里面取,不用读取文件了,那么每一回存储文件都会调用sp.editor(),进而每次都会new EditorImpl(),所以有很多个 mModified这样的map,有这么一瞬间我觉得很坑爹,这里问题留在这
private final Map mModified = Maps.newHashMap();
private boolean mClear = false;
//举例分析存储boolean类型,其他的意淫一下
public Editor putBoolean(String key, boolean value) {
synchronized (mLock) {
//来一个我就存一个,来俩我存一对,存的越多,赚的越多
mModified.put(key, value);
return this;
}
}
public Editor remove(String key) {
synchronized (mLock) {
mModified.put(key, this);
return this;
}
}
public Editor clear() {
synchronized (mLock) {
mClear = true;
return this;
}
}
//你们要的commit在这
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;
} finally {
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
//写内存的这货在这,看他干啥了
private MemoryCommitResult commitToMemory() {
//从这货名字可以猜处它最后应该是要写到内存里
Map mapToWriteToDisk;
//这又出来个mMap,好乱,猜一下m开头,成员变量,没错,这个就是你以后每次getXXX(String key)时候要从内存中操作的map,你app所有存储的东西都在这个成员变量里
mapToWriteToDisk = mMap;//结果赋给这个马甲map,这个马甲map待会肯定要去变成xml文件内容存到手机里去
synchronized (mLock) {
boolean changesMade = false;
//mClear好眼熟,成员变量,还记得clear()方法里对mClear进行赋值吗?.....原来clear的操作在这终结
if (mClear) {
if (!mMap.isEmpty()) {
changesMade = true;
mMap.clear();
}
mClear = false;
}
for (Map.Entry e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
//map.remove应该是你调用了editor.remove()方法以后要进行最终结果map的remove,这里为啥是value == this就给remove了呢?
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
//大胸弟我终于看到我曾经跨过山河大海put的值,你现在终于放进这个mMap里了
mMap.put(k, v);
}
if (hasListeners) {
keysModified.add(k);
}
}
//本次commit临时存储用的map可以皈依佛门了
mModified.clear();
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
//我想要的东西都很贵,我想要去的地方都很远,不造一个包装数据结构的类怎么把他们打包带走
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
//小姐姐长啥样,一睹芳容
private static class MemoryCommitResult {
final long memoryStateGeneration;
@Nullable final List keysModified;
@Nullable final Set listeners;
final Map mapToWriteToDisk;
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, @Nullable List keysModified,
@Nullable Set listeners,
Map mapToWriteToDisk) {
this.memoryStateGeneration = memoryStateGeneration;
this.keysModified = keysModified;
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
//原来就放了几个成员变量,盛放下打包的物品,太丑陋了,这不是去小姐姐家的路
//存完内存中的map以后,俺们看看如何存到disk里
private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {
//原来这货根据runnabl == null就是comit,不是就是apply模式
final boolean isFromSyncCommit=(postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
//最终写文件到disk的操作在这
writeToFile(mcr,isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//确认过眼神,这是commit模式
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//commit模式下,当前只有一个写入任务,直接在当前线程write
writeToDiskRunnable.run();
return;
}
}
//apply模式 || 当前是commit模式,但是有很多个写入任务,原来这么坑爹commit在任务多的时候也会apply,为啥嘞?要是胡写乱调用commit,阻塞了UI线程那不就挂了(这是我猜测)
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
public void apply() {
//和commit一样,三七二十一先给它在内存里的mMap操作完再说,再处理打包回来的马甲map
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
//这个包装的数据结构的writtenToDiskLatch到底是啥勒,他是一个标志位,表示当前mac这个文件是否写入到disk完成,如果完成在继续下一个mc数据结构写入,那么这货到底是在什么时候赋值的呢? mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//这个上面commit已经分析过,runnable !=null,新线程写文件
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
到这里我们知道了为啥apply和commit一个异步一个同步了,以及我们getXX是从mMap这个成员变量中获取的了,那么万水千山还有一步,说了半天到底怎么从把mMap的马甲Map这个mWriteToDiskMap,所在的包装类MemoryCommitResult,这个破玩意如何变成xml,再把xml写入到disk里呢?最后一个机关来了
//我靠好长啊
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
//mFile是个成员变量,是当初传过来的File文件,判空是因为有地方会有mFile.delete()操作
boolean fileExists = mFile.exists();
if (fileExists) {
boolean needsWrite = false;
//mDiskStateGeneration是一个标志位,在构造数据包装结构MemoryCommitResult的时候赋值过
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
//把mMap的马甲Map结果集写入到mFile文件,mFile是啥,waht?,当初你在ContextImpl里getSharedPreference(String name,Mode mode)传入一个name,生成一个mFile,然后new SharedPreferenceImpl(File file,Mode mode),这个mFile就来了,如今将结果map写入到mFile里,木毛病
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
//将xml文件最终写入到disk里
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
mBackupFile.delete();
mDiskStateGeneration = mcr.memoryStateGeneration;
mcr.setDiskWriteResult(true, true); mSyncTimes.add(Long.valueOf(fsyncDuration).intValue());
mNumSync++;
return;
} catch (XmlPullParserException e) {
} catch (IOException e) {
}
if (mFile.exists()) {
if (!mFile.delete()) {
}
}
mcr.setDiskWriteResult(false, false);
}
1,SharedPreference保存数据的形式是xml文件,并且创建时不同的name对应不同的xml文件,本质是文件读写。
2,在SharedPreferences的Editor中如果用commit()方法提交数据,其过程是先把数据更新到内存,然后在当前线程中写文件操作,如果用的是apply()方法提交数据,首先也是写到内存,接着在一个新线程中异步写文件,注意:操作commit时有锁操作,所以效率很低一些,如果当我们一次有多个修改写操作时等都批量put完了再一次提交确认,这样可以提高效率
3,SharedPreferences在实例化时首先会从disk异步读文件,然后缓存在内存中,接下来的读操作都是内存缓存操作而不是文件操作。
4,由于键值对在内存中保存了一份,放在mMap中,用内存换取getXXX(String key)时候速度上的提高,所以如果存储大量数据在里面,那么这个map将会很大,内存上存在问题,所以回到题目上来,轻量级存储,不适合大而复杂的数据存储