SharedPreferences源码解析

前言

文章中部分地方SharedPreferences会简写成SP,先抛出几个问题:

  • SP存储的是什么文件,存储在哪个位置?
  • SP是线程安全的吗?
  • SP是如何保证数据安全的?
  • 使用SP有哪些问题?
  • SP会把数据加载到内存中吗?
  • 首次使用SP和第二次使用SP,关于加载数据这块会有哪些不同?
  • 使用SP存储json会有问题吗?
  • SP有没有备份机制?
  • SP可以跨进程吗?

上面问题有笔者亲身经历过的面试题,也有网上找的,确实如果没有看过SP源码的话,第一次面对这些问题真的会一脸懵逼。下面我们结合源码看下SP,顺便也找找这些问题的答案。

关于SP一些基础的使用和问题可以看我以前的一篇文章:Android数据存储之SharedPreferences详细总结

基本使用

写入:

SharedPreferences settings = getSharedPreferences("test", MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString("name", "张飞");
editor.putInt("age", 18);
//editor.commit();
editor.apply();

读取:

SharedPreferences settings = getSharedPreferences("test", MODE_PRIVATE);
String name = settings.getString("name","");
int age = settings.getInt("age",0);

源码分析

使用入口

首先我们去看SharedPreferences.java代码,SharedPreferences仅仅是一个接口,并没有实现相关的代码。那具体的实现在哪里呢?我们去看getSharedPreferences(“test”, MODE_PRIVATE)方法是怎么获取到SharedPreferences的

//ContextWrapper.java
public SharedPreferences getSharedPreferences(String name, int mode) {
    return mBase.getSharedPreferences(name, mode);
}

ContextWrapper交给了mBase调用getSharedPreferences,对Context体系比较熟悉的同学应该知道这个mBase是一个ContextImpl,我们直接去看ContextImpl

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<>();
        }
        //在SharedPreferences详细总结文章中有说SharedPreferences存储的是文件
        //这里就是查找或者生成那个文件,mSharedPrefsPaths是一个ArrayMap
        //用于缓存文件路径和文件
        file = mSharedPrefsPaths.get(name);
        if (file == null) {
            //没有找到,根据路径生成文件并存储在mSharedPrefsPaths中
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
        }
    }
    return getSharedPreferences(file, mode);
}

下面接着去看getSharedPreferences方法,传递进去了文件和使用SharedPreferences的模式

public SharedPreferences getSharedPreferences(File file, int mode) {
    //SharedPreferences真正的实现
    SharedPreferencesImpl sp;
    //线程安全的获取
    synchronized (ContextImpl.class) {
        //还是从缓存中取,文件为键,SharedPreferencesImpl为值
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) {
            //检查mode,Android N及以上MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已经不支持
            checkMode(mode);
            if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
                if (isCredentialProtectedStorage()
                        && !getSystemService(UserManager.class)
                                .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                    throw new IllegalStateException("SharedPreferences in credential encrypted "
                            + "storage are not available until after user is unlocked");
                }
            }
            //为空的话创建并写入缓存
            sp = new SharedPreferencesImpl(file, mode);
            cache.put(file, sp);
            return sp;
        }
    }
    //Android 11及以上不支持使用MODE_MULTI_PROCESS
    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;
}

可见核心逻辑都在SharedPreferencesImpl,下面接着去看SharedPreferencesImpl的代码

SharedPreferencesImpl

SharedPreferencesImpl构造函数

SharedPreferencesImpl(File file, int mode) {
    //传入的对应路径的file赋值给mFile
    mFile = file;
    //看名字是一个备份用的文件
    mBackupFile = makeBackupFile(file);
    //记录模式
    mMode = mode;
    mLoaded = false;
    //mMap设置为空
    mMap = null;
    mThrowable = null;
    //从存储中加载
    startLoadFromDisk();
}

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            //new 了一个线程去加载文件
            loadFromDisk();
        }
    }.start();
}
private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }
    ...

    Map<String, Object> map = null;
    StructStat stat = null;
    Throwable thrown = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                //根据文件生成BufferedInputStream流,用于读取
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                //XmlUtils从流中读取内容并写入map,会生成一个HashMap
                map = (Map<String, Object>) XmlUtils.readMapXml(str);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                //关闭流
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
        // An errno exception means the stat failed. Treat as empty/non-existing by
        // ignoring.
    } catch (Throwable t) {
        thrown = t;
    }

    synchronized (mLock) {
        mLoaded = true;
        mThrowable = thrown;

        // It's important that we always signal waiters, even if we'll make
        // them fail with an exception. The try-finally is pretty wide, but
        // better safe than sorry.
        try {
            if (thrown == null) {
                if (map != null) {
                    //在synchronized代码块中把上面加载好的map赋值给mMap
                    //至此数据就从xml文件中读取到内存中了
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    //大概是读取失败,赋值一个空的HashMap
                    mMap = new HashMap<>();
                }
            }
            // In case of a thrown exception, we retain the old map. That allows
            // any open editors to commit and store updates.
        } catch (Throwable t) {
            mThrowable = t;
        } finally {
            mLock.notifyAll();
        }
    }
}

总结:

  1. 关于XML解析这块我们只需要知道,XML解析方式有Dom,Sax,还有Pull解析。这里使用了Pull解析。
  2. xml读取是耗时的,如果我们在SP中存储大量的数据必然会导致耗时增加
  3. 首次会加载到内存中,第二次就不会了
  4. 在内存中存储格式为Map<String, Object>数据形式

edit()

SharedPreferences并不允许直接putInt,putString必须先通过edit()获取一个Editor,然后调用Editor的各种putInt,putString等方法,然后在调用apply或者commit方法执行存入操作。下面我们分析下edit()方法

public Editor edit() {
    // TODO: remove the need to call awaitLoadedLocked() when
    // requesting an editor.  will require some work on the
    // Editor, but then we should be able to do:
    //
    //      context.getSharedPreferences(..).edit().putString(..).apply()
    //
    // ... all without blocking.
    synchronized (mLock) {
        //可以看下面代码,就是等待加载完成,也就是等待loadFromDisk执行完成
        awaitLoadedLocked();
    }
    //返回一个新建的EditorImpl
    return new EditorImpl();
}

private void awaitLoadedLocked() {
    if (!mLoaded) {
        // Raise an explicit StrictMode onReadFromDisk for this
        // thread, since the real read will be in a different
        // thread and otherwise ignored by StrictMode.
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}

EditorImpl

Editor真正的实现是EditorImpl

public final class EditorImpl implements Editor {
    //Editor的锁
    private final Object mEditorLock = new Object();

    //修改的数据
    @GuardedBy("mEditorLock")
    private final Map<String, Object> mModified = new HashMap<>();

    @GuardedBy("mEditorLock")
    private boolean mClear = false;

    @Override
    public Editor putString(String key, @Nullable String value) {
        //put 方法加锁
        synchronized (mEditorLock) {
            mModified.put(key, value);
            return this;
        }
    }

    //各种put方法,所有的put方法都类似都加锁
    ...
    

    @Override
    public Editor remove(String key) {
        //根据键移除一个,加锁
        synchronized (mEditorLock) {
            mModified.put(key, this);
            return this;
        }
    }

    @Override
    public Editor clear() {
        //清空,加锁
        synchronized (mEditorLock) {
            mClear = true;
            return this;
        }
    }
    
    @Override
    public void apply() {...}
    
    @Override
    public void commit() {...}
}

apply

我们都知道Editor有两个写入数据的方法,一个是apply异步写入非阻塞,一个是commit是阻塞的。但是现实情况真的是这样吗,我们来看看。

@Override
public void apply() {
    final long startTime = System.currentTimeMillis();
    //执行commitToMemory,并返回一个MemoryCommitResult包括写入内存的结果
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    //MemoryCommitResult有一个writtenToDiskLatch
                    //只有setDiskWriteResult方法执行的时候才会countDown
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };

    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                QueuedWork.removeFinisher(awaitCommit);
            }
        };
    //执行写入存储的操作,后面分析
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    // Okay to notify the listeners before it's hit disk
    // because the listeners should always get the same
    // SharedPreferences instance back, which has the
    // changes reflected in memory.
    notifyListeners(mcr);
}

//看方法名,顾名思义是先提交到内存中
private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    boolean keysCleared = false;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;
    //以当前SharedPreferencesImpl对象的mLock为锁
    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
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;
        //在执行的写操作数+1
        mDiskWritesInFlight++;

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

        synchronized (mEditorLock) {
            boolean changesMade = false;

            if (mClear) {
                //是清空的话,这里对mapToWriteToDisk执行清空操作
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                keysCleared = true;
                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 (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    //值为null,清空这个key
                    mapToWriteToDisk.remove(k);
                } else {
                    if (mapToWriteToDisk.containsKey(k)) {
                        Object existingValue = mapToWriteToDisk.get(k);
                        //前后值相同,不处理
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    //更新到内存中
                    mapToWriteToDisk.put(k, v);
                }

                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }
            //将mModified清空,以备下次使用
            mModified.clear();

            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }

            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    //返回一个MemoryCommitResult,包裹结果,包括监听者和写入后的map
    return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
            listeners, mapToWriteToDisk);
}

MemoryCommitResult

private static class MemoryCommitResult {
    final long memoryStateGeneration;
    final boolean keysCleared;
    @Nullable final List<String> keysModified;
    @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
    final Map<String, Object> mapToWriteToDisk;
    final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

    @GuardedBy("mWritingToDiskLock")
    volatile boolean writeToDiskResult = false;
    boolean wasWritten = false;

    private MemoryCommitResult(long memoryStateGeneration, boolean keysCleared,
            @Nullable List<String> keysModified,
            @Nullable Set<OnSharedPreferenceChangeListener> listeners,
            Map<String, Object> mapToWriteToDisk) {
        this.memoryStateGeneration = memoryStateGeneration;
        this.keysCleared = keysCleared;
        this.keysModified = keysModified;
        this.listeners = listeners;
        this.mapToWriteToDisk = mapToWriteToDisk;
    }

    void setDiskWriteResult(boolean wasWritten, boolean result) {
        this.wasWritten = wasWritten;
        writeToDiskResult = result;
        writtenToDiskLatch.countDown();
    }
}
private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    //isFromSyncCommit是否来自同步的commit,很明显不是,这里postWriteRunnable
    //不为空
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    //写入xml文件
                    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) {
            //如果是来自commit的话,看mDiskWritesInFlight是否是1
            //为1也就表示没有其他的commit或者apply在执行,只有当前
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            //直接执行writeToDiskRunnable
            writeToDiskRunnable.run();
            return;
        }
    }
    //这里是apply,所以来到这里通过QueuedWork添加这个writeToDiskRunnable
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

关于QueuedWork还有一个隐藏的问题,等会在其他里面介绍。

//QueuedWork.java
public static void queue(Runnable work, boolean shouldDelay) {
    //新建一个HandlerThread,其实就是放在子线程中执行
    Handler handler = getHandler();

    synchronized (sLock) {
        sWork.add(work);

        if (shouldDelay && sCanDelay) {
            handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
        } else {
            handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
        }
    }
}


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

总结:

  1. apply会放在子线程执行写入存储
  2. Editor写入,读取内存,读取存储,等有三把锁保证线程安全。所以SP是线程安全的。

commit

看完了apply的分析,其实commit剩余的已经没啥东西了

@Override
public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
    //和apply一样先写入内存
    MemoryCommitResult mcr = commitToMemory();

    //执行enqueueDiskWrite,注意后面参数传入的是null表示直接执行
    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;
}

总结:
commit和apply都会先写入内存,然后再写入存储,区别是写入存储commit是在当前线程,apply是在子线程执行。

writeToFile

前面分析了apply和commit并没有深入看文件的写入文件这块,继续看下

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    long startTime = 0;
    long existsTime = 0;
    long backupExistsTime = 0;
    long outputStreamCreateTime = 0;
    long writeTime = 0;
    long fsyncTime = 0;
    long setPermTime = 0;
    long fstatTime = 0;
    long deleteTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
    //文件是否存在
    boolean fileExists = mFile.exists();

    if (DEBUG) {
        existsTime = System.currentTimeMillis();

        // Might not be set, hence init them to a default value
        backupExistsTime = existsTime;
    }

    // Rename the current file so it may be used as a backup during the next read
    //如果文件存在的话将当前文件改名,以备下次读取的时候当备份文件使用
    if (fileExists) {
        boolean needsWrite = false;

        // Only need to write if the disk state is older than this commit
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            if (isFromSyncCommit) {
                //来自commit的话直接设置为true
                needsWrite = true;
            } else {
                synchronized (mLock) {
                    // No need to persist intermediate states. Just wait for the latest state to
                    // be persisted.
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }

        if (!needsWrite) {
            mcr.setDiskWriteResult(false, true);
            return;
        }
        //备份文件是否存在
        boolean backupFileExists = mBackupFile.exists();

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

        if (!backupFileExists) {
            //备份文件不存在,将当前文件改名为备份文件名
            if (!mFile.renameTo(mBackupFile)) {
                //重命名失败
                Log.e(TAG, "Couldn't rename file " + mFile
                        + " to backup file " + mBackupFile);
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
            //存在直接删除当前的文件
            mFile.delete();
        }
    }

    // Attempt to write the file, delete the backup and return true as atomically as
    // possible.  If any exception occurs, delete the new file; next time we will restore
    // from the backup.
    try {
        FileOutputStream str = createFileOutputStream(mFile);

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

        if (str == null) {
            mcr.setDiskWriteResult(false, false);
            return;
        }
        //通过XmlUtils写入xml文件
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);

        writeTime = System.currentTimeMillis();

        FileUtils.sync(str);

        fsyncTime = System.currentTimeMillis();
        //关闭流
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

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

        try {
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (mLock) {
                mStatTimestamp = stat.st_mtim;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }

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

        // Writing was successful, delete the backup file if there is one.
        //写入成功,备份文件已经没有存在的必要了,下次需要的时候读取新的或者直接重命名当前文件
        mBackupFile.delete();

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

        mDiskStateGeneration = mcr.memoryStateGeneration;
        //设置写入结果
        mcr.setDiskWriteResult(true, true);

        ...

        long fsyncDuration = fsyncTime - writeTime;
        mSyncTimes.add((int) fsyncDuration);
        mNumSync++;

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

其他

SP备份机制

其实看完writeToFile这个方法我们应该已经知道了SP的备份机制,每次读取SP的时候都会创建一个被备份文件,每次写入文件前,先看下这个备份文件是否存在,不存在将当前指向xml的文件改名为备份文件,正常SharedPreferencesImpl构造方法里面已经根据这个xml文件创建了备份文件。
writeToFile方法执行成功会把备份文件删除,这个时候xml文件已经是新的了。异常情况写入文件失败,比如掉电,强制退出等。写入新的SP会失败那么下次loadFromDisk会把备份文件设置为mFile,也就是读取旧的内容,保证不至于失败导致数据被污染。

private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }
    ...

}

QueuedWork隐藏问题

刚才说apply还隐藏了一个刺客在哪里呢,在QueuedWork里面,这里参考了字节的文章今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待

public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
        int configChanges, PendingTransactionActions pendingActions, String reason) {
    ...
    // Make sure any pending writes are now committed.
    if (r.isPreHoneycomb()) {
        //Android 11以前的版本,等待waitToFinish执行完成,所以这里会导致Activity一直等待
        //QueuedWork把任务执行完成,也就是隐藏会导致卡顿或者ANR
        QueuedWork.waitToFinish();
    }
    ...
}

存储json

参考请不要滥用SharedPreference

还有一些童鞋,他在sp里面存json或者HTML;这么做不是不可以,但是,如果这个json相对较大,那么也会引起sp读取速度的急剧下降。
JSON或者HTML格式存放在sp里面的时候,需要转义,这样会带来很多&这种特殊符号,sp在解析碰到这个特殊符号的时候会进行特殊的处理,引发额外的字符串拼接以及函数调用开销。而JSON本来就是可以用来做配置文件的,你干嘛又把它放在sp里面呢?多此一举。下面我写个demo验证一下。

参考链接

https://juejin.cn/post/6961961476047568932?searchId=20240504152606909D2A140930E1DACD5E
https://juejin.cn/post/6884505736836022280?searchId=20240503211729C6AE6135B0EDD86DCF0C
https://weishu.me/2016/10/13/sharedpreference-advices/

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值