Android文件存储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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android 使用 SharedPreferences 保存少量数据的实现代码

    1 SharedPreferences 介绍 SharedPreferences是使用键值对的方式来存储数据的 SharedPreferences share = getSharedPreferences("my_file", Context.MODE_PRIVATE); SharedPreferences.Editor editor = share.edit(); // 4 保存数据到文件 editor.putString("account", input_acc

  • Android 文件存储与SharedPreferences存储方式详解用法

    目录 持久化技术简介 文件存储 1. 将数据存储到文件中 2. 从文件中读取数据 SharedPreferences 存储 1. 将数据存储到 SharedPreferences 中 2. 从 SharedPreferences 中读取数据 持久化技术简介 数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据也不会丢失.保存在内存中的数据是处于瞬时状态的,而保存在存储设备的数据是处于持久状态的.持久化技术提供了一种机制,可以让数据在瞬时状态和持久状

  • Android SharedPreferences实现记住密码和自动登录

    本文实例为大家分享了Android SharedPreferences实现记住密码和自动登录,供大家参考,具体内容如下 效果图: 第一次进入进来 勾选记住密码和自动登录成功后,第二次进来 说明:中间存在的图片或者多余的其他部分可删掉.留下最主要的填写部分和登陆按钮即可.功能还是可以实现的. XML文件 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="h

  • Android SharedPreferences存取操作以及封装详解

    存 首先初始化 private SP sp; sp = new SP( context ); 存入数据 第一个参数为上下文,第二个参数为key,第三个参数为要存入的数据Value sp.PutData( context,"AccessToken",AccessToken ); 取 初始化 private SP sp; sp = new SP( context ); 取出数据 第一个参数为上下文,第二个参数为存入数据时定义的名称,第三个数据为取出数据的默认类型(这个参数很关键,再封装类转

  • Android用SharedPreferences实现登录注册注销功能

    Android用SharedPreferences实现登录注册注销功能 前言 本文用SharedPreferences本地缓存账号信息来实现登录注册功能,以及退出注销功能. 一.本文逻辑 本文的注册登录逻辑如下: 1.注册页面:有账号可以直接去登录页面.没有账号的话填写账号密码,检测账号密码不为空,点击立即注册,保存账号信息,跳转到登录页面. 2.登录页面:首先读取缓存的账号密码和是否记住密码,将账号显示,判断记住密码的标志,为空或false,不显示密码,需要输入密码,为true则直接将缓存的密

  • 使用SharedPreferences在Android存储对象详细代码

    为什么不用SQLite? 原因多种:除了面向对象和关系数据库之间的阻抗不匹配时,SQLite可能是矫枉过正(带来了更多的开销)对于一些简单的用例,或者其用法和语句可能会完全不喜欢.其他Android ORM 框架(ORMLite, greenDAO, Sugar ORM, ActiveAndroid, 等)或NOSQL移动数据库,如Couchbase精简版(Beta版在此时间). Couchbase精简版基本上是一个JSON数据库,它们旨在降低复杂性,但是同时又违背了抽象漏洞定律(所有不证自明的

  • Android SharedPreferences实现保存登录数据功能

    本文实例为大家分享了Android SharedPreferences保存登录数据的具体代码,供大家参考,具体内容如下 目标效果: 程序运行显示一个登陆框,用户名输入admin,密码输入123456会提示登录成功,如果不是则提示不正确,如果勾选保存用户名,在下一个程序打开时,用户名会自动读取并显示. 1.activity_main.xml页面存放所有的控件,我在每一行都使用了线性布局. activity_main.xml页面: <RelativeLayout xmlns:android="

  • android文件存储和SharedPreferences存储的项目实例

    该实例为课程作业,请尊重劳动成果. 演示 [文件存储]中查看设备保存的文件 目录 activity_main <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/re

  • Android数据共享 sharedPreferences 的使用方法

    Android数据共享 sharedPreferences 的使用方法 Android 中通过 sharedPreferences 来持久化存储数据并进行共享 在 Activity 或存在 Context 环境中即可使用 context.getSharedPreferences(name, Context.MODE_PRIVATE); 设置要保存的数据: mSp = context.getSharedPreferences(name, Context.MODE_PRIVATE); mEditor

  • Android文件存储SharedPreferences源码解析

    1.我们都知道SharedPreferences 是android可以用来存放key value的的文件. SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("key","value"); editor.commit(

  • Android Jetpack 组件LiveData源码解析

    目录 前言 基本使用 疑问 源码分析 Observer ObserverWrapper LifecycleBoundObserver MutableLiveData postValue setValue 问题答疑 LiveData 特性引出的问题 问题解决 最后 前言 本文来分析下 LiveData 的源码,以及其在实际开发中的一些问题. 基本使用 一般来说 LiveData 都会配合 ViewModel 使用,篇幅原因关于 ViewModel 的内容将在后续博客中分析,目前可以将 ViewMo

  • 详解Android开发数据持久化之文件存储(附源码)

    其实我们在社交网络上面所发出的任何信息, 都希望能够保留下来. 那么如何实现呢? 数据持久化 数据持久化, 就是将内存中的瞬时数据保存在存储设备中, 保证即便关机之后, 数据仍然存在. 保存在内存中的数据是瞬时数据, 保存在存储设备中的数据就是处于持久状态的. 持久化技术则是提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换, Android系统中主要提供了3种方式用于简单地实现数据持久化功能, 即文件存储, SharePreference存储, 以及数据库存储. 当然你也可以将数据保存在

  • Android AsyncTask使用以及源码解析

    综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看Android的消息机制--Handler的工作过程这篇文章).当然为了简化我们的操作,在Android1.5以后为我们提供了AsyncTask类,它能够将子线程处理完成后的结果返回到UI线程中,之后我们便可以根据这些结果进行一列的UI操作了. AsyncTask的使用方法 实际上AsyncTask内部也

  • Android 内核代码 wake_up源码解析

    目录 内核中通常用法: wake_up 的源码: func 赋值过程 wait_queue_head 和 wait_queue_entry 数据结构 两种等待任务 wait_queue_entry:线程 和 函数 default_wake_function 函数 综上: 内核中通常用法: 内核有个函数 wake_up 和 wake_up_interruptible 通常来说看到这俩函数调用就是唤醒等待队列上的线程. 直到看了epoll的源码,发现并非如此. bool wakeup_conditi

  • Android跑马灯MarqueeView源码解析

    跑马灯效果,大家可以去原作者浏览https://github.com/sfsheng0322/MarqueeView 下面看自定义控件的代码 public class MarqueeView extends ViewFlipper { private Context mContext; private List<String> notices; private boolean isSetAnimDuration = false; private OnItemClickListener onIt

  • Laravel框架源码解析之入口文件原理分析

    本文实例讲述了Laravel框架源码解析之入口文件原理.分享给大家供大家参考,具体如下: 前言 提升能力的方法并非使用更多工具,而是解刨自己所使用的工具.今天我们从Laravel启动的第一步开始讲起. 入口文件 laravel是单入口框架,所有请求必将经过index.php define('LARAVEL_START', microtime(true)); // 获取启动时间 使用composer是现代PHP的标志 require __DIR__.'/../vendor/autoload.php

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Android音视频开发Media FrameWork框架源码解析

    目录 一.Media FrameWork背景 二.Media Framework“路线图” 2.1 代理端 2.2 服务端 2.2.1 Source 2.2.2 Decoder 2.2.3 Renderer 2.2.4 Foundation 2.3 OMX端 2.4 Kernel端 三.media播放的流程 四.Media FrameWork源码分析 一.Media FrameWork背景 Media Framework (媒体函数库):此函数库让Android 可以播放与录制许多常见的音频与视

随机推荐