SharedPreference源码分析及注意要点

#SharedPreference相关文章虽然对,作为学习笔记使用,有问题希望能够尽情提出,共同交流
蒋八九

Sp注意事项:

  1. SDK 24 (android 7.0)以上版本,不支持MODE_WORLD_WRITABLE和MODE_WORLD_READABLE。
  2. SDK 11以上版本用MODE_MULTI_PROCESS来支持多进程,但没啥用,官方建议使用fileProvider或contentProvider代替。
  3. Sdk26开始对文件级加密做判断处理,线程处理由Executors.newSingleThreadExecutor()改成handlerThread
  4. 每次put和get都会等待读取文件加载完毕,主线程提交可能会导致ANR
  5. 每次提交都会阻塞等待且new一个新的EditorImpl,所以建议批量
  6. 不论修改多少内容,都是整个name.xml文件全量写入,性能比较差
  7. Apply是异步提交,commit是同步提交,并返回提交结果
  8. 在ActivityThread中的handlePauseActivity中,会强制执行异步任务,

Sp的获取:

SharedPreferences sharedPreferences = getSharedPreferences("aa", Context.MODE_PRIVATE);
一、根据name从pathmap缓存种获取file,没有就创建file。file地址:data/data/packetname/name.xml
ContextImpl:
public SharedPreferences getSharedPreferences(String name, int mode) {
    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) {
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
        }
    }
    return getSharedPreferences(file, mode);
}
二、根据包名从cache缓存种获取file+sp集合,再根据file获取sp对象,没有就创建一个sp
public SharedPreferences getSharedPreferences(File file, int mode) {
SDK 24 (android 7.0)以上版本,不支持MODE_WORLD_WRITABLE和MODE_WORLD_READABLE
    checkMode(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;
        }
    }

如果是MODE_MULTI_PROCESS或者sdk小于11,都是多进程模式,多进程操作不安全,数据容易丢失,建议用fileProvider或者contentProvider代替

    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
        sp.startReloadIfChangedUnexpectedly();
    }
    return sp;
}

xml文件的加载

一、加锁+异步加载:其实就是通过XmlUtils工具,将xml文件中的内容找出来然后加载到map中,这个map是静态的,所以你懂的。。。
二、虽然读取是异步的,但是加锁后对标志为的判断还是同步的,还是得wait,所以主线程加载注意ANR

SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);//文件备份,为了防止文件损坏或丢失造成异常。
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    startLoadFromDisk();
}
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    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<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);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
    } catch (Throwable t) {
    }
    synchronized (mLock) {
        mLoaded = true;
        mThrowable = thrown;
        try {
            if (thrown == null) {
                if (map != null) {
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    mMap = new HashMap<>();
                }
            }
        } catch (Throwable t) {
        } finally {
            mLock.notifyAll();
        }
    }
}

Sp的获取:
一、get没啥说的,直接从mMap中去取
sharedPreferences.getInt(“aa” , 0);
Sp的提交:
sharedPreferences.edit().putInt(“age”, 11).apply();
提交分三步:

  1. 首先创建一个临时keymap保存修改的内容,先操作这个临时文件
  2. 在commitToMemory中遍历keymap将mMap集合更新,得到一个MemoryCommitResult结果。
  3. 最后enqueueDiskWrite中,apply交给handlerThread做异步处理,commit将直接Runnable.run()做同步处理。

每次提交都会等待读取文件加载完毕,主线程提交可能会导致ANR
每次提交都会阻塞等待且new一个新的EditorImpl,所以建议批量

public Editor edit() {
    synchronized (mLock) {
        awaitLoadedLocked();  //while(!mLoaded){mLock.wait();}
    }
    return new EditorImpl();
}

不论哪种提交方式都需要通过commitToMemory建一个临时modifyMap,保存修改信息;
遍历modifyMap同步mMap中的数据,得到一个修改结果MemoryCommitResult

private MemoryCommitResult commitToMemory() {
    long memoryStateGeneration;
    List<String> keysModified = null;
    Set<OnSharedPreferenceChangeListener> listeners = null;
    Map<String, Object> mapToWriteToDisk;
    synchronized (SharedPreferencesImpl.this.mLock) {
        if (mDiskWritesInFlight > 0) {
            mMap = new HashMap<String, Object>(mMap);
        }
        mapToWriteToDisk = mMap;
        mDiskWritesInFlight++;
        boolean hasListeners = mListeners.size() > 0;
        if (hasListeners) {
            keysModified = new ArrayList<String>();
            listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
        }
        synchronized (mEditorLock) {
            boolean changesMade = false;
            if (mClear) {
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.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 (!mapToWriteToDisk.containsKey(k)) {
                        continue;
                    }
                    mapToWriteToDisk.remove(k);
                } else {
                    if (mapToWriteToDisk.containsKey(k)) {
                        Object existingValue = mapToWriteToDisk.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mapToWriteToDisk.put(k, v);
                }
                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }
            mModified.clear();
            if (changesMade) {
                mCurrentMemoryStateGeneration++;
            }
            memoryStateGeneration = mCurrentMemoryStateGeneration;
        }
    }
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

1、apply进入,将runnable提交给HandlerThread处理,SDK26之前由singleThreadPool线程池处理。
2、Commit进入直接Runnable.run()执行,并返回提交结果

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final boolean isFromSyncCommit = (postWriteRunnable == null);

    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr, isFromSyncCommit);
                }
                synchronized (mLock) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };
    if (isFromSyncCommit) {
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

这里才是最终将缓存map数据写入文件的执行方法。有用的就两句话:
1、XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); 根据IO先写入缓冲页
2、FileUtils.sync(str); 这里执行强制落盘机制,将缓冲页的数据强制落盘

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    boolean fileExists = mFile.exists();
    if (fileExists) {
        boolean needsWrite = false;
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            //异步写入
            if (isFromSyncCommit) {
                needsWrite = true;
            } else {
            //同步写入
                synchronized (mLock) {
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }
        if (!needsWrite) {
            mcr.setDiskWriteResult(false, true);
            return;
        }
        boolean backupFileExists = mBackupFile.exists();
        if (!backupFileExists) {
            if (!mFile.renameTo(mBackupFile)) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
            mFile.delete();
        }
    }
    try {
        FileOutputStream str = createFileOutputStream(mFile);
        if (str == null) {
            mcr.setDiskWriteResult(false, false);
            return;
        }
        //核心语句1,根据IO先写入缓冲页
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        //核心语句2,执行强制落盘机制,将缓冲页的数据强制落盘
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        try {
            final StructStat stat = Os.stat(mFile.getPath());
            synchronized (mLock) {
                mStatTimestamp = stat.st_mtim;
                mStatSize = stat.st_size;
            }
        } catch (ErrnoException e) {
        }
        mBackupFile.delete();

        mDiskStateGeneration = mcr.memoryStateGeneration;
        mcr.setDiskWriteResult(true, true);
        long fsyncDuration = fsyncTime - writeTime;
        mSyncTimes.add((int) fsyncDuration);
        mNumSync++;

        return;
    }catch (Exception e) {
    }
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

补充:

在ActivityThread中的handlePauseActivity中会强制等待并执行异步任务,将文件落盘。所以文件要是太大也会造成卡顿,甚至是ANR。

@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    if (r != null) {
        if (userLeaving) {
            performUserLeavingActivity(r);
        }
        r.activity.mConfigChangeFlags |= configChanges;
        performPauseActivity(r, finished, reason, pendingActions);
        if (r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }
        mSomeActivitiesChanged = true;
    }
}

你可能感兴趣的:(android)