美文网首页
SharedPreference源码分析

SharedPreference源码分析

作者: 李die喋 | 来源:发表于2020-07-08 22:41 被阅读0次

最近在复习,发现了关于多线程 多进程的问题,面试中常问的涉及到了SharedPreference的知识,决定去看看源码,到底是如何实现的。这里就不介绍它的用法和一些基础知识了,直接开始源码分析。

源码解析

ContextImpl#getSharedPreferences

这里主要是看context.getSharedPreferences()方法,context的实现类是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);
        文件名为null,创建文件,并存入mSharedPrefsPaths集合中
        if (file == null) {
            file = getSharedPreferencesPath(name);
            mSharedPrefsPaths.put(name, file);
        }
    }
    //调用下面的方法
    return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        // 获取用于缓存的cache对象 
        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())) {
                    throw new IllegalStateException("SharedPreferences in credential encrypted "
                            + "storage are not available until after user is unlocked");
                }
            }
            //可以看出SharedPreferencesImpl是SharedPreferences的实现类
            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;
}

private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
    //若第一次调用该方法,则对全局变量sSharedPrefsCache进行初始化
    if (sSharedPrefsCache == null) {
        sSharedPrefsCache = new ArrayMap<>();
    }
    //获取包名
    final String packageName = getPackageName();
    //根据包名获取packagePrefs
    ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
    //创建packagePrefs,并放入缓存中
    if (packagePrefs == null) {
        packagePrefs = new ArrayMap<>();
        sSharedPrefsCache.put(packageName, packagePrefs);
    }
    return packagePrefs;
}

SharedPreferencesImpl

构造方法

SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    //file的备份文件
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    //从硬盘中加载数据
    startLoadFromDisk();
}

startLoadFromDisk()

private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    //创建并启动了一个名为“SharedPreferencesImpl-load”的线程
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            //从硬盘中加载数据
            loadFromDisk();
        }
    }.start();
}

loadFromDisk()

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 = (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) {
        thrown = 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) {
            mThrowable = t;
        } finally {
            //释放锁,随机唤醒一个等待mLock锁的线程
            mLock.notifyAll();
        }
    }
}

从SharedPreferences中获取数据

以getString为例

getString

public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        // 让当前的线程等待从磁盘加载数据的线程加载完数据
        awaitLoadedLocked();
        // 从全局变量mMap中获取数据
        String v = (String)mMap.get(key);
        // 如果获取到数据,就返回数据,否则返回方法参数中给定的默认值
        return v != null ? v : defValue;
    }
}

awaitLoadedLocked

private void awaitLoadedLocked() {
    // 如果从硬盘加载数据没有完成
    if (!mLoaded) {
       //调用线程处理策略
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    // 如果从硬盘加载数据没有完成,则循环执行
    while (!mLoaded) {
        try {
            //释放锁等待
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    //若等待期间发生异常,则抛出异常
    if (mThrowable != null) {
        throw new IllegalStateException(mThrowable);
    }
}

写数据

SharedPreferences.Editor

@Override
public Editor edit() {
    // 同步锁,锁对象为mLock
    synchronized (mLock) {
    // 让当前的线程等待从磁盘加载数据的线程加载完数据
    awaitLoadedLocked();
    }
    // 创建EditorImpl对象并返回
    return new EditorImpl();
}

EditorImpl类

Editor是一个接口,它的具体实现类是EditorImpl类。EditorImpl类是SharedPreferencesImpl类的内部类,SharedPreferences中对数据的增 删 改都是通过调用Editor的相关方法实现的。

putString

private final Map mModified = new HashMap();// 用于临时存储需要写入磁盘的数据或需要移除的数据,之后统一处理

@Override
public Editor putString(String key, @Nullable String value) {
    // 同步锁,锁对象为mEditorLock
    synchronized (mEditorLock) {
    // 向全局变量mModified添加数据
    mModified.put(key, value);
    // 返回
    return this;
    }
}

提交数据到磁盘

commit()

public boolean commit() {
    long startTime = 0;
    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }
    //对EditorImpl对象的操作(put remove clear等)进行整合处理
    MemoryCommitResult mcr = commitToMemory();
    //将数据写入磁盘
    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try {
        //阻塞等待写入过程完成
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    } finally {
        
    }
    //对注册当前SharedPreferences对象的监听器进行回调
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

commitToMemory

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对数据进行操作
            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;
            //若用户调用clear方法清空数据
            if (mClear) {
                //若写入的数据不为空,即有需要清除的数据
                if (!mapToWriteToDisk.isEmpty()) {
                    changesMade = true;
                    mapToWriteToDisk.clear();
                }
                mClear = false;
            }
            //对提交到Editor中的数据进行遍历
            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 {
                //若值不为空,也不为自身,说明添加了新键值对或修改了键值对的值
                //若打算写入磁盘的数据包含k这个键,说明对值进行了修改
                    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;
        }
    }
    //创建MemoryCommitResult对象并返回
    return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
            mapToWriteToDisk);
}

enqueueDiskWrite()

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    //通过判断参数postWriteRunnable是否为空,表示需要同步写入磁盘还是异步写入磁盘
    final boolean isFromSyncCommit = (postWriteRunnable == null);
    //创建writeToRunnable对象,封装写入磁盘的核心操作
    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) {
            //若当前写入磁盘的线程数量为1 说明除了本线程没有其他线程写入,wasEmpty为true
            wasEmpty = mDiskWritesInFlight == 1;
        }
        //若没有其他线程正在写入
        if (wasEmpty) {
            //执行线程
            writeToDiskRunnable.run();
            return;
        }
    }
    //若为异步写入或者同步写入时有其他线程正在写入,则调用本方法异步写入
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

apply()

public void apply() {
    final long startTime = System.currentTimeMillis();
    //对EditorImpl对象的操作进行整合处理
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    //阻塞等待数据写入磁盘完成
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }
                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };
    //将awaitCommit对象保存到QueuedWork 当QueuedWork在执行任务时需要阻塞时会调用
    QueuedWork.addFinisher(awaitCommit);
    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                //将awaitCommit对象从QueuedWork中移除 QueuedWork.removeFinisher(awaitCommit);
            }
        };
    //将数据写入磁盘
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

    notifyListeners(mcr);
}

从此可以看出,这两个方法都是首先修改内存中缓存的mMap的值,然后将数据写到磁盘中。它们的主要区别是commit会等待写入磁盘后再返回,而apply则在调用写磁盘操作后就直接返回了,但是这时候可能磁盘中数据还没有被修改。

参考文章

有关SharedPreferences面试

相关文章

网友评论

      本文标题:SharedPreference源码分析

      本文链接:https://www.haomeiwen.com/subject/mzqkcktx.html