资讯专栏INFORMATION COLUMN

SharedPreferences源码之我见

wenhai.he / 3270人阅读

摘要:概述可以通过获取到,它是一个接口,实现在源码也仅仅只有行,相对于庞大的源码,小很多。但是你真的了解它吗问题不同的里面获取的相同吗支持并发吗直接保存了吗针对上面的问题,我们一点点来分析。但是仅仅是保存到了中,正真的是要在或之后。

说在前面:SharedPreferences是Android中几种重要的存储数据的方式,Android开发不会没有人从来没有使用过,但是却很好人会关注它是怎么实现的,确实SharedPreferences实现起来比较简单,本质是基于文件存储,格式是XML形势的,本篇文章并没有太深的技术,但绝对是不可缺少的知识。

概述

SharedPreferences可以通过Context获取到,它是一个接口,实现在frameworksbasecorejavaandroidappSharedPreferencesImpl.java

源码也仅仅只有630行,相对于庞大的Android源码,小很多。但是你真的了解它吗?

问题

1、不同的Activity里面Context.getSharedPreferences()获取的SharedPreferences相同吗?

2、支持并发吗?

3、SharedPreferences.putXXX()直接保存了吗?

......

针对上面的问题,我们一点点来分析。

获得SharedPreferences

我们获得SharedPreferences是通过Context.getSharedPreferences(),源码如下:

 @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
  SharedPreferencesImpl sp;
  synchronized (ContextImpl.class) {
    // 静态的 全局唯一,缓存得到的SharedPreferences,key是app包名
    if (sSharedPrefs == null) {
          sSharedPrefs = new ArrayMap>();
    }

    // 通过每一个app的包名缓存不同的SharedPreferences,key是文件名
    // 这里就明白前面提到的第一个问题了,一个文件对应一个SharedPreferences
    final String packageName = getPackageName();
    ArrayMap packagePrefs = sSharedPrefs.get(packageName);
    if (packagePrefs == null) {
      packagePrefs = new ArrayMap();
      sSharedPrefs.put(packageName, packagePrefs);
    }
    
    // 略...
    // 如果以创建过,直接从缓存中获取返回
    sp = packagePrefs.get(name);
    if (sp == null) {
      // 如果没有创建一个 文件名为data/data/packege/shared_prefs/xxx.xml
      File prefsFile = getSharedPrefsFile(name); 
      sp = new SharedPreferencesImpl(prefsFile, mode); // 创建实例对象
      packagePrefs.put(name, sp); // 放到缓存中
      return sp;
    }
  }
// 略...
  return sp;
}
创建SharedPreferences

SharedPreferences的创建很简单,我们先看看构造方法:

// 仅有一个
SharedPreferencesImpl(File file, int mode) {
  mFile = file;
  mBackupFile = makeBackupFile(file); // 创建备份
  mMode = mode;
  mLoaded = false;
  mMap = null; // 存放键值对的map
  startLoadFromDisk();  // 从本地load出来
}

从构造函数我们不难看出,SharedPreferences实际是以Map(HashMap)存储数据,创建的时候就从本地文件中读取数据到内存中,所以,不能存储太多的数据 ,否则内存会爆掉的哦。

读取本地文件中的数据

当实例化的时候就会触发,一个读取本地已存储的数据,

 private void loadFromDiskLocked() {
 // 读取过就不在读取
 if (mLoaded) {
        return;
    }
    //...
    
    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);
                // 格式为xml,解析xml得到map
                map = XmlUtils.readMapXml(str);
            } catch (XmlPullParserException e) {
                Log.w(TAG, "getSharedPreferences", e);
            } catch (FileNotFoundException e) {
                Log.w(TAG, "getSharedPreferences", e);
            } catch (IOException e) {
                Log.w(TAG, "getSharedPreferences", e);
            } finally {
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
    }
    // 加载过后更改状态,如果没有创建一个HashMap
    mLoaded = true;
    if (map != null) {
        mMap = map;
        mStatTimestamp = stat.st_mtime;
        mStatSize = stat.st_size;
    } else {
        mMap = new HashMap();
    }
    notifyAll();
}
获得存储的数据

得到数据通常是getXXX(),实际很简单,就是存map中取数据。

// 已getString()为例,其他一样
@Nullable
public String getString(String key, @Nullable String defValue) {
    // 同步哦
    synchronized (this) {
        // 确保,已从文件中读取过数据
        awaitLoadedLocked();
        // 从map中的得到数据
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}
保存数据

大家都知道保存数据是通过Editor来存储的,事实上也确实是这样的。但是putXXX()仅仅是保存到了map中,正真的是要在commit()或apply()之后。

public Editor putString(String key, @Nullable String value) {
    // 同步哦
    synchronized (this) {
            mModified.put(key, value);
            return this;
        }
    }

1、

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

2、

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

      // ...

    QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

3、

 // Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
   // ...
    try {
        FileOutputStream str = createFileOutputStream(mFile);
        if (str == null) {
            mcr.setDiskWriteResult(false);
            return;
        }
        // 写入到文件中
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
        try {
         // 更新文件的状态
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (this) {
                mStatTimestamp = stat.st_mtime;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }
        // Writing was successful, delete the backup file if there is one.
        mBackupFile.delete();
        mcr.setDiskWriteResult(true);
        return;
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }
    // Clean up an unsuccessfully written file
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn"t clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false);
}
总结

1、SharedPreferences有缓存

2、数据是通过map来快速访问,所以不用担心文件IO耗时

3、支持多线程并发

4、本质是基于文件xml的形式

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66790.html

相关文章

  • 《CDN 我见》系列二:原理篇(缓存、安全)

    摘要:真正要做高性能的系统,不仅需要在数据结构与算法层面深入,更要从硬件操作系统文件系统底层原理等多个领域做更多的研究例如阿里云自研的系统使用了裸盘技术。 《CDN之我见》共由三个篇章组成,分为原理篇、详解篇和陨坑篇。本篇章适合那些从未接触过、或仅了解一些 CDN 专业术语,想深入了解和感受 CDN 究竟是什么的同学。本次由白金老师继续为大家分享《CDN之我见》系列二,主要讲解缓存是什么、工...

    maxmin 评论0 收藏0
  • 《CDN 我见》系列二:原理篇(缓存、安全)

    摘要:真正要做高性能的系统,不仅需要在数据结构与算法层面深入,更要从硬件操作系统文件系统底层原理等多个领域做更多的研究例如阿里云自研的系统使用了裸盘技术。 《CDN之我见》共由三个篇章组成,分为原理篇、详解篇和陨坑篇。本篇章适合那些从未接触过、或仅了解一些 CDN 专业术语,想深入了解和感受 CDN 究竟是什么的同学。本次由白金老师继续为大家分享《CDN之我见》系列二,主要讲解缓存是什么、工...

    rainyang 评论0 收藏0
  • 初级理解async实现我见

    摘要:在我们写项目代码的过程中,要经常请求接口数据,在某些异步请求数据之后,将得到的值进行处理。 在我们写项目代码的过程中,要经常请求接口数据,在某些异步请求数据之后,将得到的值进行处理。通俗的一句话就是,我要把这个值放到另一个函数中,按行数顺序处理,即同步的概念! 例子:第一步,涉及异步函数 假设我有一个函数abc, function abc(){ //异步方法,请求数据得到re...

    CastlePeaK 评论0 收藏0
  • Spring框架我见(五)——Spring Boot

    摘要:通过我们可以更轻松地入门,更简单的使用的框架。团队为了摆脱框架中各类繁复纷杂的配置,使用约定优于配置的思想,在基础上整合了大量常用的第三方库的开发框架。这里还要说的一点,的出现并不是单纯的为了简化开发,更是为做铺垫。 说完了Spring 我们来聊聊Spring的进阶版Spring Boot,如果你还不知道Spring Boot,那希望这篇文章能够为你指明方向。 Spring Boot ...

    张巨伟 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<