在android开发中,SharedPreference属于比较常用的数据持久化方案之一,通常情况下我们会把那些隐私的而且属于轻量级的信息存储在SharedPreference里面,比如登录之后的用户信息。SharedPreference的存储其本质上是通过XML文件来进行存储,其XML文件放置的位置位于data/data/包名/shared_prefs中,属于内部存储的范畴。
SharedPreference的使用
SharedPreferences sharedPreferences = IApplication.getContext().getSharedPreferences("cache", 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(key, value);
editor.apply();
SharedPreference的使用比较简单,这里就不做过多讲解,下面我们就从源码的角度来探究SharedPreference的具体使用细节。
SharedPreference的源码分析
1》首先Context.getSharedPreferences()会进入到ContextImpl的getSharedPreferences方法中。
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// 如果当前手机版本小于4.4且设置的文件名为空的话默认文件 为null.xml
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
// 使用synchronized 关键字防止多线程导致的数据错误
synchronized (ContextImpl.class) {
// 判断存放文件的集合是否为空,为空则直接创建
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 通过文件名拿到对应的文件
file = mSharedPrefsPaths.get(name);
// 如果文件名为空则直接创建并添加到集合中
if (file == null) {
// getPreferencesDir() 拿到data/data/包名/shared_prefs文件
// makeFilename创建以传入的名称+“.xml”为后缀的文件
// 所以最终的file文件完整路径为data/data/包名/shared_prefs/文件名.xml
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
// 重点分析这个,看下文件到底是怎么操作的
return getSharedPreferences(file, mode);
}
// 生成XML文件并创建其File对象
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
// 返回/data/data/shared_prefs/目录的file对象
@UnsupportedAppUsage
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
SharedPreference最终的处理在这里
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
// 查看缓存里面有没有相同实例
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
// 没有实例先检查模式,如果模式是MODE_WORLD_READABLE
// 或者MODE_WORLD_WRITEABLE直接抛出异常
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");
}
}
// 创建SharedPreferencesImpl并添加到集合中
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
简单来说,ContextImpl使用sSharedPrefsCache集合保存了新创建的SharedPreferencesImpl对象,每次获取SharedPreferencesImpl对象之前,先查询sSharedPrefsCache集合,如果集合中有,则直接返回,如果没有,则新创建一个SharedPreferencesImpl对象,并加入到sSharedPrefsCache集合中。
2》SharedPreferences.Editor editor = sharedPreferences.edit();
根据上面的分析可知,最后返回的SharedPreferences其实是SharedPreferencesImpl实例,所以我们找到SharedPreferencesImpl的edit()方法。
@Override
public Editor edit() {
// 如果该线程获取到了mLock对象锁,但是mLoaded为false,也就是加载xml过程没结束,那么线程会一直等待。
synchronized (mLock) {
awaitLoadedLocked();
}
// 创建Editor的实例EditorImpl并返回
return new EditorImpl();
}
// 如果mLoad为false,线程会一直处于等待的状态
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
也就是说,在执行sharedPreferences.edit()方法的时候,首先会去判断加载xml的过程有没有结束,如果没有结束线程就会一直处于等待状态直到完成,最后返回EditorImpl实例。
3》editor.putInt(key, value);
@Override
public Editor putInt(String key, int value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
通过EditorImpl写入数据,写入的数据保存在Map集合里面。
4》editor.apply()
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
// 把内部类Editor的mModified集合中的数据保存到SharedPreferenceImpl类的mMap集合中。
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
// 把mMap集合中的数据写入到XML文件中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr); // 通过注册监听者进行刷新
}
// 把mMap集合中的数据写入到xml文件中
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 如果是apply()形式,则postWriteRunnable !=null,isFromSyncCommit 为true
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 写入磁盘XML
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
//如果是commit()的形式,且当前没有写磁盘任务(mDiskWritesInFlight == 1),则直接调用
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
// 执行writeToFile()写入操作,不起新线程
writeToDiskRunnable.run();
return;
}
}
//如果是apply()的形式,所有的线程都加入到QueuedWork中,以队列的形式保存,逐个线程启动
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
综上,SharedPreferencesImpl定义了ArrayMap集合,其对象初始化时,加载XML文件内容到ArrayMap集合中。SharedPreferencesImpl对外提供get()接口,返回其ArrayMap集合中的数据。Editor也定义了ArrayMap集合,对外提供put()接口,接受调用者传入的值并存入其ArrayMap中,在commit()或者apply()时,同步Editor和SharedPreferencesImpl中的ArrayMap值,把ArrayMap中的数据保存到XML文件中。为考虑多线程情况下的使用,SharedPreferencesImpl和Editor中的多数操作均设置了相应的对象锁和同步代码块synchronized。由于SharedPreferencesImpl的初始化是一次性加载XML,为了性能上考虑,ContextImpl设置了缓冲区(ArrayMap)保存SharedPreferencesImpl对象,以便反复使用已有对象。
总结
1.SharePreferences是Android基于xml实现的一种数据持久化手段。
2.SharePreferences不支持多进程。
3.SharePreferences的commit与apply一个是同步一个是异步(大部分场景下)。
4.不要使用SharePreferences存储太大的数据。
5.尽量使用apply来代替commit。
网友评论