SharedPreferences是以键值对的方式保存数据的,数据存放在xml文件中。

获取SharedPreferences

  1. Context类的getSharedPreferences()方法。参数一:指定文件名称;参数二:指定操作模式,Android4.2之后只有MODE_PRIVATE一种模式。
  2. Activity类的getPreferences()方法。文件名称默认是当前Activity的类名,接受一操作模式参数。
  3. PreferenceManager类中的getDefaultSharedPerferences()方法。这是一个静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀命名文件名。

SharedPreferences的使用

SharedPreferences对象本身不支持存储和修改,只能获取数据,存储修改是通过SharedPreferences.edit()获取内部接口Editor对象实现。

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
 1)写入数据:
//步骤1:创建一个SharedPreferences对象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步骤2: 实例化SharedPreferences.Editor对象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步骤3:将获取过来的值放入文件
editor.putString("name", “Tom”);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步骤4:提交
editor.commit();


2)读取数据:
SharedPreferences sharedPreferences= getSharedPreferences("data", Context .MODE_PRIVATE);
String userId=sharedPreferences.getString("name","");

3)删除指定数据
editor.remove("name");
editor.commit();


4)清空数据
editor.clear();
editor.commit();

关键点

  1. 没有xml时会创建文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public SharedPreferences getSharedPreferences(String name, int mode) { 
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//先从mSharedPrefsPaths查询是否存在相应文件
file = mSharedPrefsPaths.get(name);
if (file == null) {
//如果文件不存在, 则创建新的文件,文件的路径是/data/data/package name/shared_prefs/
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode); }
  1. SharedPreferences加载文件使用异步加载的方式。
1
2
3
4
5
6
private void startLoadFromDisk() { 
synchronized (this) { mLoaded = false; }
new Thread("SharedPreferencesImpl-load") {
public void run() { loadFromDisk(); }
}.start();
}
  1. 读文件的时候会等待一个锁,调用线程会被阻塞。因此,如果文件太大可能会导致ANR。
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
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
//检查是否加载完成
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}

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);
}
}
  1. 写文件实际上会创建一个新的Map用于记录要修改的内容。
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
33
34
35
public Editor edit() {
synchronized (this) {
awaitLoadedLocked();
}
return new EditorImpl();
}

public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;

//插入数据
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
//插入数据, 先暂存到mModified对象
mModified.put(key, value);
return this;
}
}
//移除数据
public Editor remove(String key) {
synchronized (this) {
mModified.put(key, this);
return this;
}
}

//清空全部数据
public Editor clear() {
synchronized (this) {
mClear = true;
return this;
}
}
}
  1. commit和apply的差异
    commit行是isFromSyncCommit,直接将runnable run了起来。是同步的过程。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public boolean commit() {
//将数据更新到内存
MemoryCommitResult mcr = commitToMemory();
//将内存数据同步到文件
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
try {
//进入等待状态, 直到写入文件的操作完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
//通知监听则, 并在主线程回调onSharedPreferenceChanged()方法
notifyListeners(mcr);
// 返回文件操作的结果数据
return mcr.writeToDiskResult;
}

private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
if (mDiskWritesInFlight > 0) {
mMap = new HashMap<String, Object>(mMap);
}
//这里就是要写到磁盘的map
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;

//是否有监听key改变的监听者
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}

synchronized (this) {
//当mClear为true, 则直接清空mMap
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
}

for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
//注意此处的this是个特殊值, 用于移除相应的key操作.
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;
}
}
mMap.put(k, v);
}

mcr.changesMade = true; // changesMade代表数据是否有改变
if (hasListeners) {
mcr.keysModified.add(k); //记录发生改变的key
}
}
mModified.clear(); //清空EditorImpl中的mModified数据
}
}
return mcr;
}

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--;
}
//此时postWriteRunnable为null不执行该方法
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};

final boolean isFromSyncCommit = (postWriteRunnable == null);

if (isFromSyncCommit) { //commit方法会进入该分支
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
//commitToMemory过程会加1,则wasEmpty=true
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//跳转到上面
writeToDiskRunnable.run();
return;
}
}
//不执行该方法
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

apply是异步的过程,实际使用HandlerThread去执行的。

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
33
public void apply() {
final long startTime = System.currentTimeMillis();

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.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);
}

1
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
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
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();

synchronized (sLock) {
sWork.add(work);

if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}

private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();

sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
  • apply() 没有返回值而 commit() 返回 boolean 表明修改是否提交成功
  • commit() 是把内容同步提交到硬盘的,而 apply() 先立即把修改提交到内存,然后开启一个异步的线程提交到硬盘,并且如果提交失败,你不会收到任何通知。
  • get 操作都是线程安全的, 并且 get 仅仅是从内存中 (mMap) 获取数据
  1. SharedPreferences不支持多进程,支持多线程。
  2. SharedPreference的文件备份机制
    SharedPreference的写入操作正式执行之前,首先会对文件进行备份,写入成功删除备份文件。若异常终止,则将备份文件重命名为源文件。
1
2
3
4
5
6
// 尝试写入文件
private void writeToFile(...) {
if (!backupFileExists) {
!mFile.renameTo(mBackupFile);
}
}
1
2
3
// 写入成功,立即删除存在的备份文件
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
1
2
3
4
5
6
7
8
9
// 从磁盘初始化加载时执行
private void loadFromDisk() {
synchronized (mLock) {
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
}