SharedPreferences 详解

文章出处:http://blog.csdn.net/shift_wwx

请转载的朋友标明出处~~


前言:之前 SharedPreference 实现不同进程间的数据共享  中谈了一下数据共享,最近在用到这一块的时候感觉有点模糊,索性就看一下source code,分享一下~


一、使用

分两个函数:

    private void getDataFromSP() {
        SharedPreferences sp = getSharedPreferences("sp_name", Context.MODE_PRIVATE);
        int value1 = sp.getInt("field_1", 0);
        String value2 = sp.getString("field_2", "");
    }
    private void setDataToSp() {
        SharedPreferences sp = getSharedPreferences("sp_name", Context.MODE_PRIVATE);
        Editor editor = sp.edit();
        editor.putInt("field_1", 0);
        editor.putString("field_2", "");
        editor.commit();
    }

二、source code 详解

1、获取对象

code 中拖过getSharePreferences 来获取SharedPreferences 的对象,调用过程不说了,说一下实现:

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            if (sSharedPrefs == null) {
                sSharedPrefs = new ArrayMap>();
            }

            final String packageName = getPackageName();
            ArrayMap packagePrefs = sSharedPrefs.get(packageName);
            if (packagePrefs == null) {
                packagePrefs = new ArrayMap();
                sSharedPrefs.put(packageName, packagePrefs);
            }

            // At least one application in the world actually passes in a null
            // name.  This happened to work because when we generated the file name
            // we would stringify it to "null.xml".  Nice.
            if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                    Build.VERSION_CODES.KITKAT) {
                if (name == null) {
                    name = "null";
                }
            }

            sp = packagePrefs.get(name);
            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                packagePrefs.put(name, sp);
                return sp;
            }
        }
        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;
    }
通过code可以看到几点:

1)一个package 可以创建多个xml,每个xml代表一个SharedPreferences 对象

2)如果xml 没有的时候会新建文件,新建一个SharedPreferences 对象

3)参数mode是 Context.MODE_MULTI_PROCESS 的时候,可以允许多进程操作

4)SharedPreferences 真正实现的class 是SharedPreferencesImpl


2、读取信息

SharedPreferences 从source code看,是一个interface:

public interface SharedPreferences {}
所以是需要实现的,实现它的地方之前讲过是 SharedPreferencesImpl.java

几个get 的方法:

    public int getInt(String key, int defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            Integer v = (Integer)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
    public Set getStringSet(String key, Set defValues) {
        synchronized (this) {
            awaitLoadedLocked();
            Set v = (Set) mMap.get(key);
            return v != null ? v : defValues;
        }
    }
    public boolean getBoolean(String key, boolean defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            Boolean v = (Boolean)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
从code 看,是在SharedPreferencesImpl 中有个mMap的东西:

private Map mMap;
step 1 中讲过在getSharedPreferences的时候会新建一个SharedPreferencesImpl 对象:

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

    private void startLoadFromDisk() {
        synchronized (this) {
            mLoaded = false;
        }
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                synchronized (SharedPreferencesImpl.this) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }
最终:

map = XmlUtils.readMapXml(str);

3、存储数据

使用的时候SharedPreferences 用内部类Editor,可是Source code 中Editor 也是interface ,实现的地方是EditorImpl

    public final class EditorImpl implements Editor {
        private final Map mModified = Maps.newHashMap();
        private boolean mClear = false;
    ......
    ......
    }
可以看到会新建一个Map,所以,在使用的时候一直put,就是存进这个map 中。

几个put 的接口:

public Editor putString(String key, String value) {
    synchronized (this) {
        mModified.put(key, value);
        return this;
    }
}
public Editor putStringSet(String key, Set values) {
    synchronized (this) {
        mModified.put(key,
                (values == null) ? null : new HashSet(values));
        return this;
    }
}
public Editor putInt(String key, int value) {
    synchronized (this) {
        mModified.put(key, value);
        return this;
    }
}
public Editor putLong(String key, long value) {
    synchronized (this) {
        mModified.put(key, value);
        return this;
    }
}
public Editor putFloat(String key, float value) {
    synchronized (this) {
        mModified.put(key, value);
        return this;
    }
}
public Editor putBoolean(String key, boolean value) {
    synchronized (this) {
        mModified.put(key, value);
        return this;
    }
}
put 完后最终调用commit 来完成存储:

        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;
        }
存储过程暂时不介绍,注意的是最后的Listener:

private void notifyListeners(final MemoryCommitResult mcr) {
    if (mcr.listeners == null || mcr.keysModified == null ||
        mcr.keysModified.size() == 0) {
        return;
    }
    if (Looper.myLooper() == Looper.getMainLooper()) {
        for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
            final String key = mcr.keysModified.get(i);
            for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                if (listener != null) {
                    listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                }
            }
        }
    } else {
        // Run this function on the main thread.
        ActivityThread.sMainThreadHandler.post(new Runnable() {
                public void run() {
                    notifyListeners(mcr);
                }
            });
    }
}
onSharedPreferenceChanged 是肯定要在主线程中调用的。


4、Editor 其他的一些接口

1)clear

public Editor clear() {
    synchronized (this) {
        mClear = true;
        return this;
    }
}
在commit 的时候会判断 mClear:

if (mClear) {
    if (!mMap.isEmpty()) {
        mcr.changesMade = true;
        mMap.clear();
    }
    mClear = false;
    
    ......
    ......    
}
2)remove
public Editor remove(String key) {
    synchronized (this) {
        mModified.put(key, this);
        return this;
    }
}

刚开始看到这个很奇怪,功能是remove的,为什么会put呢,因为是两份map操作,这个临时的map,即mModified不知道mMap 里面有没有这个,所以,就put 了this。

在commit的时候会比较每个元素,当碰到了this 之后会确认mMap 里面是否存在,没有就算了,有就remove掉:

if (v == this || v == null) {
    if (!mMap.containsKey(k)) {
        continue;
    }
    mMap.remove(k);
}
3)apply

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);
}
其实完成和commit 的功能差不多。

两者的区别:

/**
 * Commit your preferences changes back from this Editor to the
 * {@link SharedPreferences} object it is editing.  This atomically
 * performs the requested modifications, replacing whatever is currently
 * in the SharedPreferences.
 *
 * 

Note that when two editors are modifying preferences at the same * time, the last one to call apply wins. * *

Unlike {@link #commit}, which writes its preferences out * to persistent storage synchronously, {@link #apply} * commits its changes to the in-memory * {@link SharedPreferences} immediately but starts an * asynchronous commit to disk and you won't be notified of * any failures. If another editor on this * {@link SharedPreferences} does a regular {@link #commit} * while a {@link #apply} is still outstanding, the * {@link #commit} will block until all async commits are * completed as well as the commit itself. * *

As {@link SharedPreferences} instances are singletons within * a process, it's safe to replace any instance of {@link #commit} with * {@link #apply} if you were already ignoring the return value. * *

You don't need to worry about Android component * lifecycles and their interaction with apply() * writing to disk. The framework makes sure in-flight disk * writes from apply() complete before switching * states. * *

The SharedPreferences.Editor interface * isn't expected to be implemented directly. However, if you * previously did implement it and are now getting errors * about missing apply(), you can simply call * {@link #commit} from apply(). */

区别1:

commit 是有返回值的,表示是否成功;而apply是没有的。

由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

区别2:

commit 同步提交到disk 中;而apply 是立马存在内存中,然后异步去存储到disk中,中间是收不到失败的信息的。

不用去担心线程安全问题, 因为如果一个其他的线程去commit,而刚好有一个还没有完成的apply,commit会被阻塞到异步线程提交完成。







你可能感兴趣的:(android)