在Android中,我们通常会需要存储一些数据,有一些大型的数据诸如图片、JSON数据等,可以通过读写File的方式实现;有一些大量级的关系型数据,可以通过数据库SQLite实现;还有一些简单的、无安全风险的键值对数据,可以通过Android提供的SharedPreferences实现。
SharedPreferences是一个轻量级的xml键值对文件,使用起来也是很方便。
//根据文件名,获取SharedPreferences对象;mode一般都使用MODE_PRIVATE,只能由该App访问
val sp = context.getSharedPreferences("setting", Context.MODE_PRIVATE)
//根据key,获取指定值
val needInitChannels = sp.getBoolean("isDebug", false)
//获取Editor编辑对象,用于编辑SharedPreferences
val editor = sp.edit()
editor.putBoolean("isDebug",true)
//同步提交到SharedPreferences文件,获取是否同步成功的结果
val res:Boolean = editor.commit()
//异步提交到SharedPreferences文件
editor.apply()
当我们第一次访问一个名为"setting"的SharedPreferences文件,系统会在应用数据目录下(/data/data/packageName/)的shared_prefs文件夹下,创建一个同名的xml文件。
我们是通过Context的getSharedPreferences()方法来获取一个SharedPreferences对象,而Context的实际逻辑载体,是在ContextImpl里,所以我们来看ContextImpl的该方法。
public SharedPreferences getSharedPreferences(String name, int mode) {
...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//1.获取缓存File对象
file = mSharedPrefsPaths.get(name);
if (file == null) {
//生成File对象
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
//2.获取File对象对应的SharedPreferences对象
return getSharedPreferences(file, mode);
}
这里第一步我们可以看到,有一个ArrayMap会缓存每一个SharedPreferences文件对象,如果第一次加载的话,会调用getSharedPreferencesPath()生成File对象。
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
///data/data/packageName/目录
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
if (!file.exists()) {
final String path = file.getAbsolutePath();
try {
Os.mkdir(path, mode);
...
}...
}
return file;
}
我们会看到,如果应用目录下还没有shared_prefs文件夹,则创建一个该文件夹。
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
}
然后,会在shared_prefs文件夹下,创建一个我们指定名字的xml文件,用来存储键值对数据。
至此,会生成一个相应SharedPreferences的File文件,并进行缓存。
第二步,通过File获取SharedPreferences对象。
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
...
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
...
return sp;
}
这里我们看到,返回的SharedPreferences实际对象,是一个SharedPreferencesImpl对象实例,并且也通过一个ArrayMap做了一个缓存,也就是说,一个name会对应一个SharedPreferences的File实例,而一个File会对应一个SharedPreferencesImpl实例。
通过上述内容,可以知道我们实际操作的对象,其实都是SharedPreferencesImpl对象。这里我们以getBoolean()为例,看一下如何获取值。
public boolean getBoolean(String key, boolean defValue) {
synchronized (mLock) {
//等待从磁盘加载数据(需要的话)
awaitLoadedLocked();
//直接从map取值返回
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
那么从磁盘读取文件内容,并生成Map对象的初始化过程,是在何时进行的呢?是在SharedPreferencesImpl的构造函数中进行。
SharedPreferencesImpl(File file, int mode) {
...
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//工作线程进行
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
...
}
...
Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
//读取文件内容
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//解析XML生成Map
map = (Map<String, Object>) XmlUtils.readMapXml(str);
}...
}
}
...
mMap = map;
...
mLock.notifyAll();
}
可以看到,SharedPreferencesImpl创建的时候,简单粗暴的开启了一个工作线程,进行File的读取和解析,并生成了Map对象,赋值给mMap变量。
而在我们的getBoolean()等操作Map的方法调用时,会通过awaitLoadedLocked()方法判断是否map已经生成,如果没有则等待。
private void awaitLoadedLocked() {
...
while (!mLoaded) {
try {
mLock.wait();
}...
}
}
再来看更新,上面说到过,更新是通过SharedPreferences的edit()方法,获取一个Editor对象进行。
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
可见每次都是创建一个新的EditorImpl对象实例进行操作。
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
所有的更新操作,都会维护在EditorImpl对象的一个HashMap中,等待后续应用。
更新后,我们需要写入到磁盘中,EditorImpl为我们提供了两个方法,一个是commit()方法,一个是apply()方法,他们的区别,主要是同步和异步的差别。
public boolean commit() {
...
//1.将改动先同步到内存
MemoryCommitResult mcr = commitToMemory();
//2.同步写入磁盘
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
}...
return mcr.writeToDiskResult;
}
第一步是调用commitToMemory()方法,将EditorImpl的modify改动,应用到内存中,即应用到mMap中。
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
//当前更新操作的线程数+1
mDiskWritesInFlight++;
...
synchronized (mEditorLock) {
...
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {//remove
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {//update or insert
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
}...
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
第二步,将内存中最新的数据,写到文件中。
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//commit()方法时为true
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//将Map写入File
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
//当前更新操作的线程数-1
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
//只有当前线程调用时,因为同步进行,所以一直为1;有多个线程同时调用时,会大于1
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//当前线程直接调用
writeToDiskRunnable.run();
return;
}
}
//异步调用
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
当只有一个线程操作SharedPreferences的话,mDiskWritesInFlight计数器始终为1,因为是同步写入File,写入后计数器会-1。
而写入文件就是很简单的IO操作,只不过需要把Map转换为xml的格式。
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
...
try {
//OutputStream
FileOutputStream str = createFileOutputStream(mFile);
...
//将map数据写成xml格式到file中
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
...
//设置成功写入的结果
mcr.setDiskWriteResult(true, true);
}
再来看apply()方法。
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
...
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//postWriteRunnable不为null
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
...
}
这里可以看到,调用的方法和commit()都一样,唯独调用enqueueDiskWrite()方法时,第二个Runnable对象不为null,按照上面说,不为null时,走的方法就是异步方法QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
//将任务加入到队列中等待执行
sWork.add(work);
//通过handler发送消息
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static class QueuedWorkHandler extends Handler {
...
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
}
private static void processPendingWork() {
...
for (Runnable w : work) {
//执行任务
w.run();
}
}
可以看到,内部就是通过handler发送消息执行任务的,任务还是一样的writeFile()方法,那么为何是异步的呢?就得来看看handler是啥handler了。
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;
}
}
现在就明白了吧,是一个全局的HandlerThread对象,也就是一个工作线程,所以apply()方法,会通过一个全局唯一的异步线程进行写文件的操作。