首页 > 技术文章 > SharedPreferences小探

asi24 2014-09-20 02:16 原文

想到个问题,SharedPreferences有没有使用缓存相关的技术?会不会操作不成功?线程安全么?进程操作可靠吗?

 

首先想到的是Activity里面的:

public abstract SharedPreferences getSharedPreferences(String name, int mode);

  

android.content.Context中,我们首先找到简单的解释:

The single SharedPreferences instance that can be used to retrieve and modify the preference values.

关键字:单例

 

//ContextImpl.java
724 @Override 725 public SharedPreferences getSharedPreferences(String name, int mode) { 726 SharedPreferencesImpl sp; 727 synchronized (ContextImpl.class) { 728 if (sSharedPrefs == null) { 729 sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); 730 } 731 732 final String packageName = getPackageName(); 733 ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); 734 if (packagePrefs == null) { 735 packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); 736 sSharedPrefs.put(packageName, packagePrefs); 737 } 738 739 // At least one application in the world actually passes in a null 740 // name. This happened to work because when we generated the file name 741 // we would stringify it to "null.xml". Nice. 742 if (mPackageInfo.getApplicationInfo().targetSdkVersion < 743 Build.VERSION_CODES.KITKAT) { 744 if (name == null) { 745 name = "null"; 746 } 747 } 748 749 sp = packagePrefs.get(name); 750 if (sp == null) { 751 File prefsFile = getSharedPrefsFile(name); 752 sp = new SharedPreferencesImpl(prefsFile, mode); 753 packagePrefs.put(name, sp); 754 return sp; 755 } 756 } 757 if ((mode & Context.MODE_MULTI_PROCESS) != 0 || 758 getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { 759 // If somebody else (some other process) changed the prefs 760 // file behind our back, we reload it. This has been the 761 // historical (if undocumented) behavior. 762 sp.startReloadIfChangedUnexpectedly(); 763 } 764 return sp; 765 }

 如果设置Context.MODE_MULTI_PROCESS,sp只保证每次尝试加载最新修改的文件。

认识1:getSharedPreferences通过Map保证SharedPreferences单例。

 

//SharedPreferencesImpl.java
52 final class More ...SharedPreferencesImpl implements SharedPreferences {
53     private static final String TAG = "SharedPreferencesImpl";
54     private static final boolean DEBUG = false;
55 
56     // Lock ordering rules:
57     //  - acquire SharedPreferencesImpl.this before EditorImpl.this
58     //  - acquire mWritingToDiskLock before EditorImpl.this
59 
60     private final File mFile;
61     private final File mBackupFile;
62     private final int mMode;
63 
64     private Map<String, Object> mMap;     // guarded by 'this'

mMap是不是editor的缓存?


//SharedPreferencesImpl.java

273 public Editor edit() { 274 // TODO: remove the need to call awaitLoadedLocked() when 275 // requesting an editor. will require some work on the 276 // Editor, but then we should be able to do: 277 // 278 // context.getSharedPreferences(..).edit().putString(..).apply() 279 // 280 // ... all without blocking. 281 synchronized (this) { 282 awaitLoadedLocked(); 283 } 284 285 return new EditorImpl(); 286 } 303 public final class EditorImpl implements Editor { 304 private final Map<String, Object> mModified = Maps.newHashMap(); 305 private boolean mClear = false; 306 307 public Editor putString(String key, String value) { 308 synchronized (this) { 309 mModified.put(key, value); 310 return this; 311 } 312 }

认识2:每次edit会new EditorImpl ,并且EditorImpl 自带mModified缓存。

388        // Returns true if any changes were made
389        private MemoryCommitResult commitToMemory() {
390            MemoryCommitResult mcr = new MemoryCommitResult();
391            synchronized (SharedPreferencesImpl.this) {
392                // We optimistically don't make a deep copy until
393                // a memory commit comes in when we're already
394                // writing to disk.
395                if (mDiskWritesInFlight > 0) {
396                    // We can't modify our mMap as a currently
397                    // in-flight write owns it.  Clone it before
398                    // modifying it.
399                    // noinspection unchecked
400                    mMap = new HashMap<String, Object>(mMap);
401                }
402                mcr.mapToWriteToDisk = mMap;
403                mDiskWritesInFlight++;
404
405                boolean hasListeners = mListeners.size() > 0;
406                if (hasListeners) {
407                    mcr.keysModified = new ArrayList<String>();
408                    mcr.listeners =
409                            new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
410                }
411
412                synchronized (this) {
413                    if (mClear) {
414                        if (!mMap.isEmpty()) {
415                            mcr.changesMade = true;
416                            mMap.clear();
417                        }
418                        mClear = false;
419                    }
420
421                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
422                        String k = e.getKey();
423                        Object v = e.getValue();
424                        if (v == this) {  // magic value for a removal mutation
425                            if (!mMap.containsKey(k)) {
426                                continue;
427                            }
428                            mMap.remove(k);
429                        } else {
430                            boolean isSame = false;
431                            if (mMap.containsKey(k)) {
432                                Object existingValue = mMap.get(k);
433                                if (existingValue != null && existingValue.equals(v)) {
434                                    continue;
435                                }
436                            }
437                            mMap.put(k, v);
438                        }
439
440                        mcr.changesMade = true;
441                        if (hasListeners) {
442                            mcr.keysModified.add(k);
443                        }
444                    }
445
446                    mModified.clear();
447                }
448            }
449            return mcr;
450        }

452        public boolean commit() {
453            MemoryCommitResult mcr = commitToMemory();
454            SharedPreferencesImpl.this.enqueueDiskWrite(
455                mcr, null /* sync write on this thread okay */);
456            try {
457                mcr.writtenToDiskLatch.await();
458            } catch (InterruptedException e) {
459                return false;
460            }
461            notifyListeners(mcr);
462            return mcr.writeToDiskResult;
463        }
commit时,editor先把mModified同步到mMap,然后再写入File。
认识3:editor.commit是线程安全的。
564    private void writeToFile(MemoryCommitResult mcr) {
565        // Rename the current file so it may be used as a backup during the next read
566        if (mFile.exists()) {
567            if (!mcr.changesMade) {
568                // If the file already exists, but no changes were
569                // made to the underlying map, it's wasteful to
570                // re-write the file.  Return as if we wrote it
571                // out.
572                mcr.setDiskWriteResult(true);
573                return;
574            }
575            if (!mBackupFile.exists()) {
576                if (!mFile.renameTo(mBackupFile)) {
577                    Log.e(TAG, "Couldn't rename file " + mFile
578                          + " to backup file " + mBackupFile);
579                    mcr.setDiskWriteResult(false);
580                    return;
581                }
582            } else {
583                mFile.delete();
584            }
585        }
586
587        // Attempt to write the file, delete the backup and return true as atomically as
588        // possible.  If any exception occurs, delete the new file; next time we will restore
589        // from the backup.
590        try {
591            FileOutputStream str = createFileOutputStream(mFile);
592            if (str == null) {
593                mcr.setDiskWriteResult(false);
594                return;
595            }
596            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
597            FileUtils.sync(str);
598            str.close();
599            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
600            try {
601                final StructStat stat = Libcore.os.stat(mFile.getPath());
602                synchronized (this) {
603                    mStatTimestamp = stat.st_mtime;
604                    mStatSize = stat.st_size;
605                }
606            } catch (ErrnoException e) {
607                // Do nothing
608            }
609            // Writing was successful, delete the backup file if there is one.
610            mBackupFile.delete();
611            mcr.setDiskWriteResult(true);
612            return;
613        } catch (XmlPullParserException e) {
614            Log.w(TAG, "writeToFile: Got exception:", e);
615        } catch (IOException e) {
616            Log.w(TAG, "writeToFile: Got exception:", e);
617        }
618        // Clean up an unsuccessfully written file
619        if (mFile.exists()) {
620            if (!mFile.delete()) {
621                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
622            }
623        }
624        mcr.setDiskWriteResult(false);
625    }

认识4:存在mBackupFile,Rename the current file so it may be used as a backup during the next read。

认识5:SharedPreferences在写文件的时候是可能会出现问题的,如果对结果的依赖性很强,需求设置监听确认设置是否正确(回调在主线程)。

97     private void More ...loadFromDiskLocked() {
98         if (mLoaded) {
99             return;
100        }
101        if (mBackupFile.exists()) {
102            mFile.delete();
103            mBackupFile.renameTo(mFile);
104        }
105
106        // Debugging
107        if (mFile.exists() && !mFile.canRead()) {
108            Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
109        }
110
111        Map map = null;
112        StructStat stat = null;
113        try {
114            stat = Libcore.os.stat(mFile.getPath());
115            if (mFile.canRead()) {
116                BufferedInputStream str = null;
117                try {
118                    str = new BufferedInputStream(
119                            new FileInputStream(mFile), 16*1024);
120                    map = XmlUtils.readMapXml(str);
121                } catch (XmlPullParserException e) {
122                    Log.w(TAG, "getSharedPreferences", e);
123                } catch (FileNotFoundException e) {
124                    Log.w(TAG, "getSharedPreferences", e);
125                } catch (IOException e) {
126                    Log.w(TAG, "getSharedPreferences", e);
127                } finally {
128                    IoUtils.closeQuietly(str);
129                }
130            }
131        } catch (ErrnoException e) {
132        }
133        mLoaded = true;
134        if (map != null) {
135            mMap = map;
136            mStatTimestamp = stat.st_mtime;
137            mStatSize = stat.st_size;
138        } else {
139            mMap = new HashMap<String, Object>();
140        }
141        notifyAll();
142    }

认识6:SharedPreferences在读文件的时候也可能会出现问题,一旦出问题,新建一个空的map,相当于丢弃之前的配置重写,结合如果设置Context.MODE_MULTI_PROCESS,如果文件重新加载出问题,那么之前的配置丢失。

总结:SharedPreferences使用时,单例,存在缓存(二次加载更快速),线程安全,但是也可能会操作不成功,而且多进程操作无法保证结果。

推荐阅读