SharedPreferences 源码及原理

本文深入探讨Android中的SharedPreferences,详细解析其创建流程,包括如何通过Context获取实例,以及SharedPreferencesImpl的使用。接着讨论写入操作,分析putString方法如何更新内存中的数据并在commit时持久化到硬盘。最后,阐述读取数据的过程,说明如何从硬盘加载初始内容并安全地读取键值对。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SharedPreferences 源码及原理

SharedPreferences 是Android中提供的一种轻量级数据存储方式,用来以键值对方式保存简单的数据类型。

创建流程

Android开发者通过Context的方法context.getSharedPreferences(String, Int)来获取SharedPreferences实例。Context中的具体实现如下

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return mBase.getSharedPreferences(name, mode);
    }

其直接调用了Context中代理模式的mBase的实现。mBase是一个ContextImpl实例。其内部方法实现如下

    // 通过名称获取SharedPreferences实例
    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        // At least one application in the world actually passes in a null
        // name.  This happened to work because when we generated the file name
        // we would stringify it to "null.xml".  Nice.
        if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";
            }
        }

        File file;
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }
    
    // 获取文件路径
    @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);
        }
    }

ContextImpl通过 mSharedPrefsPaths缓存文件实例,它是一个ArrayMap<String, File>格式的变量。若缓存中没有目标文件,则通过getSharedPreferencesPath()方法生成,并将其放入缓存。然后通过getSharedPreferences(File, Int)方法真正创建实例。通过源码可知,其路径在应用的getDataDir()路径下的"shared_prefs" 文件夹内,以"${name}.xml"命名

   // 通过文件获取SharedPreferences实例
    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        checkMode(mode);
        if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
            if (isCredentialProtectedStorage()
                    && !getSystemService(StorageManager.class).isUserKeyUnlocked(
                            UserHandle.myUserId())
                    && !isBuggy()) {
                throw new IllegalStateException("SharedPreferences in credential encrypted "
                        + "storage are not available until after user is unlocked");
            }
        }
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
            // If somebody else (some other process) changed the prefs
            // file behind our back, we reload it.  This has been the
            // historical (if undocumented) behavior.
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }
    
    // 获取文件缓存,以提高效率
    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;
    }

getSharedPreferences(File, int)方法内,其首先通过getSharedPreferencesCacheLocked()方法获取缓存,若缓存不存在则直接生成一个SharedPreferencesImpl实例并添加到缓存中。由此可知,SharedPreferences的实现类是SharedPreferencesImpl。至此,SharedPreference创建完成。

写入

SharedPreference通过编辑器莫斯进行修改,其edit()方法返回一个EditorImpl实例。

以添加String为例

/* SharedPreferencesImpl.EditorImpl */

public final class EditorImpl implements Editor {
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final Map<String, Object> mModified = Maps.newHashMap();

    ...
    public Editor putString(String key, @Nullable String value) {
        synchronized (mLock) {
            mModified.put(key, value);
            return this;
        }
    }
    
    ...
    
    public boolean commit() {
        long startTime = 0;

        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }

        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");
            }
        }
        notifyListeners(mcr);
        return mcr.writeToDiskResult;
    }
}

通过putString方法将变更提交到mModified变量中,之后通过commit()方法将变更正式生效。可以看到在commit方法中,首先通过commitToMemory()方法获得一个MemoryCommitResult实例,并将其作为参数传递到SharedPreferencesImpl.this.enqueueDiskWrite()中。并通过mcr中的变量writtenToDiskLatch(CountDownLatch实例)实现同步。下面观察commitToMemory()enqueueDiskWrite()方法

// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;

    synchronized (SharedPreferencesImpl.this.mLock) {
        // We optimistically don't make a deep copy until
        // a memory commit comes in when we're already
        // writing to disk.
        if (mDiskWritesInFlight > 0) {
            // We can't modify our mMap as a currently
            // in-flight write owns it.  Clone it before
            // modifying it.
            // noinspection unchecked
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;
        mDiskWritesInFlight++;

        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        synchronized (mLock) {
            boolean changesMade = false;
                if (mClear) {
                if (!mMap.isEmpty()) {
                    changesMade = true;
                    mMap.clear();
                }
                mClear = false;
            }

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

                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }

            mModified.clear();

            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }

            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

首先看commitToMemory()方法,其核心内容在中间的同步代码块中,在这里将改变SharedPreferenceImpl的变量mMap也就是内存中保存的数据。并返回MemoryCommitResult作为结果。之后该结果会被用到enqueueDiskWrite()中。

    /**
     * Enqueue an already-committed-to-memory result to be written
     * to disk.
     *
     * They will be written to disk one-at-a-time in the order
     * that they're enqueued.
     *
     * @param postWriteRunnable if non-null, we're being called
     *   from apply() and this is the runnable to run after
     *   the write proceeds.  if null (from a regular commit()),
     *   then we're allowed to do this disk write on the main
     *   thread (which in addition to reducing allocations and
     *   creating a background thread, this has the advantage that
     *   we catch them in userdebug StrictMode reports to convert
     *   them where possible to apply() ...)
     */
    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final boolean isFromSyncCommit = (postWriteRunnable == null);

        final Runnable writeToDiskRunnable = new Runnable() {
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };

        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }

        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

在该方法中,其尝试通过writeToFile()方法将变更确切的写入到硬盘中,该方法将map数据以xml格式写入到目标文件,并最终通过MemoryCommitResult的方法setDiskWriteResult(boolean, boolean)方法记录结果。其方法源码如下

        void setDiskWriteResult(boolean wasWritten, boolean result) {
            this.wasWritten = wasWritten;
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }

简单易懂,设置好结果后通过countDown()方法通知调用线程解除阻塞,接下来旧按照流程返回写入结果了。

读取数据

SharedPreferencesImpl类的构造器中会先尝试从硬盘读取一份内容保存在mMap中。之后的所有读取操作基本都是直接获取mMap的数据。

    public Map<String, ?> getAll() {
        synchronized (mLock) {
            awaitLoadedLocked();
            //noinspection unchecked
            return new HashMap<String, Object>(mMap);
        }
    }

    @Nullable
    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

由源码可知,其通过同步代码块实现并发,并用awaitLoadedLocked()方法确保数据准备完毕。getAll()方法则返回的是mMap的拷贝以防数据源污染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值