SharedPreferences小探
想到个问题,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使用时,单例,存在缓存(二次加载更快速),线程安全,但是也可能会操作不成功,而且多进程操作无法保证结果。