为了方便,文中部分使用sp表示SharedPreferences或者SharedPreferences的实现类。
SharedPreferences是个接口,SharedPreferencesImpl是SharedPreferences真正的实现。
final class SharedPreferencesImpl implements SharedPreferences {
}
获取SharedPreferences实例的三种方法
- Activity的getPreferences方法
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
- PreferenceManager的getDefaultSharedPreferences方法
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
- Context的getSharedPreferences方法
public abstract SharedPreferences getSharedPreferences(String name, int mode);
1,2两种方法内部还是调用了Context的getSharedPreferences方法来获取SharedPreferences实例。Context类中的getSharedPreferences方法是抽象方法,真正的实现是在ContextImpl类中。
首先交代一下ContextImpl类中的比较重要的成员变量
//sp的名称和对应的文件路径的映射
private ArrayMap mSharedPrefsPaths;
//包名和所有(sp的路径和sp对象的映射)的映射
private static ArrayMap> sSharedPrefsCache;
注意sSharedPrefsCache
是一个静态变量。在sSharedPrefsCache
这个对象声明中,String
类型参数表示包名;ArrayMap
表示sp的路径和sp对象的映射。
ContextImpl的getSharedPreferences(String name, int mode)方法
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// 如果targetSdkVersion小于19,如果传入的name为null,则将name赋值为"null"
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
//1. 如果mSharedPrefsPaths为null,则初始化mSharedPrefsPaths。
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//2. 从mSharedPrefsPaths中根据preference的名称获取对应的文件路径
file = mSharedPrefsPaths.get(name);
if (file == null) {
//3.如果文件路径不存在,则新建一个文件路径
file = getSharedPreferencesPath(name);
//加入到mSharedPrefsPaths缓存
mSharedPrefsPaths.put(name, file);
}
}
//4. 返回SharedPreferences实例
return getSharedPreferences(file, mode);
}
- 如果mSharedPrefsPaths为null,则初始化mSharedPrefsPaths。
3.如果文件路径不存在,则新建一个文件路径
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
生成的文件路径是/data/data/包名/shared_prefs/name.xml
ContextImpl的getSharedPreferences(File file, int mode)方法
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//5.获取当前包名下所有的SharedPreferences缓存
final ArrayMap cache = getSharedPreferencesCacheLocked();
//根据路径获取缓存的sp对象
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
//...
//6.如果路径没有对应的sp对象,则根据路径和mode构建一个新的sp对象
sp = new SharedPreferencesImpl(file, mode);
//加入缓存
cache.put(file, sp);
//返回新建的sp对象
return sp;
}
}
//...
//缓存中存在路径对应的sp对象,直接返回
return sp;
}
5.ContextImpl的getSharedPreferencesCacheLocked方法
private ArrayMap getSharedPreferencesCacheLocked() {
//7. 如果sSharedPrefsCache为null,则先初始化
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
//获取包名
final String packageName = getPackageName();
//获取包名对应的所有(sp的路径和sp对象的映射)的映射
ArrayMap packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
//如果当前包下还没有sp的路径和sp对象的映射,则新建一个sp的路径和sp对象的映射加入sSharedPrefsCache。
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
//返回packagePrefs对象
return packagePrefs;
}
6.如果路径没有对应的sp对象,则根据路径和mode构建一个新的sp对象
//SharedPreferencesImpl的构造函数
SharedPreferencesImpl(File file, int mode) {
//要获取的sp对应的文件路径
mFile = file;
//构建一个备份文件
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
//用来存储从sp文件读入的键值对
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
sp中几个成员变量
//sp对应的文件
private final File mFile;
//备份文件
private final File mBackupFile;
//获取sp的模式
private final int mMode;
//读锁
private final Object mLock = new Object();
//写锁,当把内存中的键值对写如到文件的时候需要用到,因为可能有多个写操作,加锁来保证线程同步。
private final Object mWritingToDiskLock = new Object();
SharedPreferencesImpl的makeBackupFile方法,构建一个备份文件
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
SharedPreferencesImpl的startLoadFromDisk,从磁盘加载sp文件
private void startLoadFromDisk() {
//获取所mLock,同步代码块执行完毕后,释放锁
synchronized (mLock) {
//是否加载过了,置为false
mLoaded = false;
}
//在新的线程加载
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
//获取锁
synchronized (mLock) {
if (mLoaded) {
return;
}
//如果备份文件存在,则把sp文件删除,并把备份文件重命名为sp文件的名字
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
//...
//存储sp文件中所有的键值对
Map map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
//如果文件可读,则读入所有的键值对,存储到map中
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//读取并解析文件
map = (Map) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
//...
try {
if (thrown == null) {
if (map != null) {
//如果map不为null,将mMap指向map
mMap = map;
//...
} else {
//为mMap赋值
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
//唤醒其他等待mLock的线程
mLock.notifyAll();
}
}
}
经过上面一系列操作,我们已经获取了一个sp对象。并把sp文件中的所有键值对都存在了mMap中了。现在我们可以向sp中读写数据了。
从sp中读取数据
@Override
public String getString(String key, String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
读取数据很简单,就是根据key值从mMap中查找对应的value。
向sp中写数据
向sp中写数据是通过SharedPreferences.Editor对象来完成的。
private SharedPreferences.Editor editor;
editor = sharedPreferences.edit();
Editor是SharedPreferences类的一个内部接口,Editor的真正实现是EditorImpl,EditorImpl是SharedPreferencesImpl的一个内部类。这个对应关系很清晰。
SharedPreferencesImpl的edit方法
@Override
public Editor edit() {
//等待从磁盘加载sp文件完成
synchronized (mLock) {
awaitLoadedLocked();
}
//返回EditorImpl对象
return new EditorImpl();
}
EditorImpl类中几个成员变量
//锁对象
private final Object mEditorLock = new Object();
//用来存放被新增的键值对
private final Map mModified = new HashMap<>();
EditorImpl的putString方法
@Override
public Editor putString(String key, @Nullable String value) {
//先获取锁,然后将数据存入mModified对象
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
其他写入数据的方法都是类似,不再赘述。我们可以看到我们调用putXXX方法的时候,只是把数据存入了mModified对象,并没有真正的写入磁盘,真正写入磁盘的操作是调用commit或者apply方法的时候。
在看commit方法和apply方法之前,我们先看一下remove方法,这个方法感觉有点意思。
@Override
public Editor remove(String key) {
synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
删除key对应的value的时候,是用当前EditorImpl对象替代了旧的value。
接下来看一下EditorImpl的commit方法
EditorImpl的commit方法
@Override
public boolean commit() {
//...
//获取要提交的信息
MemoryCommitResult mcr = commitToMemory();
//将要提交的信息加入写队列
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null );
try {
//等待写出是否成功
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
//...
//返回写出是否成功
return mcr.writeToDiskResult;
}
EditorImpl的commitToMemory方法
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List keysModified = null;
Set listeners = null;
//要写入磁盘的所有键值对
Map mapToWriteToDisk;
//先获取读锁
synchronized (SharedPreferencesImpl.this.mLock) {
// mDiskWritesInFlight > 0表示有正在执行写操作的任务
if (mDiskWritesInFlight > 0) {
//如果有正在执行写操作的任务,我们不能修改mMap,所以我们拷贝一份
mMap = new HashMap(mMap);
}
//指向mMap
mapToWriteToDisk = mMap;
//增加正在执行写任务的数量
mDiskWritesInFlight++;
//...
synchronized (mEditorLock) {
boolean changesMade = false;
//如果是clear操作,就清空mapToWriteToDisk
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
//遍历mModified,看看那些键值对被修改过了
for (Map.Entry e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
//如果要加入的value是EditorImpl对象本身或者null,
//说明我们是要删除key对应的value。
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
//删除删除key对应的value
mapToWriteToDisk.remove(k);
} else {
if (mapToWriteToDisk.containsKey(k)) {
//新的value和原来的sp文件中的value值相等,不需要改变
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
//向mapToWriteToDisk中加入新的键值对,或者更新key对应的value
mapToWriteToDisk.put(k, v);
}
//标记sp发生了变化
changesMade = true;
//...
}
//所有数据加入到mapToWriteToDisk以后清空mModified
mModified.clear();
if (changesMade) {//发生变化以后,将当前内存状态版本号加1
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
//构建一个MemoryCommitResult对象返回
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
获取到MemoryCommitResult对象以后调用SharedPreferencesImpl的enqueueDiskWrite方法
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//是否是同步提交,postWriteRunnable == null 就是同步提交
final boolean isFromSyncCommit = (postWriteRunnable == null);
//构建一个Runnable
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//向磁盘写数据
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//如果是commit方法就直接在当前线程写出数据
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//注释1处,如果当前只有我们自己一个写任务,就直接向磁盘写数据
writeToDiskRunnable.run();
return;
}
}
//注释2处,将写任务加入队列
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
注意:注释1处,有点疑问,如果wasEmpty为false的话,那么commit方法调用也会走到注释2处,那不就成异步的了吗?会有问题吗?目前还每整明白。
感觉正常逻辑下,wasEmpty是true。由于mLock
锁的原因。
SharedPreferencesImpl的writeToFile方法
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
//...
boolean fileExists = mFile.exists();
//...
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
//...
boolean backupFileExists = mBackupFile.exists();
//...
//如果备份文件不存在
if (!backupFileExists) {
//把mFile重命名为mBackupFile,路径结尾是.bak
if (!mFile.renameTo(mBackupFile)) {
//如果重命名失败,通知写出失败
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
//尝试写出文件,删除备份并尽可能以原子方式返回true。
//如果有任何异常,删除新的文件,下一次我们会从备份文件中恢复。
try {
FileOutputStream str = createFileOutputStream(mFile);
//...
//写出文件
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
//...
//关闭文件
str.close();
//根据sp的mode,设置文件的权限。
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
//...
//写出成功,如果有备份文件的话,就删除
mBackupFile.delete();
//...
//写出成功,调用MemoryCommitResult的setDiskWriteResult方法,唤醒等待的线程
mcr.setDiskWriteResult(true, true);
//...
//写出成功以后就返回
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 写出失败,清除失败的文件
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
//唤醒等待的线程
mcr.setDiskWriteResult(false, false);
}
EditorImpl的apply方法
@Override
public void apply() {
//获取MemoryCommitResult和commit方法中一样的
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//等待写出成功
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
//...
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//调用SharedPreferencesImpl的enqueueDiskWrite方法
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
//...
}
SharedPreferencesImpl的enqueueDiskWrite方法
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//现在是异步,isFromSyncCommit是false
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//写出数据
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
//调用apply方法的时候传入的postWriteRunnable不为null
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//...
//调用QueuedWork的queue方法,传入的第二个参数是true
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
调用QueuedWork的queue方法
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
//把传入的Runnable加入sWork
sWork.add(work);
//此条件满足,延迟发送消息
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
QueuedWork的getHandler方法返回的是一个QueuedWorkHandler对象
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
//构建HandlerThread是为了获取一个Looper
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
QueuedWorkHandler类
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
QueuedWorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
//处理消息
processPendingWork();
}
}
}
QueuedWorkHandler的processPendingWork方法
private static void processPendingWork() {
synchronized (sProcessingWork) {
LinkedList work;
synchronized (sLock) {
work = (LinkedList) sWork.clone();
sWork.clear();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
for (Runnable w : work) {
//调用Runnable对象的run方法,最终会调用SharedPreferencesImpl的writeToFile方法
w.run();
}
}
}
}
EditorImpl的apply方法和commit方法本质上是一样的,不同之处在于:
- apply方法内部是在新的线程中写出数据的,而commit方法是在当前线程写出数据的。也就是说如果我们在主线程调用commit方法,可能会造成线程阻塞。
- commit方法是有返回值的,我们可以根据返回值知道写出是否成功,而apply方法是没有返回值的。
参考链接:
- Android SharedPreferences的理解与使用