1. 程式人生 > >SharedPreferences的使用和原理分析

SharedPreferences的使用和原理分析

簡介

SharedPreferences一般用來儲存一些簡單的資料型別,比如int,String,Boolean

SharedPreferences的內部使用ArrayMap鍵值對的形式來臨時儲存資料,最終ArrayMap的資料會通過IO流寫入到XML檔案中

這個XML檔案在手機中的位置是: /data/data/shared_prefs/

1,使用

//獲取SharedPreferences 檔名稱,模式
SharedPreferences sf = getSharedPreferences("demo",Context.MODE_PRIVATE);
SharedPreferences.Editor mEditor = sf.edit();
//儲存
mEditor.putString("name","leidong");
mEditor.putBoolean("man",true);
mEditor.putInt("age",35);
//同步儲存到xml檔案, //mEditor.apply();另開執行緒將資料儲存在xml
mEditor.commit();

//````````````````````````````
//獲取資料
String name = sf.getString("name","");
boolean man = sf.getBoolean("man",true);
int age = sf.getInt("age",0);

Mode:

//私有模式。XML檔案每次都覆蓋寫入
public static final int MODE_PRIVATE = 0x0000;
//檔案開放讀寫許可權。不安全,官方已經遺棄這個用法
public static final int MODE_WORLD_READABLE = 0x0001;
public static final int MODE_WORLD_WRITEABLE = 0x0002;

//新增模式。檢查檔案是否存在,存在就往檔案追加內容,不存在就建立新的檔案。
public static final int MODE_APPEND = 0x8000
//跨程序模式,官方已經遺棄這個用法
public static final int MODE_MULTI_PROCESS = 0x0004;

2,SharedPreferences 的獲取

  //Activity中 name預設為類名
 public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }

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

Activity,Context,ContextWrapper,ContextImpl之間的關係

ContextImpl.java

 private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

//存放xml檔案 File
private ArrayMap<String, File> mSharedPrefsPaths;
 
public SharedPreferences getSharedPreferences(String name, int mode) {

        ......
        
        //xml檔案
        File file;
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            //如果檔案不存在,建立檔案bignqie儲存在mSharedPrefsPaths中
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
}

public File getSharedPreferencesPath(String name) {
    return makeFilename(getPreferencesDir(), name + ".xml");
}

private File makeFilename(File base, String name) {
   if (name.indexOf(File.separatorChar) < 0) {
     return new File(base, name);
   }
     throw new IllegalArgumentException(
            "File " + name + " contains a path separator");
}
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) {

                ......

                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
       
               ......

        return sp;
    }

    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }

        final String packageName = getPackageName();
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }

        return packagePrefs;
    }
SharedPreferencesImpl.java
//標記作用,xml是否已經被載入到記憶體中
private boolean mLoaded = false; 
//建構函式,初始化,建立備份檔案mBackupFile ,讀取磁碟中的xml檔案
SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);//備份檔案
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    startLoadFromDisk();
}
//起一個新執行緒,載入磁碟的xml檔案內容
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}
//
private void loadFromDisk() {
    synchronized (mLock) {
        //如果xml已經被載入過,直接返回
        if (mLoaded) {
            return;
        }
        //如果備份存在,說明之前讀寫操作被中斷,把mBackupFile當成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;
    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);//讀取xml中的內容到記憶體中,賦值給map
            } 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;//標記為已經載入過xml檔案
        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) {
                    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中儲存的鍵值對資料
public int getInt(String key, int defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();//如果mLoaded為false,執行緒會一直處於等待的狀態
        Integer v = (Integer)mMap.get(key);//獲取讀取到記憶體中的值(mMap鍵值對)
        return v != null ? v : defValue;
    }
}

SharedPreferences.Editor對資料的儲存操作

private final Map<String, Object> mModified = new HashMap<>();

public Editor edit() {
   
    //如果該執行緒獲取到了mLock物件鎖,但是mLoaded為false,也就是載入xml過程沒結束,那麼執行緒會一直等待
    synchronized (mLock) {
        awaitLoadedLocked();
    }

    return new EditorImpl();
}
//如果mLoaded為false,執行緒會一直處於等待的狀態
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);
    }
}

//儲存的鍵值對資料只是儲存到了內部類Editor的mModified集合中
public Editor putInt(String key, int value) {
    synchronized (mEditorLock) {
        mModified.put(key, value);
        return this;
    }
}

public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
    //把內部類Editor的mModified集合中的資料儲存到SharedPreferencesImpl類的mMap集合中。
    MemoryCommitResult mcr = commitToMemory();
    //把mMap集合中的資料寫入到xml檔案中
    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;
}

public void apply() {
    final long startTime = System.currentTimeMillis();
    //把內部類Editor的mModified集合中的資料儲存到SharedPreferencesImpl類的mMap集合中。
    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);
            }
        };
    //把mMap集合中的資料寫入到xml檔案中
    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);//通知註冊監聽者
}
//把mMap集合中的資料寫入到xml檔案中。
private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    //如果是apply()形式,則postWriteRunnable != null,isFromSyncCommit 為true。
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr, isFromSyncCommit);//寫入磁碟xml的操作被封裝在新起的執行緒中
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    // Typical #commit() path with fewer allocations, doing a write on
    // the current thread.
    //如果是commit()的形式,且當前沒有寫磁碟任務(mDiskWritesInFlight == 1),則直接呼叫
    //writeToDiskRunnable.run()執行writeToFile()寫入操作,不起新執行緒。
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
    //如果是apply()的形式,所有的執行緒都加入到QueuedWork中,以佇列的形式儲存,逐個執行緒啟動
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}