Android SharePreferences持久化机制

Posted Nipuream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android SharePreferences持久化机制相关的知识,希望对你有一定的参考价值。

android SharePreferences持久化机制

SharePreferences作为Android开发中比较常见的持久化工具类,很多人平时工作中没有对它起到足够的重视,包括我。但是互联网公司面试好像很喜欢考察这个知识点,感觉有必要对它进行一个梳理和总结。

一、加载 getSharedPreferences

@Override
public SharedPreferences getSharedPreferences(File file, int mode) 
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) 
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        if (sp == null) 
            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");
                
            
            //1. 如果没有缓存则会创建sp的实现类
            sp = new SharedPreferencesImpl(file, mode);
            cache.put(file, sp);
            return sp;
        
    
    
    //2. 多进程的情况下
    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;


//如果有修改意图(pendIntent),则更新失败
void startReloadIfChangedUnexpectedly() 
    synchronized (mLock) 
        // TODO: wait for any pending writes to disk?
        if (!hasFileChangedUnexpectedly()) 
            return;
        
        startLoadFromDisk();
    

会有缓存类对sp实现类对象进行缓存,如果没有则新建;多进程情况下会判断此文件是否有修改的任务类,如果有修改的意图(pendIntent)则会打开失败(可以先简单这么理解)。下面看看sp的初始化工作:

SharedPreferencesImpl(File file, int mode) 
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    startLoadFromDisk();


private void startLoadFromDisk() 
    synchronized (mLock) 
        mLoaded = false;
    
    new Thread("SharedPreferencesImpl-load") 
        public void run() 
            loadFromDisk();
        
    .start();


private void loadFromDisk() 
    
    synchronized (mLock) 
        if (mLoaded) 
            return;
        
        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;
    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;
    
  //... 加载失败的处理

这里会创建个 backup 文件,主要后续操作失败了不影响原来的文件,操作成功则将原来的文件删除掉,将备份文件修改为目标文件。

二、commit apply的区别

@Override
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;


private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) 
    final boolean isFromSyncCommit = (postWriteRunnable == null);

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

    //commit的话,则还是在当前线程执行,不会传递给QueueWork的。
    if (isFromSyncCommit) 
        boolean wasEmpty = false;
        synchronized (mLock) 
            wasEmpty = mDiskWritesInFlight == 1;
        
        if (wasEmpty) 
            writeToDiskRunnable.run();
            return;
        
    

    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

commit的操作其实还在在当前线程执行完成持久化操作(写入文件后续再说)。

@Override
public void apply() 
    final long startTime = System.currentTimeMillis();

    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() 
        @Override
        public void run() 
            try 
                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);

apply操作其实是将持久化操作交给QueueWork的线程去处理的,并将等待的操作放到finish的队列中。这样做的好处是不会阻塞主线程,但是呢,也会存在一定的问题,这也是面试中喜欢问的地方。

@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
                               PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) 
    final ActivityClientRecord r = mActivities.get(token);
    r.activity.mConfigChangeFlags |= configChanges;

    final StopInfo stopInfo = new StopInfo();
    performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
                             reason);

    if (localLOGV) Slog.v(
        TAG, "Finishing stop of " + r + ": show=" + show
        + " win=" + r.window);

    updateVisibility(r, show);

    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) 
        QueuedWork.waitToFinish();
    

    stopInfo.setActivity(r);
    stopInfo.setState(r.state);
    stopInfo.setPersistentState(r.persistentState);
    pendingActions.setStopInfo(stopInfo);
    mSomeActivitiesChanged = true;

问题就出在这,当快速打开关闭一个Activity的时候,假设sp中存的内容比较多,在执行stop的生命周期方法的时候,会等待这些apply的任务完成,这时候很容易导致Anr的产生。解决的办法就是在stop之前将QueueWork中finish队列中的消息全都移除掉。

三、写入文件操作

![sp](d:\\Users\\Administrator\\Desktop\\Android学习总结\\Android基础\\sp.png)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;

        //这里会检查文件修改时间和内存修改时间点(apply,commit)
        if (mDiskStateGeneration < mcr.memoryStateGeneration) 
            if (isFromSyncCommit) 
                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();
        
    

    //和前文所说一致,会先修改备份文件,如果修改成功,则更新原文件,若更新失败,删除
    try 
        FileOutputStream str = createFileOutputStream(mFile);

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

        if (str == null) 
            mcr.setDiskWriteResult(false, false);
            return;
        
        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);

        if (DEBUG) 
            Log.d(TAG, "write: " + (existsTime - startTime) + "/"
                  + (backupExistsTime - startTime) + "/"
                  + (outputStreamCreateTime - startTime) + "/"
                  + (writeTime - startTime) + "/"
                  + (fsyncTime - startTime) + "/"
                  + (setPermTime - startTime) + "/"
                  + (fstatTime - startTime) + "/"
                  + (deleteTime - startTime));
        

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

        if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) 
            mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
        

        return;
     catch (XmlPullParserException e) 
        Log.w(TAG, "writeToFile: Got exception:", e);
     catch (IOException e) 
        Log.w(TAG, 以上是关于Android SharePreferences持久化机制的主要内容,如果未能解决你的问题,请参考以下文章

Android SharePreferences持久化机制

Android - SharePreferences数据存储

Android - SharePreferences数据存储的简单使用

Android应用Preference相关及源代码浅析(SharePreferences篇)

Android 存储:SharePreferences存储List<Map<String,Object>>类型数组

Android 存储:SharePreferences存储List<Map<String,Object>>类型数组