很久没有分析源码了,今天我们来分析下SharedPreferences;
大家一起来学习;
一、SharedPreferences简单使用
第一个参数是储存的xml文件名称,第二个是打开方式,一般就用
- Context.MODE_PRIVATE;
- SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
- //可以创建一个新的SharedPreference来对储存的文件进行操作
- SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
- //像SharedPreference中写入数据需要使用Editor
- SharedPreference.Editor editor = sp.edit();
- //类似键值对
- editor.putString("name", "string");
- editor.putInt("age", 0);
- editor.putBoolean("read", true);
- //editor.apply();
- editor.commit();
- SharedPreference sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
- //第一个参数是键名,第二个是默认值
- String name=sp.getString("name", "暂无");
- int age=sp.getInt("age", 0);
- boolean read=sp.getBoolean("isRead", false);
- SharedPreferences sp=context.getSharedPreferences("名称", Context.MODE_PRIVATE);
- //检查当前键是否存在
- boolean isContains=sp.contains("key");
- //使用getAll可以返回所有可用的键值
- //Map<String,?> allMaps=sp.getAll();
当我们要清除SharedPreferences中的数据的时候一定要先clear()、再commit(),不能直接删除xml文件;
- SharedPreference sp=getSharedPreferences("名称", Context.MODE_PRIVATE);
- SharedPrefence.Editor editor=sp.edit();
- editor.clear();
- editor.commit();
二、SharedPreferences源码分析
- SharedPreferences preferences = getSharedPreferences("test", Context.MODE_PRIVATE);
实际上context的真正实现类是ContextImp,所以进入到ContextImp的getSharedPreferences方法查看:
- @Override
- public SharedPreferences getSharedPreferences(String name, int mode) {
- ......
- File file;
- synchronized (ContextImpl.class) {
- if (mSharedPrefsPaths == null) {
- //定义类型:ArrayMap<String, File> mSharedPrefsPaths;
- mSharedPrefsPaths = new ArrayMap<>();
- }
- //从mSharedPrefsPaths中是否能够得到file文件
- file = mSharedPrefsPaths.get(name);
- if (file == null) {//如果文件为null
- //就创建file文件
- file = getSharedPreferencesPath(name);
- 将name,file键值对存入集合中
- mSharedPrefsPaths.put(name, file);
- }
- }
- return getSharedPreferences(file, mode);
- }
ArrayMap<String, File> mSharedPrefsPaths;对象是用来存储SharedPreference文件名称和对应的路径,获取路径是在下列方法中,就是获取data/data/包名/shared_prefs/目录下的
- @Override
- public File getSharedPreferencesPath(String name) {
- return makeFilename(getPreferencesDir(), name + ".xml");
- }
- private File getPreferencesDir() {
- synchronized (mSync) {
- if (mPreferencesDir == null) {
- mPreferencesDir = new File(getDataDir(), "shared_prefs");
- }
- return ensurePrivateDirExists(mPreferencesDir);
- }
- }
路径之后才开始创建对象
- @Override
- public SharedPreferences getSharedPreferences(File file, int mode) {
- //重点1
- checkMode(mode);
- .......
- SharedPreferencesImpl sp;
- synchronized (ContextImpl.class) {
- //获取缓存对象(或者创建缓存对象)
- final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
- //通过键file从缓存对象中获取Sp对象
- sp = cache.get(file);
- //如果是null,就说明缓存中还没后该文件的sp对象
- if (sp == null) {
- //重点2:从磁盘读取文件
- sp = new SharedPreferencesImpl(file, mode);
- //添加到内存中
- cache.put(file, sp);
- //返回sp
- return sp;
- }
- }
- //如果设置为MODE_MULTI_PROCESS模式,那么将执行SP的startReloadIfChangedUnexpectedly方法。
- if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
- getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
- sp.startReloadIfChangedUnexpectedly();
- }
- return sp;
- }
就是重载之前的方法,只是入参由文件名改为File了,给创建过程加锁了synchronized ,通过方法getSharedPreferencesCacheLocked()获取系统中存储的所有包名以及对应的文件,这就是每个sp文件只有一个对应的SharedPreferencesImpl实现对象原因
getSharedPreferencesCacheLocked
- private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
- if (sSharedPrefsCache == null) {
- sSharedPrefsCache = new ArrayMap<>();
- }
- final String packageName = getPackageName();
- ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
- if (packagePrefs == null) {
- packagePrefs = new ArrayMap<>();
- sSharedPrefsCache.put(packageName, packagePrefs);
- }
- return packagePrefs;
- }
- SharedPreferencesImpl(File file, int mode) {
- mFile = file; //存储文件
- //备份文件(灾备文件)
- mBackupFile = makeBackupFile(file);
- //模式
- mMode = mode;
- //是否加载过了
- mLoaded = false;
- // 存储文件内的键值对信息
- mMap = null;
- //从名字可以知道是:开始加载数据从磁盘
- startLoadFromDisk();
- }
2、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;
- }
- //备份文件是否存在,
- if (mBackupFile.exists()) {
- //删除file原文件
- mFile.delete();
- //将备份文件命名为:xml文件
- mBackupFile.renameTo(mFile);
- }
- }
- .......
- Map map = null;
- StructStat stat = null;
- try {
- //下面的就是读取数据
- stat = Os.stat(mFile.getPath());
- if (mFile.canRead()) {
- BufferedInputStream str = null;
- try {
- str = new BufferedInputStream(
- new FileInputStream(mFile), 16*1024);
- map = XmlUtils.readMapXml(str);
- } catch (Exception e) {
- Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
- } finally {
- IoUtils.closeQuietly(str);
- }
- }
- } catch (ErrnoException e) {
- /* ignore */
- }
- synchronized (mLock) {
- //已经加载完毕,
- mLoaded = true;
- //数据不是null
- if (map != null) {
- //将map赋值给全局的存储文件键值对的mMap对象
- mMap = map;
- //更新内存的修改时间以及文件大小
- mStatTimestamp = stat.st_mtime;
- mStatSize = stat.st_size;
- } else {
- mMap = new HashMap<>();
- }
- //重点:唤醒所有以mLock锁的等待线程
- mLock.notifyAll();
- }
- }
3、get获取SP中的键值对
- @Nullable
- public String getString(String key, @Nullable String defValue) {
- synchronized (mLock) { 锁判断
- awaitLoadedLocked(); //等待机制
- String v = (String)mMap.get(key); //从键值对中获取数据
- return v != null ? v : defValue;
- }
- }
- private void awaitLoadedLocked() {
- .......
- while (!mLoaded) { //在加载数据完毕的时候,值为true
- try {
- //线程等待
- mLock.wait();
- } catch (InterruptedException unused) {
- }
- }
- }
如果数据没有加载完毕(也就是说mLoaded=false),此时将线程等待;
4、putXXX以及apply源码
- public Editor edit() {
- //跟getXXX原理一样
- synchronized (mLock) {
- awaitLoadedLocked();
- }
- //返回EditorImp对象
- return new EditorImpl();
- }
- public Editor putBoolean(String key, boolean value) {
- synchronized (mLock) {
- mModified.put(key, value);
- return this;
- }
- }
- public void apply() {
- final long startTime = System.currentTimeMillis();
- //根据名字可以知道:提交数据到内存
- final MemoryCommitResult mcr = commitToMemory();
- ........
- //提交数据到磁盘中
- SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
- //重点:调用listener
- notifyListeners(mcr);
- }
5、commitToMemory
- 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
- mapToWriteToDisk = mMap;
- .......
- synchronized (mLock) {
- boolean changesMade = false;
- //重点:是否清空数据
- if (mClear) {
- if (!mMap.isEmpty()) {
- changesMade = true;
- //清空缓存中键值对信息
- mMap.clear();
- }
- mClear = false;
- }
- //循环mModified,将mModified中的数据更新到mMap中
- for (Map.Entry<String, Object> e : mModified.entrySet()) {
- String k = e.getKey();
- Object v = e.getValue();
- // "this" is the magic value for a removal mutation. In addition,
- // setting a value to "null" for a given key is specified to be
- // equivalent to calling remove on that 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);
- }
- .........
- }
- //清空临时集合
- mModified.clear();
- ......
- }
- }
- return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
- mapToWriteToDisk);
- }
6、commit方法
- public boolean commit() {
- .......
- //更新数据到内存
- MemoryCommitResult mcr = commitToMemory();
- //更新数据到磁盘
- SharedPreferencesImpl.this.enqueueDiskWrite(
- mcr, null /* sync write on this thread okay */);
- try {
- //等待:等待磁盘更新数据完成
- mcr.writtenToDiskLatch.await();
- } catch (InterruptedException e) {
- return false;
- } finally {
- if (DEBUG) {
- Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
- + " committed after " + (System.currentTimeMillis() - startTime)
- + " ms");
- }
- }
- //执行listener回调
- notifyListeners(mcr);
- return mcr.writeToDiskResult;
- }
三、QueuedWork详解
1、QueuedWork
QueuedWork这个类,因为sp的初始化之后就是使用,前面看到,无论是apply还是commit方法都是通过QueuedWork来实现的;
QueuedWork是一个管理类,顾名思义,其中有一个队列,对所有入队的work进行管理调度;
其中最重要的就是有一个HandlerThread
- 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;
- }
- }
2、入队queue
- // 如果是commit,则不能delay,如果是apply,则可以delay
- public static void queue(Runnable work, boolean shouldDelay) {
- Handler handler = getHandler();
- synchronized (sLock) {
- sWork.add(work);
- if (shouldDelay && sCanDelay) {
- // 默认delay的时间是100ms
- handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
- } else {
- handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
- }
- }
- }
3、消息的处理
- 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();
- }
- }
- }
- private static void processPendingWork() {
- synchronized (sProcessingWork) {
- LinkedList<Runnable> work;
- synchronized (sLock) {
- work = (LinkedList<Runnable>) sWork.clone();
- sWork.clear();
- getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
- }
- if (work.size() > 0) {
- for (Runnable w : work) {
- w.run();
- }
- }
- }
- }
4、waitToFinish
系统中很多地方会等待sp的写入文件完成,等待方式是通过调用QueuedWork.waitToFinish();
- public static void waitToFinish() {
- Handler handler = getHandler();
- synchronized (sLock) {
- // 移除所有消息,直接开始调度所有work
- if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
- handler.removeMessages(QueuedWorkHandler.MSG_RUN);
- }
- sCanDelay = false;
- }
- StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
- try {
- // 如果是waitToFinish调用过来,则马上执行所有的work
- processPendingWork();
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- }
- try {
- // 在所有的work执行完毕之后,还需要执行Finisher
- // 前面在apply的时候有一步是QueuedWork.addFinisher(awaitCommit);
- // 其中的实现是等待sp文件的写入完成
- // 如果没有通过msg去调度而是通过waitToFinish,则那个runnable就会在这里被执行
- while (true) {
- Runnable finisher;
- synchronized (sLock) {
- finisher = sFinishers.poll();
- }
- if (finisher == null) {
- break;
- }
- finisher.run();
- }
- } finally {
- sCanDelay = true;
- }
- ...
- }
系统中对于四大组件的处理逻辑都在ActivityThread中实现,在service/activity的生命周期的执行中都会等待sp的写入完成,正是通过调用QueuedWork.waitToFinish(),确保app的数据正确的写入到disk;
5、sp使用的建议
SharedPreferences的本身实现就是分为两步,一步是内存,一部是磁盘,而主线程又依赖SharedPreferences的写入,所以可能当io成为瓶颈的时候,App会因为SharedPreferences变的卡顿,严重情况下会ANR,总结下来有以下几点:
本文转载自微信公众号「Android开发编程」
【编辑推荐】