Android文件存储SharedPreferences源码解析

 更新时间:2022年08月26日 15:11:17   作者:niuyongzhi  
SharedPreferences是安卓平台上一个轻量级的存储类,用来保存应用的一些常用配置,比如Activity状态,Activity暂停时,将此activity的状态保存到SharedPereferences中;当Activity重载,系统回调方法onSaveInstanceState时,再从SharedPreferences中将值取出

1.我们都知道SharedPreferences 是android可以用来存放key value的的文件。

        SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString("key","value");
        editor.commit();
    SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putString("key","value");
        editor.apply();

SharedPreferences是一个接口。getSharedPreferences 拿到的是它的实现类SharedPreferencesImpl。

ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
public SharedPreferences getSharedPreferences(String name, int mode) {
	   if (sp == null) {
		 File prefsFile = getSharedPrefsFile(name);
		 sp = new SharedPreferencesImpl(prefsFile, mode);
		 packagePrefs.put(name, sp);
		 return sp;
     }
}

在构造函数中,会把存储的键值对保存到一个hashMap中

  SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        //读取文件中存储的key value,并存到全局变量mMap中
        startLoadFromDisk();
    }
  private void loadFromDiskLocked() {
    .......
         str = new BufferedInputStream(
                            new FileInputStream(mFile), 16*1024);
         map = XmlUtils.readMapXml(str);
        if (map != null) {
               mMap = map;
               mStatTimestamp = stat.st_mtime;
               mStatSize = stat.st_size;
       } else {
           mMap = new HashMap<String, Object>();
       }
    }

当我们getString等取值的时候,就是从这个mMap中取的。

    get方法就是从这个map中读取。
public String getString(String key, String defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

2. sharedPrefrence存数据,有两种方式,commit和apply。

sp.edit()拿到的也是一个接口,Editor,实现类是EditorImpl。

 SharedPreferences.Editor editor = sp.edit();
 public Editor edit() {
     return new EditorImpl();
 }

当调用putString(String key, String value)时,先保存到了一个map中

  private final Map<String, Object> mModified = Maps.newHashMap();
 public Editor putString(String key, String value) {
        synchronized (this) {
            //将要修改的key value,存放到map中
            mModified.put(key, value);
            return this;
        }
}

那么commit和apply的区别是什么?

1).commit有返回值是一个boolean类型。

apply没有返回值,返回的是void。

2)commit是同步存储,所以必须拿到返回值,代码才能往下走,否则会阻塞在这。

apply是异步存储,直接丢在了一个线程中执行,我们不必等待他的返回结果。

直接看源码

  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;
	}
	notifyListeners(mcr);
	return mcr.writeToDiskResult;
}
public void apply() {
	final MemoryCommitResult mcr = commitToMemory();
	final Runnable awaitCommit = new Runnable() {
			public void run() {
				try {
					mcr.writtenToDiskLatch.await();
				} catch (InterruptedException ignored) {
				}
			}
		};
	QueuedWork.add(awaitCommit);
	Runnable postWriteRunnable = new Runnable() {
			public void run() {
				awaitCommit.run();
				QueuedWork.remove(awaitCommit);
			}
		};
	SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
	notifyListeners(mcr);
}

分析源码

commit和apply都调用了这行代码,

final MemoryCommitResult mcr = commitToMemory();

和private void enqueueDiskWrite(final MemoryCommitResult mcr,

final Runnable postWriteRunnable) ;

这俩的不同在于第二个参数 Runnable postWriteRunnable。commit传的是一个null,而apply传的是一个Runnable对象。这个参数很关键,后面会根据这个参数进行判断,选择是异步存储还是同步存储。

先看commitToMemory()是如何实现的。

这个方法是将要修改的键值对(存在mModified中),和文件中的的全量键值对(存在mMap中),

进行比对,把更新后的map赋值给 mcr.mapToWriteToDisk = mMap;

   private MemoryCommitResult commitToMemory() {
          MemoryCommitResult mcr = new MemoryCommitResult();
           //mMap存储了文件中所有的键值对。
           mcr.mapToWriteToDisk = mMap;
           对要新增或修改的键值对进行遍历。并添加到mMap中
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                       String k = e.getKey();
                       Object v = e.getValue();
                if (mMap.containsKey(k)) {
                   Object existingValue = mMap.get(k);
                   if (existingValue != null && existingValue.equals(v)) {
                       continue;
                   }
               }
               mMap.put(k, v);
            }
            mcr.changesMade = true;
            mModified.clear();
         return mcr;
   }

在看第二个方法enqueueDiskWrite(mrc,runnable)。

如果是commit方式存储,runnable==null。则调用writeToDiskRunnable.run();进行存储,这个方法是同步的。

如果是apply方式存储,runnable!=null。会直接放进一个线程池中执行。

QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);

这也就是为什么apply是异步存储。

  注意第二个参数,commit传的是null。apply传的是一个postWriteRunnable
   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--;
                       }
                       if (postWriteRunnable != null) {
                           postWriteRunnable.run();
                       }
                   }
               };
           //根据 postWriteRunnable 是不是null来区分是commit方式还是apply方式
           final boolean isFromSyncCommit = (postWriteRunnable == null);
           // Typical #commit() path with fewer allocations, doing a write on
           // the current thread.
           //如果是commit方式,上面的注释很也说明了commit是在当前线程执行的文件存储。
           if (isFromSyncCommit) {
               boolean wasEmpty = false;
               synchronized (SharedPreferencesImpl.this) {
                   wasEmpty = mDiskWritesInFlight == 1;
               }
               if (wasEmpty) {
                   //直接调用Runnable的run方法。在当前线程执行文件的存储。所以是同步方式
                   writeToDiskRunnable.run();
                   return;
               }
           }
            // 如果是applay方式,上面代码不会执行,也就不会return。
            //则会把存储文件的方法放到一个线程池中去执行
           QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
       }

然后在看看writeToFile(MemoryCommitResult mcr)。将修改后的键值对,保存入文件中。

先是对源文件做了一个备份,然后全量的写入文件。

如果写成功,会将备份文件删除。

如果写文件时出现异常,则会将备份文件恢复。

 private void writeToFile(MemoryCommitResult mcr) {
            //在写文件前,先将源文件进行一个备份
             if (!mBackupFile.exists()) {
                 if (!mFile.renameTo(mBackupFile)) {
                     mcr.setDiskWriteResult(false);
                     return;
                 }
             } else { //如果备份文件存在,则将源文件删掉
                 mFile.delete();
             }
          FileOutputStream str = createFileOutputStream(mFile);
            //将文件中所有的keyvalue,保护要修改的,全量存入新的文件中。
            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
            FileUtils.sync(str);
            str.close();
            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
            // Writing was successful, delete the backup file if there is one.
            //删除备份的文件
            mBackupFile.delete();
            mcr.setDiskWriteResult(true);
 }

到此这篇关于Android文件存储SharedPreferences源码解析的文章就介绍到这了,更多相关Android SharedPreferences内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Flutter WebView与JS互相调用简易指南

    详解Flutter WebView与JS互相调用简易指南

    这篇文章主要介绍了详解Flutter WebView与JS互相调用简易指南,分为JS调用Flutter和Flutter调用JS,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04
  • Android ViewPager画廊效果详解及实例

    Android ViewPager画廊效果详解及实例

    这篇文章主要介绍了Android ViewPager画廊效果详解及实例的相关资料,这里提供实例代码及实现效果图,具有参考价值,需要的朋友可以参考下
    2016-12-12
  • Android ArrayMap源代码分析

    Android ArrayMap源代码分析

    这篇文章主要介绍了Android ArrayMap源代码分析的相关资料,需要的朋友可以参考下
    2016-10-10
  • 详解Android的自动化构建及发布

    详解Android的自动化构建及发布

    本篇文章主要介绍了Android的自动化构建及发布,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Android Apt之Activity Route的示例

    Android Apt之Activity Route的示例

    本篇文章主要介绍了Android Apt之Activity Route的示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • Android中EditText 设置 imeOptions 无效问题的解决方法

    Android中EditText 设置 imeOptions 无效问题的解决方法

    有时候我们需要在EditText 输出完之后 需要在键盘出现 右下角变成“Go”或“前往 搜索时;通常我们需要设置Android:imeOptions属性,但是今天我发现设置了无效,下面给大家分享下解决方案
    2016-12-12
  • Android实现长图展开与收起效果

    Android实现长图展开与收起效果

    这篇文章主要为大家详细介绍了Android实现长图展开与收起效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-09-09
  • 从源码编译Android系统的Java类库和JNI动态库的方法

    从源码编译Android系统的Java类库和JNI动态库的方法

    这篇文章主要介绍了从源码编译Android系统的Java类库和JNI动态库的方法,例子基于Linux系统环境下来讲,需要的朋友可以参考下
    2016-02-02
  • Flutter 状态管理的实现

    Flutter 状态管理的实现

    这篇文章主要介绍了Flutter 状态管理的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • Android封装MVP实现登录注册功能

    Android封装MVP实现登录注册功能

    这篇文章主要为大家详细介绍了Android封装MVP实现登录注册功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11

最新评论