源码学习->18SharedPreferences

一、SharedPreferences构建:

1.1 ContextImpl.getSharedPreferences:

private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;

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

        final String packageName = getPackageName();
        ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
            sSharedPrefs.put(packageName, packagePrefs);
        }
        /**
         * api19以下支持name = null;
         */
        if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";
            }
        }

        sp = packagePrefs.get(name);
        if (sp == null) {
            /**
             * 1. 一个name对应一个SharedPreferencesImpl;
             * 2. 将File缓存在SharedPreferencesImpl中;
             */
            File prefsFile = getSharedPrefsFile(name);     模块<1.2>
            sp = new SharedPreferencesImpl(prefsFile, mode);     模块<1.3>
            packagePrefs.put(name, sp);
            return sp;
        }
    }
    return sp;
}
1.2 ContextImpl.getSharedPrefsFile:
/**
 * 构建对应的File文件;
 */
@Override
public File getSharedPrefsFile(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);
    }
}
1.3 SharedPreferencesImpl构造函数:
SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    /**
     * 1. 结合模块<1.4>可知, 在使用Sp时, 需要先根据name将指定目录下的<name>文件中的内容全部
     *    加载进内存中, 然后在进行读写操作;
     * 2. 数据量越大, 加载就越耗时, 所以这也就是为什么一直强调尽量不要在一个sp中存储大量数据;
     */
    startLoadFromDisk();    模块<1.4>
}
1.4 SharedPreferencesImpl.startLoadFromDisk:
private void startLoadFromDisk() {
    synchronized (this) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            synchronized (SharedPreferencesImpl.this) {
                /**
                 * File中的数据加载进Map是在子线程中进行, 与putXXX操作如何进行同步?  模块<二>
                 */
                loadFromDiskLocked();
            }
        }
    }.start();
}

private void loadFromDiskLocked() {
    if (mLoaded) {
        return;
    }
    if (mBackupFile.exists()) {
        mFile.delete();
        mBackupFile.renameTo(mFile);
    }
    Map map = null;
    StructStat stat = null;
    stat = Os.stat(mFile.getPath());
    if (mFile.canRead()) {
        /**
         * 数据量越大, 这一块就越耗时, 进行get/edit操作时如果加载没完成会处于阻塞状态;
         */
        BufferedInputStream str = null;
        str = new BufferedInputStream(new FileInputStream(mFile), 16*1024);
        map = XmlUtils.readMapXml(str);
    }
    mLoaded = true;
    if (map != null) {
        mMap = map;
        mStatTimestamp = stat.st_mtime;
        mStatSize = stat.st_size;
    } else {
        mMap = new HashMap<String, Object>();
    }
    notifyAll();
}

二、SharedPreferencesImpl.edit:

public Editor edit() {
    /**
     * 这里的插入操作与初始化Sp时磁盘数据加载到内存共用同一把锁, 如果初始化时的数据加载
     * 耗时过长, 这里就会被一直阻塞;
     */
    synchronized (this) {
        awaitLoadedLocked();
    }
    return new EditorImpl();
}

private void awaitLoadedLocked() {
    while (!mLoaded) {
        wait();
    }
}

三、EditorImpl.putXXX:

public Editor putInt(String key, int value) {
    synchronized (this) {
        /**
         * 对于指定name的Sp, 由于sp与EditorImpl都是单例, 在结合这里的锁对象, 所以Sp是线程安全的;
         */
        mModified.put(key, value);
        return this;
    }
}

四、EditorImpl.commit:

4.1 EditorImpl.commit:
public boolean commit() {
    /**
     * 将数据从缓存集合mModified读取到mMap中, 然后将mMap赋值给MCR;
     */
    MemoryCommitResult mcr = commitToMemory();     模块<4.2>
    /**
     * 将数据从mcr.mapToWriteToDisk中写入到磁盘中;
     */
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);     模块<4.3>
    try {
        mcr.writtenToDiskLatch.await();   
    } catch (InterruptedException e) {
        return false;
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}
4.2 EditorImpl.commitToMemory:
private MemoryCommitResult commitToMemory() {
    MemoryCommitResult mcr = new MemoryCommitResult();
    synchronized (SharedPreferencesImpl.this) {
        if (mDiskWritesInFlight > 0) {
            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());
        }
        synchronized (this) {
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                ...
                mMap.put(k, v);
                mcr.changesMade = true;
                if (hasListeners) {
                    mcr.keysModified.add(k);
                }
            }
            mModified.clear();
        }
    }
    return mcr;
}
  • 简而言之, EditorImpl.putXXX方法将数据缓存进mModified中, 然后调用commit将mModified数据读取到mMap中, 然后赋值给MemoryCommitResult.mapToWriteToDisk, 同时情况mModified;
4.3 SharedPreferencesImpl.enqueueDiskWrite:
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
        public void run() {
            // 全局共用一把锁, 写入磁盘操作是线程同步的;
            synchronized (mWritingToDiskLock) {
                writeToFile(mcr);       模块<4.4>
            }
            synchronized (SharedPreferencesImpl.this) {
                mDiskWritesInFlight--;
            }
            if (postWriteRunnable != null) {
                postWriteRunnable.run();
            }
        }
    };
    /**
     * commit方式 ---> isFromSyncCommit = true;
     * apply方式 ---> isFromSyncCommit = false;
     */
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (SharedPreferencesImpl.this) {
            /**
             * 1. 结合模块<4.2>可知, mDiskWritesInFlight默认为0, 调用一次commit, 触发一次
             *    mDiskWritesInFlight++ = 1操作, 然后在writeToDiskRunnable.run中又重置
             *    mDiskWritesInFlight = 0操作;
             * 2. 所以调用一次commit时, mDiskWritesInFlight == 1, empty = true;
             */
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            /**
             * 调用commit时会跳转到这里, 触发writeToDiskRunnable.run;
             */
            writeToDiskRunnable.run();
            return;
        }
    }
    /**
     * 如果是apply方式, 则会执行到这里, 可知, writeToDiskRunnable被运行在子线程中, 然后
     * 触发writeToFile在子线程中执行;
     */
    QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);    模块<5.2>
}
4.4 SharedPreferencesImpl.writeToFile:
private void writeToFile(MemoryCommitResult mcr) {
    if (mFile.exists()) {
        if (!mcr.changesMade) {
            /**
             * 正如文章所说, changesMade默认为false, mModified数据被写入到mMap时将changesMade
             * 置为true, 所以如果数据没有发生变化, 则不对File做任何处理;
             */
            mcr.setDiskWriteResult(true);
            return;
        }
    }
    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);
        final StructStat stat = Os.stat(mFile.getPath());
        synchronized (this) {
            mStatTimestamp = stat.st_mtime;
            mStatSize = stat.st_size;
        }
        mBackupFile.delete();
        mcr.setDiskWriteResult(true);
        return;
    } 
    catch (XmlPullParserException e) {...} 
    catch (IOException e) {...}
    mcr.setDiskWriteResult(false);
}
五、EditorImpl.apply:
5.1 EditorImpl.apply:
public void apply() {
    /**
     * 将mModified数据写入到MemoryCommitResult中;
     */
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
        public void run() {
            mcr.writtenToDiskLatch.await();
        }
    };
    QueuedWork.add(awaitCommit);
    Runnable postWriteRunnable = new Runnable() {
        public void run() {
            awaitCommit.run();
            QueuedWork.remove(awaitCommit);
        }
    };
    /**
     * 将MemoryCommitResult中缓存的数据写入到磁盘中;
     */
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);    模块<4.3>
    notifyListeners(mcr);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容