Android中SharedPreferences的理解

一、SharedPreferences的创建

通过Context的getSharedPreferences(String name, int mode)返回创建的SharedPreferences.
说明:
1. 无论在任一线程、通过任一Activity或Context调用,相同的name只会创建一个SharedPreferences实例。实例的创建是线程安全的,通过互斥访问Synchronized(ContextImpl.class)实现;
2. SharedPreferences的mode以第1次调用getSharedPreferences(String name, int mode)时的参数为准,如果后面传入的mode参数与第1次不一致,则后面的参数无效。
3. 已创建的SharedPreferences保存在ArrayMap< String, ArrayMap< String, SharedPreferencesImpl > >中,即实现了从packageName到preferenceName,再到SharedPreferences实例的映射。

google官方的API注释如下:

/**
 * Retrieve and hold the contents of the preferences file 'name', returning
 * a SharedPreferences through which you can retrieve and modify its
 * values.  Only one instance of the SharedPreferences object is returned
 * to any callers for the same name, meaning they will see each other's
 * edits as soon as they are made.
 *
 * @param name Desired preferences file. If a preferences file by this name
 * does not exist, it will be created when you retrieve an
 * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
 * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
 * default operation, {@link #MODE_WORLD_READABLE}
 * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
 *
 * @return The single {@link SharedPreferences} instance that can be used
 *         to retrieve and modify the preference values.
 *
 * @see #MODE_PRIVATE
 * @see #MODE_WORLD_READABLE
 * @see #MODE_WORLD_WRITEABLE
 */
public SharedPreferences getSharedPreferences(String name, int mode);
二、SharedPreferences的设计与实现

2.1 SharedPreferences的构造方法
构造方法主要工作是:
加载磁盘文件/data/data/package_name/shared_prefs/xxx.xml到内存、解析xml文件、并保存属性名和属性值到HashMap中。

final class SharedPreferencesImpl implements SharedPreferences {
    private final File mFile;
    private final File mBackupFile;
    private final int mMode;
    private Map<String, Object> mMap;
    private boolean mLoaded = false;

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

    private void startLoadFromDisk() {
        synchronized (this) {
            mLoaded = false;
        }
        //启动一个子线程执行加载xml文件, 主线程直接返回.
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                synchronized (SharedPreferencesImpl.this) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }

    private void loadFromDiskLocked() {
        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 map = null;
        StructStat stat = null;
        try {
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16*1024);
                    map = XmlUtils.readMapXml(str);
                } catch (XmlPullParserException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (IOException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
        }
        mLoaded = true;
        if (map != null) {
            mMap = map;
            mStatTimestamp = stat.st_mtime;
            mStatSize = stat.st_size;
        } else {
            mMap = new HashMap<String, Object>();
        }
        //加载完成, 通知主线程.
        notifyAll();
    }

2.2 EditorImpl的设计与实现
EditorImpl用于缓存要提交到SharedPreferences的数据,调用者可以通过该Editor实例将要修改的内容提交到SharedPreferences对应的xml文件中。

/**
 * Interface used for modifying values in a {@link SharedPreferences}
 * object.  All changes you make in an editor are batched, and not copied
 * back to the original {@link SharedPreferences} until you call {@link #commit}
 * or {@link #apply}
 */
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();
}

注意:
Editor类是线程安全的,支持同一个Editor实例的在多个线程间的调用;
edit()方法会检测构造方法中异步加载xml操作是否完成,如果未完成,则阻塞等待。

public Editor edit() {
    synchronized (this) {
        awaitLoadedLocked();
    }

    return new EditorImpl();
}

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 {
            wait();
        } catch (InterruptedException unused) {
        }
    }
}

2.3 提交Editor的中的数据到xml文件中
(1) 用于描述提交结果的类MemoryCommitResult

private static class MemoryCommitResult {
    public boolean changesMade;  // any keys different?
    public List<String> keysModified;  // may be null
    public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
    public Map<?, ?> mapToWriteToDisk;
    public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
    public volatile boolean writeToDiskResult = false;

    public void setDiskWriteResult(boolean result) {
        writeToDiskResult = result;
        writtenToDiskLatch.countDown();
    }
}

(2) 提交修改的数据到xml中

public boolean commit() {
    MemoryCommitResult mcr = commitToMemory();
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        mcr.writtenToDiskLatch.await();        
    } catch (InterruptedException e) {
        return false;
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;                
}

public void apply() {
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
        public void run() {
            try {
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException ignored) {
            }
        }
    };

    QueuedWork.add(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
        public void run() {
            awaitCommit.run();
            QueuedWork.remove(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);
}

private MemoryCommitResult commitToMemory() {
    MemoryCommitResult mcr = new MemoryCommitResult();
    synchronized (SharedPreferencesImpl.this) {
        if (mDiskWritesInFlight > 0) {
            //mMap的数据在其他线程中被修改,可能被同一Editor实例在其它线程修改,
            //也可能被其它的Editor实例在其它线程修改。
            mMap = new HashMap<String, Object>(mMap);
        }
        mcr.mapToWriteToDisk = mMap;
        mDiskWritesInFlight++;

        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            mcr.keysModified = new ArrayList<String>();
            mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }

        //对当前的Editor实例加锁,修改mModified、mMap和mcr.
        synchronized (this) {
            if (mClear) {
                if (!mMap.isEmpty()) {
                    mcr.changesMade = true;
                    mMap.clear();
                }
                mClear = false;
            }

            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();

                if (v == this || v == null) {
                    if (!mMap.containsKey(k)) {
                        continue;
                    }
                    mMap.remove(k);
                } else {
                    if (mMap.containsKey(k)) {
                        Object existingValue = mMap.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mMap.put(k, v);
                }

                mcr.changesMade = true;
                if (hasListeners) {
                    mcr.keysModified.add(k);
                }
            }

            mModified.clear();
        }
    }
    return mcr;
}

private void enqueueDiskWrite(final MemoryCommitResult mcr,
        final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr);
                }
                synchronized (SharedPreferencesImpl.this) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

    final boolean isFromSyncCommit = (postWriteRunnable == null);

    // Typical #commit() path with fewer allocations, doing a write on
    // the current thread.
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (SharedPreferencesImpl.this) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }

    QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

2.4 将HashMap中的内容写入xml文件中

// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
    // Rename the current file so it may be used as a backup during the next read
    if (mFile.exists()) {
        if (!mcr.changesMade) {
            // If the file already exists, but no changes were
            // made to the underlying map, it's wasteful to
            // re-write the file.  Return as if we wrote it out.
            mcr.setDiskWriteResult(true);
            return;
        }
        if (!mBackupFile.exists()) {
            if (!mFile.renameTo(mBackupFile)) {
                Log.e(TAG, "Couldn't rename file " + mFile
                      + " to backup file " + mBackupFile);
                mcr.setDiskWriteResult(false);
                return;
            }
        } else {
            mFile.delete();
        }
    }

    // Attempt to write the file, delete the backup and return true as atomically as
    // possible.  If any exception occurs, delete the new file; next time we will restore
    // from the backup.
    try {
        FileOutputStream str = createFileOutputStream(mFile);
        if (str == null) {
            mcr.setDiskWriteResult(false);
            return;
        }
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
        try {
            final StructStat stat = Os.stat(mFile.getPath());
            //持有SharedPreferencesImpl.this锁.
            synchronized (this) {
                mStatTimestamp = stat.st_mtime;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
            // Do nothing
        }
        // Writing was successful, delete the backup file if there is one.
        mBackupFile.delete();
        mcr.setDiskWriteResult(true);
        return;
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }
    // Clean up an unsuccessfully written file
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值