SharedPreference是Android中的轻量级的存储方式,将键值对写入xml文件中,并保存在/data/data/package_name/shared_prefs路径下。

1、SharedPreferences.java

它是一个interface,如下:

public interface SharedPreferences {    public interface OnSharedPreferenceChangeListener {        void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);    }    public interface Editor {        /**         * 写入键值对         */        Editor putString(String key, String value);        Editor putStringSet(String key, Set values);        Editor putInt(String key, int value);        Editor putBoolean(String key, boolean value);        /**         * 清除键值为key的preference         */        Editor remove(String key);        /**         * 清除preference所有内容         */        Editor clear();        /**         *commit将其preference同步写入持久存储;         *写入成功后返回true         *如果不关心返回值,并在应用主线程中使用,请考虑使用apply()以替换commit()         */        boolean commit();        /**         *apply启动异步将更改提交到磁盘;没有返回值,如果失败,将不会收到任何通知。         */        void apply();    }    /**     * Retrieve all values from the preferences.     */    Map getAll();    /**     * 获取数据     */    String getString(String key, String defValue);    Set getStringSet(String key, Set defValues);    int getInt(String key, int defValue);    boolean getBoolean(String key, boolean defValue);    void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);    void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);}

2、SharedPreference的获取

通过 Context#getSharedPreferences 方式获取。

在Context.java中,getSharedPreferences()是一个抽象方法,

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

具体实现在ContextImpl.java中。

这里先说一下这个方法中的两个参数:

name:preference文件名;

mode:有MODE_PRIVATE,MODE_WORLD_READABLE,MODE_WORLD_WRITEABLE,MODE_MULTI_PROCESS。其中MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE在AndroidN以后版本不能使用,若使用会抛SecurityException异常;MODE_MULTI_PROCESS在某些Android版本上支持不好,未来可能会Deprecated,设置了此mode,每次getSharedPreferences时都会重新检查并加载磁盘文件。(如需精确的跨进程数据管理,应使用ContentProvider.)

ContextImpl.getSharedPreferences():

    @Override    public SharedPreferences getSharedPreferences(String name, int mode) {        // 判空处理 "null.xml".        if (mPackageInfo.getApplicationInfo().targetSdkVersion <                Build.VERSION_CODES.KITKAT) {            if (name == null) {                name = "null";            }        }        File file;        synchronized (ContextImpl.class) {            if (mSharedPrefsPaths == null) {                mSharedPrefsPaths = new ArrayMap<>();            }            file = mSharedPrefsPaths.get(name);            if (file == null) {                //创建name.xml文件                file = getSharedPreferencesPath(name);                mSharedPrefsPaths.put(name, file);            }        }        return getSharedPreferences(file, mode);    }    @Override    public SharedPreferences getSharedPreferences(File file, int mode) {        SharedPreferencesImpl sp;        synchronized (ContextImpl.class) {            final ArrayMap cache = getSharedPreferencesCacheLocked();            sp = cache.get(file);            if (sp == null) {                //检查mode                checkMode(mode);                if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {                    if (isCredentialProtectedStorage()                            && !getSystemService(UserManager.class)                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {                        //AndroidO及以后版本,sp是ce加密,在用户第一次解锁之前无法使用                        throw new IllegalStateException("SharedPreferences in credential encrypted "                                + "storage are not available until after user is unlocked");                    }                }                //创建sp,保存到缓存中并返回                sp = new SharedPreferencesImpl(file, mode);                cache.put(file, sp);                return sp;            }        }        if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {            //多进程模式需要重新读取文件            sp.startReloadIfChangedUnexpectedly();        }        return sp;    }

Sharedpreferences的实现类是SharedPreferencesImpl类,构造方法如下:

    SharedPreferencesImpl(File file, int mode) {        mFile = file; //磁盘上的xml文件        mBackupFile = makeBackupFile(file); //xml.bak备份文件,用于写入失败时的恢复        mMode = mode;        mLoaded = false;        mMap = null; //用于内存中缓存数据,getXxx从这里获取数据        mThrowable = null;        startLoadFromDisk();    }

构造方法中涉及到startLoadFromDisk(),用于从磁盘读取文件:

    private void startLoadFromDisk() {        synchronized (mLock) {            mLoaded = false;        }        //启动了一个名为"SharedPreferencesImpl-load"从磁盘加载数据        new Thread("SharedPreferencesImpl-load") {            public void run() {                loadFromDisk();            }        }.start();    }    private void loadFromDisk() {        synchronized (mLock) {            //若已经读取过,直接返回,不从磁盘读取            if (mLoaded) {                return;            }            if (mBackupFile.exists()) {                mFile.delete();                mBackupFile.renameTo(mFile);            }        }        Map 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);                    //将xml文件转换为map                    map = (Map) XmlUtils.readMapXml(str);                } catch (Exception e) {                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);                } finally {                    IoUtils.closeQuietly(str);                }            }        } catch (ErrnoException e) {        } catch (Throwable t) {            thrown = t;        }        synchronized (mLock) {            //设置mLoaded为true,表示已经读取过,下次getSharedPreference时不再从磁盘读取            mLoaded = true;            mThrowable = thrown;            try {                if (thrown == null) {                    if (map != null) {                        //将map赋给全局变量mMap                        mMap = map;                        mStatTimestamp = stat.st_mtim;                        mStatSize = stat.st_size;                    } else {                        mMap = new HashMap<>();                    }                }            } catch (Throwable t) {                mThrowable = t;            } finally {                //已经加载完毕,释放mLock锁,通知其他等待线程。                mLock.notifyAll();            }        }    }

3、SharedPreference获取数据方法

以getString(String, String)为例,代码如下:

    public String getString(String key, @Nullable String defValue) {        synchronized (mLock) {            //阻塞等待sp从磁盘读取xml到内存之后再get数据            awaitLoadedLocked();            String v = (String)mMap.get(key);            //v为null,则返回默认值            return v != null ? v : defValue;        }    }    private void awaitLoadedLocked() {        ...;        while (!mLoaded) {            try {                mLock.wait();            } catch (InterruptedException unused) {            }        }        if (mThrowable != null) {            throw new IllegalStateException(mThrowable);        }    }

awaitLoadedLocked是等待xml文件加载完毕;如果首次执行getSharedPreferences后立即调用getXxx ,若加载还未完成(mLoaded为false), getXxx 会卡在awaitLoadedLocked,一旦加载完毕,则加载线程会通过notifyAll通知所有在 awaitLoadedLocked中等待的线程,getXxx 就能够返回了。sp最好不要加载过大的文件,否则阻塞时间过长。

4、SharedPreference写入及移除数据

4.1、写入数据需要Editor对象,先来看一下SharedPreferencesImpl.java中Editor对象的获取:

    public Editor edit() {        synchronized (mLock) {            awaitLoadedLocked();        }        //每次调用edit()都会创建一个EditorImpl对象        return new EditorImpl();    }

EditorImpl类中有两个成员变量mModified和mClear,其中mModified表示每次putXxx后的改变的配置项,mClear表示清空配置项,但是只清了SharedPreferenceImpl的mMap。

现在来看EditorImpl中的put,remove方法:

        public Editor putString(String key, @Nullable String value) {            synchronized (mEditorLock) {                //将键值对存入mModified                mModified.put(key, value);                return this;            }        }        public Editor remove(String key) {            synchronized (mEditorLock) {                //remove中存入value是this                mModified.put(key, this);                return this;            }        }

可见,修改值只会将其暂存到mModified中,在Editor中所做的所有更改直到调用了commit()apply()才会设置到mMap(内存)和xml文件(磁盘)中去。

4.2、commit及apply实现

commit()方法:

        public boolean commit() {            //完成内存写入,并返回一个MemoryCommitResult对象            MemoryCommitResult mcr = commitToMemory();            //放入磁盘写入队列            SharedPreferencesImpl.this.enqueueDiskWrite(                mcr, null /* sync write on this thread okay */);            try {                //等待其他线程执行完毕 writtenToDiskLatch.countdown减为0后从await方法返回                mcr.writtenToDiskLatch.await();            } catch (InterruptedException e) {                return false;            } finally {            }            //通知监听者            notifyListeners(mcr);            //返回提交结果            return mcr.writeToDiskResult;        }

内存写入:

        private MemoryCommitResult commitToMemory() {            long memoryStateGeneration;            List keysModified = null;            Set listeners = null;            Map mapToWriteToDisk;            synchronized (SharedPreferencesImpl.this.mLock) {                if (mDiskWritesInFlight > 0) {                    //此时正在写入,无法修改                    mMap = new HashMap(mMap);                }                mapToWriteToDisk = mMap;                mDiskWritesInFlight++;                //设置监听处理                boolean hasListeners = mListeners.size() > 0;                if (hasListeners) {                    keysModified = new ArrayList();                    listeners = new HashSet(mListeners.keySet());                }                synchronized (mEditorLock) {                    //标记是否发生改变                    boolean changesMade = false;                    //如果调用了clear方法,会先执行clear操作                    if (mClear) {                        if (!mapToWriteToDisk.isEmpty()) {                            changesMade = true;                            mapToWriteToDisk.clear();                        }                        mClear = false;                    }                    for (Map.Entry e : mModified.entrySet()) {                        String k = e.getKey();                        Object v = e.getValue();                        //remove方法里面存放的value是this,如果是this就去执行remove操作                        if (v == this || v == null) {                            if (!mapToWriteToDisk.containsKey(k)) {                                continue;                            }                            mapToWriteToDisk.remove(k);                        } else {                            if (mapToWriteToDisk.containsKey(k)) {                                Object existingValue = mapToWriteToDisk.get(k);                                //未发生修改,则跳过                                if (existingValue != null && existingValue.equals(v)) {                                    continue;                                }                            }                            //发生了修改,则存入map中                            mapToWriteToDisk.put(k, v);                        }                        //标记发生了修改                        changesMade = true;                        if (hasListeners) {                            keysModified.add(k);                        }                    }                    //将修改的map:mModified置为null                    mModified.clear();                    if (changesMade) {                        //提交到内存的次数                        mCurrentMemoryStateGeneration++;                    }                    memoryStateGeneration = mCurrentMemoryStateGeneration;                }            }            //将提交到内存次数,发生修改的集合,监听者及map封装成MemoryCommitResult并返回            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,                    mapToWriteToDisk);        }

内存修改完成后,接着加入磁盘写入队列:

    private void enqueueDiskWrite(final MemoryCommitResult mcr,                                  final Runnable postWriteRunnable) {        //commit为true,apply为false        final boolean isFromSyncCommit = (postWriteRunnable == null);        final Runnable writeToDiskRunnable = new Runnable() {                @Override                public void run() {                    synchronized (mWritingToDiskLock) {                        //写入文件                        writeToFile(mcr, isFromSyncCommit);                    }                    synchronized (mLock) {                        mDiskWritesInFlight--;                    }                    //apply操作时此runnable不为null,apply操作时该runnable执行                    if (postWriteRunnable != null) {                        postWriteRunnable.run();                    }                }            };        //commit操作时才执行        if (isFromSyncCommit) {            boolean wasEmpty = false;            synchronized (mLock) {                wasEmpty = mDiskWritesInFlight == 1;            }            if (wasEmpty) {                //writeToDiskRunnable执行,执行写入文件                writeToDiskRunnable.run();                return;            }        }        //apply操作时才会执行        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);    }

由上述代码段看出commit()是同步的,通过enqueueDiskWrite()调用 writeToFile()方法将数据写到磁盘。

apply()方法:

apply方法利用QueuedWork实现异步操作.

        public void apply() {            final long startTime = System.currentTimeMillis();            final MemoryCommitResult mcr = commitToMemory();            final Runnable awaitCommit = new Runnable() {                    @Override                    public void run() {                        try {                            //线程等待                            mcr.writtenToDiskLatch.await();                        } catch (InterruptedException ignored) {                        }                    }                };            //将awaitCommit加入QueuedWork            QueuedWork.addFinisher(awaitCommit);            Runnable postWriteRunnable = new Runnable() {                    @Override                    public void run() {                        awaitCommit.run();                        QueuedWork.removeFinisher(awaitCommit);                    }                };            //放入写队列            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);            //通知监听者            notifyListeners(mcr);        }

通过阅读源码,整理总结如下:

1、sharedpreference是一个使用二级缓存(磁盘用xml文件,内存使用hashmap)的key-value存储方式

2、sp写入文件不要太大,否则会导致卡顿等

3、xml解析速度较慢,空间占用大,因此对于高频变化的存储场景不适用

4、不要多次调用edit(),每次调用都会创建一个editor对象

5、不要多次调用apply()或commit(),多次写入的话应该批量修改,最后一次执行调用

6、尽可能早地调用getSharedPreference,这样可以确保在调用getXxx或putXxx时数据已经完成写入内存

7、sharedpreference的跨进程模式只是在获取sharedpreference时重新从磁盘读取文件,使用此模式时每次都要通过getSharedPreferences获取sharedpreference变量,重新load数据,这样会引起其他线程等待耗时,降低内存读写性能。跨进程使用它是不可靠的。

 

更多相关文章

  1. android 科大讯飞语音唤醒demo
  2. Android(安卓)线程中更新UI
  3. Android应用程序全屏显示
  4. Android(安卓)ApiDemos示例解析(23):App->Intents
  5. android中退出整个app应用程序
  6. Android全屏(包含3种隐藏顶部状态栏及标题栏和一种隐藏Android(安
  7. android 2.2 apidemos 赏析笔记 6
  8. android之helloworld详解
  9. Android双击事件拦截方法

随机推荐

  1. android 测试
  2. 【Android】选项卡使用
  3. Android(安卓)webview HitTestResult识别
  4. Android Launcher3去除应用列表,二级菜单,
  5. ViewPagerIndicator使用
  6. Android Studio 调试工具常见问题
  7. React Native與Android交互
  8. android 获取以太网的连接状态
  9. Android中如何获得本机号码信息
  10. 整理一下Android中的ListView