Android SharedPreferences实现原理

Android SharedPreferences实现原理

  • 一.简介
  • 二.实现原理
    • 1.创建
    • 2.取值
    • 3.更新
    • 4.应用
      • (1)commit()
      • (2)apply()
  • 三.总结

一.简介

在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文件。

Android SharedPreferences实现原理_第1张图片
在这里插入图片描述

二.实现原理

1.创建

我们是通过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实例。

2.取值

通过上述内容,可以知道我们实际操作的对象,其实都是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();
        }...
    }
}

3.更新

再来看更新,上面说到过,更新是通过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中,等待后续应用。

4.应用

更新后,我们需要写入到磁盘中,EditorImpl为我们提供了两个方法,一个是commit()方法,一个是apply()方法,他们的区别,主要是同步和异步的差别。

(1)commit()

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

(2)apply()

再来看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()方法,会通过一个全局唯一的异步线程进行写文件的操作。

三.总结

  1. SharedPreferences的File创建和内容解析,在内存中是有缓存的
  2. SharedPreferences的提交,commit()方法是在当前线程完成,而apply()方法在全局唯一的一个工作线程中完成
  3. 所有的文件和内存读写操作,都通过锁对象进行加锁,保证了多线程同步

你可能感兴趣的:(原理解析,android相关)