SharedPreferences

SharedPreferences

SharedPreference 是一个轻量级的 key-value 存储框架,SharedPreference的具体实现与每个应用的上下文环境有关,每个应用有自己的单独的文件夹存放这些数据,对其他应用不可见。

SharedPreference文件都是默认存放在/data/data/< package name>/shared_prefs/目录下的。

SharedPreferences的使用

首先要通过SharedPreferences存储对象,就需要获取SharedPreferences对象,然后在存储数据的时候通过SharedPreferences对象来获取Editor对象,然后通过Editor对象来存储数据,存储完之后需要通过apply或者commit来进行保存。

//获取SharedPreferences对象,这其中第一个参数是一个自定义的字符串,需要使用者自己传入,会以这个参数创建文件夹来保存参数。第二个是一种模式,但现在其他模式都被废弃,只保留了MODE_PRIVATE这种模式,这种模式表示只有这个程序能够读写。这个SharedPreference可以通过Context,Activity和PreferenceManager中的getPreferences方法 这三种途径获取。
SharedPreferences sharedPreferences = getSharedPreferences("app", MODE_PRIVATE);
//获取Editor
SharedPreferences.Editor editor = sharedPreferences.edit();
//通过Editor来保存数据
editor.putString("name", "tom");
editor.putInt("age", 10);
//保存(还有commit方法能够保存)
editor.apply();

SharedPreferences源码分析

getSharedPreferences方法

这是getSharedPreferences方法

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

在ContextImpl是上面的mBase的实现类,在这个方法中的getSharedPreferences方法:

@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) {
        //获取SharedPreferences的第一个参数和文件是一一对应的关系
        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 SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        //根据报名来创建一个ArrayMap<File, SharedPreferencesImpl>,通过包名来获取
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        //通过文件名来获取SharedPreferencesImpl
        sp = cache.get(file);
        //如果SharedPreferencesImpl为空,就创建一个SharedPreferencesImpl
        if (sp == null) {
            //检查模式,如果模式为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;
        }
    }
    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这个类是和文件一一对应的。

SharedPreferences

这是SharedPreferences接口中的方法:

public interface SharedPreferences {
    public interface OnSharedPreferenceChangeListener {
        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
    }
    public interface Editor {
        Editor putString(String key, @Nullable String value);
        Editor putStringSet(String key, @Nullable Set<String> values);
        Editor putInt(String key, int value);
        Editor putLong(String key, long value);
        Editor putFloat(String key, float value);
        Editor putBoolean(String key, boolean value);
        Editor remove(String key);
        Editor clear();
        boolean commit();
        void apply();
    }
    Map<String, ?> getAll();
    @Nullable
    String getString(String key, @Nullable String defValue);
    @Nullable
    Set<String> getStringSet(String key, @Nullable Set<String> defValues);
    int getInt(String key, int defValue);
    long getLong(String key, long defValue);
    float getFloat(String key, float defValue);
    boolean getBoolean(String key, boolean defValue);
    boolean contains(String key);
    Editor edit();
    void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
    void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
}

SharedPreferencesImpl类

//构造方法
SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    //file的备份文件
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    //真正保存的数据
    mMap = null;
    mThrowable = null;
    //第一次创建时,会去磁盘加载数据
    startLoadFromDisk();
}

startLoadFromDisk方法:

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    //在子线程加载
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

loadFromDisk方法:

private void loadFromDisk() {
    synchronized (mLock) {
        //如果数据已经加载过了,就直接返回
        if (mLoaded) {
            return;
        }
        //如果备份文件存在,则将原来的文件删除,并将备份文件重命名为mFile
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }

    // Debugging
    if (mFile.exists() && !mFile.canRead()) {
        Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
    }

    Map<String, Object> map = null;
    StructStat stat = null;
    Throwable thrown = null;
    //将文件中的数据转化为map中的数据
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                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) {
                    //真正获取数据的map是mMap
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    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();
        }
    }
}

这里有一个mLock锁,这个锁很重要,这里获取到了锁,当SharedPreferences去操作数据的时候就会先去判断这个锁是否释放了,如果没有,那么就会等待锁释放了在执行,执行到就将数据加载到了内存中,最终保存在mMap中。

SharedPreferencesImpl的构建过程其实就是将数据从磁盘加载进内存,并保存在mMap中。
获取数据

public String getString(String key, @Nullable String defValue) {
    /*这里一进来就加了一个锁,这个锁是为了防止数据还没加载就执行到这里,那这时就需要将锁释放掉,
但又不对数据进行操作,那这个awaitLoadedLocked()就起到了这个作用,
这也就是说,数据没加载完是不能对数据进行操作的,这时就阻塞在这里*/
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

awaitLoadedLocked方法:

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

读数据的过程就是如果数据已经加载完成就直接在mMap中找到Key中对应的数据,如果没有加载则等待加载完成。

添加数据:

public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();
    }
    return new EditorImpl();
}

在这其实是返回一个EditorImpl对象,所以每次操作数据的时候尽量一次性操作多个数据,每调用一次这个方法就会重新创建一个对象。

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

put方法就是将数据传入一个集合中。

commit和apply方法:
apply方法:

//构造函数中传入1,也就说明调用一次countDown()会唤醒线程
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
public void apply() {
    final long startTime = System.currentTimeMillis();
	//先将数据同步到内存,返回的是MermoryCommitResult对象
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    // writtenToDiskLatch是一个CountDownLatch对象,除非数据全部同步到磁盘,否则这里就一直阻塞
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }

                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };
	// 这里将awaitCommit添加到QueuedWork中的目的是为了退出activity时,确保所有数据已经全部同步到磁盘中了
    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                // 数据同步完这里就会将awaitCommit从QueuedWork移除
                QueuedWork.removeFinisher(awaitCommit);
            }
        };
	// 开始执行将数据同步到磁盘中,会新开线程
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    // 这里是唤醒回调,前提是你注册了registerOnSharedPreferenceChangeListener()回调
    // 还有一个条件就是这个key是新添加的才会触发
    notifyListeners(mcr);
}

commit方法:

public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
	//将数据同步到内存,返回的是MemoryCommitResult对象
    MemoryCommitResult mcr = commitToMemory();
	// 开始执行将数据同步到磁盘中,会新开线程,这和前面的apply调用的相同的方法,参数不同。
    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");
        }
    }
    //最终会调用onSharedPreferenceChanged方法
    notifyListeners(mcr);
    // 返回文件操作的结果
    return mcr.writeToDiskResult;
}

在apply方法中首先调用的是commitToMemory方法:

//这个方法主要是将保存在临时集合中的数据合并到mMap中,返回一个MemoryCommitResult对象,将数据保存在磁盘中使用的就是这个类
private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    boolean keysCleared = false;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;

    synchronized (SharedPreferencesImpl.this.mLock) {
        if (mDiskWritesInFlight > 0) {
            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 (mEditorLock) {
            boolean changesMade = false;

            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    //如果mClear为true,则这个集合将会被清空
                    mapToWriteToDisk.clear();
                }
                keysCleared = true;
                mClear = false;
            }
			//遍历存储在临时集合中的数据,然后合并到原来的数据集合mMap中
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                if (v == this || v == null) {
                    if (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    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.clear();

            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }

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

enqueueDiskWrite方法:

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
           // 注意这里的postWriterRunnable,如果是apply()方法,传进来的参数是不为null的
           // 如果是commit(),这里传进来的为null
           //isFromSyncCommit的值决定了是立即同步还是稍后再同步                          
        final boolean isFromSyncCommit = (postWriteRunnable == null);

        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        //当没有key没有改变,则直接返回了;否则将mMap全部信息写入文件,
                        //如果写入成功则删除备份文件,如果写入失败则删除mFile
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    //apply执行
                    if (postWriteRunnable != null) {
                      //将awaitCommit添加到QueuedWork中的目的是为了退出activity时,确保所有数据已经全部同步到磁盘中了 
                      //主要是阻塞线程,数据同步完这里就会将awaitCommit从QueuedWork移除
                        postWriteRunnable.run();
                    }
                }
            };
          // 如果是commit(),那么就会执行到这里,在当前线程中同步数据
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
            // 由于commitToMemory会让mDiskWritesInFlight+1,则wasEmpty为true
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
            //调用run方法,仍然在当前线程上执行。
                writeToDiskRunnable.run();
                return;
            }
        }	
		// 如果是apply(),那么就会执行到这里。
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

从上面就可以知道commit方法就是在主线程中执行,没有重新开启新的线程去执行writeToFile方法。

但是apply方法就还需要执行QueuedWork.queue方法:

public static void queue(Runnable work, boolean shouldDelay) {
    Handler handler = getHandler();

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

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

先获取到一个handler,然后将同步数据的任务添加到了sWork这个集合中,那后面处理肯定也是去这个集合里面拿了,重点看这个getHandler():

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

这里有一个QueueWorkHandler类:

private static class QueuedWorkHandler extends Handler {
    static final int MSG_RUN = 1;

    QueuedWorkHandler(Looper looper) {
        super(looper);
    }

    public void handleMessage(Message msg) {
        if (msg.what == MSG_RUN) {
            processPendingWork();
        }
    }
}

在这个handleMessage方法中执行了processPendingWork方法:

private static void processPendingWork() {
    long startTime = 0;

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

    synchronized (sProcessingWork) {
        LinkedList<Runnable> work;

        synchronized (sLock) {
            work = sWork;
            sWork = new LinkedList<>();
			//删除所有的msg-s,因为现在要处理所有的work
            getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
        }

        if (work.size() > 0) {
            for (Runnable w : work) {
                w.run();
            }

            if (DEBUG) {
                Log.d(LOG_TAG, "processing " + work.size() + " items took " +
                        +(System.currentTimeMillis() - startTime) + " ms");
            }
        }
    }
}

当退出Activity的时候数据没同步完是会阻塞线程的。Activity生命周期都是通过ActivityThread来控制的,在ActivityThread的handleStopActivity方法中,这里控制的就是Activity的stop,在这个方法中有调用到:

public static void waitToFinish() {
    long startTime = System.currentTimeMillis();
    boolean hadMessages = false;
 
    Handler handler = getHandler();
    // 先看下Handler中是否还有等待的消息,如果有那么就移除
    synchronized (sLock) {
        if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
            // Delayed work will be processed at processPendingWork() below
            handler.removeMessages(QueuedWorkHandler.MSG_RUN);
 
            if (DEBUG) {
                hadMessages = true;
                Log.d(LOG_TAG, "waiting");
            }
        }
 
        // We should not delay any work as this might delay the finishers
        sCanDelay = false;
    }
 
    StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
    try {
        // 这里就是将本该在子线程中执行的任务直接拿到主线程中来执行了
        processPendingWork();
    } finally {
        StrictMode.setThreadPolicy(oldPolicy);
    }
 
    try {
        // 这里是为了确保所有的任务确实已经执行完了
        while (true) {
            Runnable finisher;
 
            synchronized (sLock) {
                finisher = sFinishers.poll();
            }
 
            if (finisher == null) {
                break;
            }
 
            finisher.run();
        }
    } finally {
        sCanDelay = true;
    }
 
    synchronized (sLock) {
        long waitTime = System.currentTimeMillis() - startTime;
 
        if (waitTime > 0 || hadMessages) {
            mWaitTimes.add(Long.valueOf(waitTime).intValue());
            mNumWaits++;
 
            if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
                mWaitTimes.log(LOG_TAG, "waited: ");
            }
        }
    }
}

总结

1.sp数据都保存在内存缓存mMap中,缓存路径为data/data/packagename/shared_prefs,这样也就说明sp文件不适合存储大数据,会十分浪费内存。
2.apply()写入文件操作是异步执行的,不会占用主线程资源。先保存到内存,再通过异步线程保存sp文件到磁盘,commit保存到内存跟磁盘是同步保存,所以,如果频繁保存数据的话,apply肯定要高效,优先推荐使用apply。commit()有返回值,apply()没有返回值,apply()失败了是不会报错的。保存数据时,apply()是先将数据保存到内存中,然后在子线程中将数据更新到磁盘,commit()是将数据保存到内存中,之后立即将数据同步到磁盘,这个操作是在当前线程中完成的,所以会阻塞线程,所以现在android建议使用apply(),但是使用apply()时也有需要注意的地方,如果保存数据时这个过程还没有结束,但是这时退出了activity,这时activity会先确保数据是否已经全部同步到磁盘了,如果没有,这时会两种情况,一是保存的过程正在子线中执行,这时等待就好,如果这时还没分发给子线程,那么就直接切换到主线程执行了,所以这时提交数据时也有需要注意的地方,使用SharedPreferences.Editor提交数据时,尽量在所有数据都提交后再调用apply()方法;
3.从sp中读取数据,需要等sp构造方法调用从磁盘读取数据成功后才继续往下执行,所以为了不造成阻塞,我们可以提前创建出SharedPreferences对象,而不是在使用的时候再去创建。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值