android9.0中SharedPreferences源码分析(一)

2020-09-03 17:56:30 浏览数 (1)

通过问题的形式来分析SharedPreferences的源码

1.硬盘数据怎么加载到内存的?
  1. 构造方法执行加载
  2. 在子线程中加载,因此加载过程不会阻塞 UI
  3. 本地有数据, 将本地数据 赋值给内存的 mMap,没有的话初始化mMap
代码语言:javascript复制
final class SharedPreferencesImpl implements SharedPreferences {
    
    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
         //1.构造方法执行加载
        startLoadFromDisk();
    }
    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        //2.在子线程中加载,因此加载过程不会阻塞 UI
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                //这个执行一次
                loadFromDisk();
            }
        }.start();
    }
​
    private void loadFromDisk() {
        //...
        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);
                    // 从本地影片的 xml 加载数据,然后xml 数据转为 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) {
        }
​
        synchronized (mLock) {
            mLoaded = true;
            mThrowable = thrown;
​
            try {
                if (thrown == null) {
                    // 3.本地有数据, 将本地数据 赋值给内存的 mMap
                    if (map != null) {
                        mMap = map;
                        mStatTimestamp = stat.st_mtim;
                        mStatSize = stat.st_size;
                    } else {
                        mMap = new HashMap<>();
                    }
                }
            } catch (Throwable t) {
            }
        }
    }    
}
2.getString() 流程是怎样的?

1.获取 SP 的值的时候 若当前文件过大在加载过程中.则 UI 阻塞

2.在获取 sp 值的时候 会一直判断标记mLoaded,变为 true,true表示从本地加载到内存成功,为false 则 UI 阻塞

3.数据加载成功,若 UI 线程阻塞则唤醒

4.使用 wait/notify 机制

代码语言:javascript复制
final class SharedPreferencesImpl implements SharedPreferences {
    
    private boolean mLoaded = false;      
  
    SharedPreferencesImpl(File file, int mode) {
        //2.在获取 sp 值的时候 会一直判断标记mLoaded,变为 true 则表示是否从本地加载到内存成功
        // 为 false 则 UI 阻塞
        mLoaded = false;
    }
    
    @Override
    @Nullable
    public String getString(String key, @Nullable String defValue) {
        synchronized (mLock) {
            //1.获取 SP 的值的时候 若当前文件过大在加载过程中.则 UI 阻塞
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
    
     //3.数据加载成功,若 UI 线程阻塞则唤醒
    @GuardedBy("mLock")
    private void awaitLoadedLocked() {
        //...
        while (!mLoaded) {
            try {
                //4.使用 wait/notify 机制
                // 这儿阻塞 UI ,等待加载成功会唤醒
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
        if (mThrowable != null) {
            throw new IllegalStateException(mThrowable);
        }
    }    
}
3.数据怎么存储到本地的

这个过程涉及到 3 个 map 之间的数据传递

1. map : mModified

接受用户数据的 map

代码语言:javascript复制
private final Map<String, Object> mModified = new HashMap<>();

2. map : mapToWriteToDisk

存储到硬盘使用的 map

代码语言:javascript复制
final Map<String, Object> mapToWriteToDisk;

3. map: mMap

内存中存储数据的 map,初次加载也是将本地xml数据放入 mMap

代码语言:javascript复制
private Map<String, Object> mMap;

数据传递的过程:

1.在 9.0之前的是: mModified 将数据提交到内存 mMap ,然后 clear()清空自己

2.在 9.0的处理是: mModified 将数据直接提交给mapToWriteToDisk

3.mapToWriteToDisk 将数据 存储到本地

4. commitToMemory() 存储到内存做了什么?

1.使用了两个同步代码块,两个对象锁

2.执行writing到本地的时候,写入过程尚未完成时,又调用了 commitToMemory(),而   且此时没有执行mDiskWritesInFlight--,直接修改 mMap 可能会影响写入结果

3. 数据提交到mapToWriteToDisk

代码语言:javascript复制
final class SharedPreferencesImpl implements SharedPreferences {
 
    public final class EditorImpl implements Editor {
      
        private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            List<String> keysModified = null;
            Map<String, Object> mapToWriteToDisk;
            
            //对象锁啊,和get操作同步
            synchronized (SharedPreferencesImpl.this.mLock) {
                if (mDiskWritesInFlight > 0) {
                    //深copy
                    mMap = new HashMap<String, Object>(mMap);
                }
                //将mMap赋值给mapToWriteToDisk,写入本地 xml 使用
                mapToWriteToDisk = mMap;
                mDiskWritesInFlight  ;
​
               //...
                //对象锁啊 和put 操作同步
                synchronized (mEditorLock) {
                    boolean changesMade = false;
                    //...
                     // 变量我们的mModified map 里面放入的是将要 存储内存的 sp 数据
                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                        if (v == this || v == null) {
                           //...
                        } else {
                            //已存在
                            if (mapToWriteToDisk.containsKey(k)) {
                                Object existingValue = mapToWriteToDisk.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            // 放入 map
                            mapToWriteToDisk.put(k, v);
                        }
​
                        changesMade = true;
                        //...
                    }
​
                     // 每次内存存储完 清空一下mModified类型的hashMap 的数据
                    mModified.clear();
​
                    if (changesMade) {
                        mCurrentMemoryStateGeneration  ;
                    }
​
                    memoryStateGeneration = mCurrentMemoryStateGeneration;
                }
            }
            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
        }        
    }
}

其他:

synchronized 的详解

深copy

见相关文章

更多内容 欢迎关注公众号

0 人点赞