SharedPreferences源码分析

SharedPreferences是Android平台上 轻量级的存储类,用来保存App的各种配置信息,其本质是一个以 键值对(key-value)的方式保存数据的xml文件,其保存在/data/data/PACKAGE_NAME /shared_prefs目录下。

 

这里先列举在使用时的注意事项:

1、SharedPreferences一经加载,它内部存储的数据就会以Map的形式一直保存在内存中,所以不宜存放大数据。

2、获取SharedPreferences时,如果是初次加载,那么需要从文件中去加载数据到内存中,这是一个耗时操作,所以加载数据的这个过程是在子线程中完成的,但是,注意重点,当我们对SharedPreferences进行操作,而SharedPreferences加载数据又还没有结束,这时会导致线程等待,直到数据加载完后在开始操作数据,所以,对于要用到SharedPreferences,应该提前去获取,以确保使用时已经将数据加载完毕,这里说的是首次,如果加载过一次,那么之后的获取就是直接从内存中获取了;所以说如果不合理使用就会出现卡顿甚至ANR问题:

3、保存数据时,apply()是先将数据保存到内存中,然后在子线程中将数据保存到磁盘,commit()也是先将数据保存到内存中,之后立即将数据同步到磁盘,这个操作是在当前线程中完成,所以会阻塞线程,所以现在android建议使用apply(),但是使用apply()时也有需要注意的地方,如果保存数据时这个过程还没有结束,但是这时退出了activity,这时activity会先确保数据是否已经全部同步到磁盘了,如果没有,这时会两种情况,一是保存的过程正在子线中执行,这时等待就好,如果这时还没分发给子线程,那么就直接切换到主线程执行了,所以这时提交数据时也有需要注意的地方:使用SharedPreferences.Editor提交数据时,edit() 方法每次都会新建一个 EditorImpl 对象。所以应该多次 putXXX(),尽量在所有数据都提交后再调用apply()方法。

4、无论是 commit() 还是 apply() ,针对任何修改都是全量写入。所以应该要分清哪些数据是常用的,哪些是不常用的,常用的保存在一起,不常用的保存在一起。

5、commit() 同步保存,有返回值。apply() 异步保存,无返回值。按需取用。

6、onPause() 、onReceive() 等时机会等待异步写操作执行完成,可能造成卡顿或者 ANR。

 

以上提出的使用注意事项和结论,我会从下面分析中一一解答。

一、读取数据

基本写法:

1

2

SharedPreferences sharedPreferences = getContext().getSharedPreferences("sp_yy_fly", Context.MODE_PRIVATE);

boolean value= sharedPreferences .getBoolean("key", false);

注:

上文中 Context.MODE_PRIVATE是用来声明读写模式,将此位置设置未mode,

mode指定为MODE_PRIVATE,则该配置文件只能被自己的应用程序访问。(也可写成0)

mode指定为MODE_WORLD_READABLE,则该配置文件除了自己访问外还可以被其它应该程序读取。不安全,官方已弃用(也可写成1)

mode指定为MODE_WORLD_WRITEABLE,则该配置文件除了自己访问外还可以被其它应该程序写入。不安全,官方已弃用(也可写成2)

mode指定为MODE_APPEND,检查文件是否存在,存在就往文件追加内容,否则就创建新文件

读取数据主要执行两步:

  • 获取SharedPreferences实例
  • 调用getString(key,"") 方法获取数据

从上面可以看出getSharedPerferences(String name,int mode)方法来自Context,所以在Context的实现类ContextImpl中分析getSharedPreferences方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

   /**

    * 文件名为 key,具体文件为 value。存储所有 sp 文件

    * 由 ContextImpl.class 锁保护

    */

   @GuardedBy("ContextImpl.class")

    private ArrayMap<String, File> mSharedPrefsPaths;

 

    @Override

    public SharedPreferences getSharedPreferences(String name, int mode) {

        // At least one application in the world actually passes in a null

        // name.  This happened to work because when we generated the file name

        // we would stringify it to "null.xml".  Nice.

//        在api 19之前,如果name为null,那默认就将文件取名为null.xml,但是之后就会报NullPointerException;

        if (mPackageInfo.getApplicationInfo().targetSdkVersion <

                Build.VERSION_CODES.KITKAT) {

            if (name == null) {

                name = "null";

            }

        }

 

        File file;

        synchronized (ContextImpl.class) {

           //首先会尝试从mSharedPrefsPaths中获取name在本地的File,

            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);

    }

    /**

     * 创建一个name +".xml"的文件

     * @param name

     * @return

     */

    @Override

    public File getSharedPreferencesPath(String name) {

        return makeFilename(getPreferencesDir(), name + ".xml");

    }

 

    /**

     *将此 xml文件,其保存在/data/data/PACKAGE_NAME /shared_prefs目录下。

     * @return

     */

    @UnsupportedAppUsage

    private File getPreferencesDir() {

        synchronized (mSync) {

            if (mPreferencesDir == null) {

                //getDataDir()获取包名信息

                mPreferencesDir = new File(getDataDir(), "shared_prefs");

            }

            return ensurePrivateDirExists(mPreferencesDir);

        }

    }

mSharedPrefsPaths 是一个 ArrayMap ,缓存了文件名和 sp 文件的对应关系。首先会根据参数中的文件名 name 查找缓存中是否存在对应的 sp 文件。如果不存在的话,会新建名称为 [name].xml 的文件,并存入缓存 mSharedPrefsPaths 中。最后会调用另一个重载的 getSharedPreferences(file,mode) 方法,参数是 File 。

这里先看下ArrayMap,它是Android中提供的,它的作用类似HashMap,但是ArrayMap采用了一种独特的方式,能够重复的利用因为数据扩容而遗留下来的数组空间,方便下一个ArrayMap的使用。而HashMap没有这种设计,所以在内存方面ArrayMap比HashMap更有效率,所以在android中推荐使用ArrayMap,这里使用的就是ArrayMap。

接下来调用的就是getSharedPreferences(file, mode),如果你不想使用默认保存文件的地方,那就可以使用这个方法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

@Override

public SharedPreferences getSharedPreferences(File file, int mode) {

    SharedPreferencesImpl sp;

    synchronized (ContextImpl.class) {

        //从内存中获取缓存

        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();

        sp = cache.get(file);

        if (sp == null) {

            checkMode(mode);

            //targetSdk为androidO及以上、访问内部存储空间、当前设备处于锁定状态时不可以访问内部存储空间

            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");

                }

            }

            sp = new SharedPreferencesImpl(file, mode);

            cache.put(file, sp);

            return sp;

        }

    }

    // mode 为 MODE_MULTI_PROCESS 时,文件可能被其他进程修改,则重新加载

    // 显然这并不足以保证跨进程安全

    if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||

        getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {

        // If somebody else (some other process) changed the prefs

        // file behind our back, we reload it.  This has been the

        // historical (if undocumented) behavior.

        sp.startReloadIfChangedUnexpectedly();

    }

    return sp;

}

注:targetSdk为androidO及以上、访问内部存储空间、当前设备处于锁定状态时不可以访问内部存储空间

这个方法主要的逻辑就是先获取缓存,根据文件查看缓存中是否存在,如果存在就直接返回,如果不存在,那么就新建一个SharedPreferences,然后保存到缓存中,再返回,这里获取缓存使用的是getSharedPreferencesCacheLocked(),这里先去看下它的缓存是怎样设计的;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/**

 * 使用static使sSharedPrefsCache一直缓存在内存中,所以说在第一次加载后,在这之后就一直在内存中获取了

 */

private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

 

@GuardedBy("ContextImpl.class")

private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {

    if (sSharedPrefsCache == null) {

        sSharedPrefsCache = new ArrayMap<>();

    }

 

    final String packageName = getPackageName();

    ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);

    if (packagePrefs == null) {

        packagePrefs = new ArrayMap<>();

        sSharedPrefsCache.put(packageName, packagePrefs);

    }

 

    return packagePrefs;

}

这里用的也是ArrayMap,并且还是static的,这也就是说这个是一直保存在内存中的,这也就是为什么说在第一次加载后,在这之后就一直在内存中获取了,接下来就去看下初次加载是怎样的一个执行逻辑,对于初次加载,是创建sp = new SharedPreferencesImpl(file, mode)这样一个对象并返回,那我们就去看看它的构造方法到底做了些什么事情:

SharedPreferencesImpl(file, mode)的构造方法:

1

2

3

4

5

6

7

8

9

10

@UnsupportedAppUsage

SharedPreferencesImpl(File file, int mode) {

    mFile = file; // sp 文件

    mBackupFile = makeBackupFile(file);// 创建备份文件

    mMode = mode;

    mLoaded = false;// 标识 sp 文件是否已经加载到内存

    mMap = null; // 存储 sp 文件中的键值对

    mThrowable = null;

    startLoadFromDisk();

}

咋一看没什么啊,都是初始化一些变量,但这里有一个重点需要注意,那就是startLoadFromDisk(),看名字就知道是去磁盘中加载数据,也就是说只有在创建实例的时候才会去加载数据,前面的有说到,一旦创建一个实例后就会保存到缓存中,这也就是说只在第一次获取的时候才会去磁盘中加载数据;所以第一次读取时稍微慢一点, 后面的都在内存操作所以比较快。

startLoadFromDisk()方法会启动一个线程, 然后异步调用loadFromDisk()把xml文件的内容加载到内存Map中。

startLoadFromDisk()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

@UnsupportedAppUsage

    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();

             // 如果存在备份文件,直接将备份文件重命名为 sp 文件

                mBackupFile.renameTo(mFile);

            }

        }

 

        // Debugging

        if (mFile.exists() && !mFile.canRead()) {

            Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");

        }

 

        Map<String, Object> map = null;

        StructStat stat = null;

        Throwable thrown = null;

        //具体的文件读取操作,然后解析XML,存储在Map中!这也是为何SharedPrefernces不是线程安全的,因为使用的是Map数据结构

        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) {

            // An errno exception means the stat failed. Treat as empty/non-existing by

            // ignoring.

        } catch (Throwable t) {

            thrown = t;

        }

 

        synchronized (mLock) {

            //数据读取完成,将mLoaded置为true

            mLoaded = true;

            mThrowable = thrown;

 

            // It's important that we always signal waiters, even if we'll make

            // them fail with an exception. The try-finally is pretty wide, but

            // better safe than sorry.

            try {

                if (thrown == null) {

                    if (map != null) {

//                        赋值给mMap,之后的获取数据都是通过mMap

                        mMap = map;

                        mStatTimestamp = stat.st_mtim;// 更新修改时间

                        mStatSize = stat.st_size;// 更新文件大小

                    } else {

                        mMap = new HashMap<>();

                    }

                }

                // In case of a thrown exception, we retain the old map. That allows

                // any open editors to commit and store updates.

            } catch (Throwable t) {

                mThrowable = t;

            } finally {

                //读取完成,唤醒所有等待的线程

                mLock.notifyAll();

            }

        }

    }

简单捋一下流程:

  1. 判断是否已经加载进内存
  2. 判断是否存在遗留的备份文件,如果存在,重命名为 sp 文件
  3. 读取 sp 文件,并存入内存
  4. 更新文件信息
  5. 释放锁,唤醒处于等待状态的线程

这里是具体的文件读取操作,然后解析XML,然后存储在Map中,这也是为何SharedPrefernces不是线程安全的,因为使用的是Map数据结构导致的线程不安全。这里有一个mLock锁,这个锁很重要,这里获取到了锁,当SharedPreferences去操作数据的时候就会先去判断这个锁是否释放了,如果没有,那么就会等待锁释放了在执行,最后一步mLock.notifyAll()指加载完成,通知正在等待的代码继续执行。读取数据完成后就将数据加载到了内存中,最终保存在mMap中,那么接下来看他是怎样获取数据的,这里就看下它的getString()方法

1

2

3

4

5

6

7

8

9

10

@Override

@Nullable

public String getString(String key, @Nullable String defValue) {

    synchronized (mLock) {

    //等待文件加载到内存

        awaitLoadedLocked();

        String v = (String)mMap.get(key);

        return v != null ? v : defValue;

    }

}

getString根据key调用各种get方法,从mMap中获取值。其中在读取get方法时需要加锁。这里一进来就加了一个锁,这个锁是为了防止数据还没加载就执行到这里,那这时就需要将锁释放掉,但又不对数据进行操作,那这个awaitLoadedLocked()就起到了这个作用。

继续分析awaitLoadedLocked()方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@GuardedBy("mLock")

private void awaitLoadedLocked() {

    if (!mLoaded) {

        // Raise an explicit StrictMode onReadFromDisk for this

        // thread, since the real read will be in a different

        // thread and otherwise ignored by StrictMode.

        BlockGuard.getThreadPolicy().onReadFromDisk();

    }

    //在读取文件完成后,mLoaded会置为true

    while (!mLoaded) {

        try {

            mLock.wait();

        } catch (InterruptedException unused) {

        }

    }

    if (mThrowable != null) {

        throw new IllegalStateException(mThrowable);

    }

}

awaitLoadedLocked()会判断当前文件是否下载完成, 如果没有完成就阻塞等待; 上面异步加载文件的loadFromDisk()方法, 在加载完成后会调用mLock.notifyAll()通知这里继续执行。这也就是说,数据没加载完是不能对数据进行操作的,这时就阻塞在这里,到这,一开始说到的第一点和第二点就可以解释了,那么接下来就来看看数据是如何修改的。

二、存入数据

SharedPreferences sharedPreferences = getContext().getSharedPreferences("sp_yy_fly", Context.MODE_PRIVATE);

SharedPreferences.Editor edit =sharedPreferences.edit();

edit.putBoolean("key", false);

edit.putInt("key",123);

edit.putFloat("key",1234L);

edit.putString("key","value");

edit.apply();

//edit.commit();

从上面可以看出,在获取SharedPreferences 实例后,调用edit()方法获取Editor的实例EditorImpl,这里也是要等数据加载完才能对数据进行操作,所以awaitLoadedLocked()会判断当前文件是否下载完成, 如果没有完成就阻塞等待。实际上对数据进行修改使用的是EditorImpl这个类。

 

每调用一次edit()方法就会新建一个EditorImpl实例,所以不要每修改一次就调用一次,可以尽量将所有需要修改的数据放一起进行操作,接下来看看这个类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

public final class EditorImpl implements Editor {

       private final Object mEditorLock = new Object();

 

       @GuardedBy("mEditorLock")

       private final Map<String, Object> mModified = new HashMap<>();

 

       @GuardedBy("mEditorLock")

       private boolean mClear = false;

 

       @Override

       public Editor putString(String key, @Nullable String value) {

           synchronized (mEditorLock) {

               mModified.put(key, value);

               return this;

           }

       }

       @Override

       public Editor putStringSet(String key, @Nullable Set<String> values) {

           synchronized (mEditorLock) {

               mModified.put(key,

                       (values == null) ? null : new HashSet<String>(values));

               return this;

           }

       }

       @Override

       public Editor putInt(String key, int value) {

           synchronized (mEditorLock) {

               mModified.put(key, value);

               return this;

           }

       }

       @Override

       public Editor putLong(String key, long value) {

           synchronized (mEditorLock) {

               mModified.put(key, value);

               return this;

           }

       }

       @Override

       public Editor putFloat(String key, float value) {

           synchronized (mEditorLock) {

               mModified.put(key, value);

               return this;

           }

       }

       @Override

       public Editor putBoolean(String key, boolean value) {

           synchronized (mEditorLock) {

               mModified.put(key, value);

               return this;

           }

       }

 

       @Override

       public Editor remove(String key) {

           synchronized (mEditorLock) {

               mModified.put(key, this);

               return this;

           }

       }

 

       @Override

       public Editor clear() {

           synchronized (mEditorLock) {

               mClear = true;

               return this;

           }

       }

 

拿到SharedPreferences.Editor后就可以往里面putString(), putInt 等等...put的数据都是保存在mModified这个临时的Map集合里面了。

这个时候数据还是在内存里面, 只有调用commit()或者apply()才会保存到本地文件。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

         @Override

        public void apply() {

            final long startTime = System.currentTimeMillis();

//            将临时存储数据的集合mModified合并到要保存到磁盘里的集合中mapToWriteToDisk

            final MemoryCommitResult mcr = commitToMemory();

            final Runnable awaitCommit = new Runnable() {

                    @Override

                    public void run() {

                        try {

                            // writtenToDiskLatch是一个CountDownLatch对象,除非数据全部同步到磁盘,否则这里就一直阻塞

                            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中的目的是为了退出activity时,确保所有数据已经全部同步到磁盘中了

            QueuedWork.addFinisher(awaitCommit);

 

            Runnable postWriteRunnable = new Runnable() {

                    @Override

                    public void run() {

                        awaitCommit.run();

                        // 数据同步完这里就会将awaitCommit从QueuedWork移除

                        QueuedWork.removeFinisher(awaitCommit);

                    }

                };

            // 开始执行将数据同步到磁盘中,会新开线程,后面会说到

            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

 

            // Okay to notify the listeners before it's hit disk

            // because the listeners should always get the same

            // SharedPreferences instance back, which has the

            // changes reflected in memory.

            notifyListeners(mcr);

        }

注:QueuedWork这是个内部工具类,用于跟踪那些未完成的或尚未结束的全局任务,新任务通过方法这个类用于可延迟

注:CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Override

 public boolean commit() {

     long startTime = 0;

     if (DEBUG) {

         startTime = System.currentTimeMillis();

     }

     MemoryCommitResult mcr = commitToMemory();

     SharedPreferencesImpl.this.enqueueDiskWrite(

         mcr, null /* sync write on this thread okay */);

     try {

         mcr.writtenToDiskLatch.await();

     } catch (InterruptedException e) {

         return false;

     } finally {

         if (DEBUG) {

             Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration

                     + " committed after " + (System.currentTimeMillis() - startTime)

                     + " ms");

         }

     }

     notifyListeners(mcr);

     return mcr.writeToDiskResult;

 }

 

 

相同点:

  • 都会调用commitToMemory(), 然后调用enqueueDiskWrite()把Map的数据写入本地文件中

不同点:

  • commit() 有返回值, 可以知道成功还是失败
  • apply() 没有返回值, 并且多了2个Runnable

commitToMemory()方法会先把mMap的数据放到mapToWriteToDisk这个map上,之后遍历之前put数据的mModified这个Map, 把我们修改的数据同步到mapToWriteToDisk中, 并且清空mModified自己。最后返回一个MemoryCommitResult对象,将数据保存到磁盘中用到的就是这个类;此处就可以解释问题4:无论是 commit() 还是 apply() ,针对任何修改都是全量写入。所以应该要分清哪些数据是常用的,哪些是不常用的,常用的保存在一起,不常用的保存在一起。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

     // Returns true if any changes were made

        private MemoryCommitResult commitToMemory() {

            long memoryStateGeneration;

            List<String> keysModified = null;

            Set<OnSharedPreferenceChangeListener> listeners = null;

            Map<String, Object> mapToWriteToDisk;

 

            synchronized (SharedPreferencesImpl.this.mLock) {

                // We optimistically don't make a deep copy until

                // a memory commit comes in when we're already

                // writing to disk.

                if (mDiskWritesInFlight > 0) {

                    // We can't modify our mMap as a currently

                    // in-flight write owns it.  Clone it before

                    // modifying it.

                    // noinspection unchecked

                    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;

                    }

                      // 遍历存储在临时集合中的数据,然后合并到mapToWriteToDisk中

                    for (Map.Entry<String, Object> e : mModified.entrySet()) {

                        String k = e.getKey();

                        Object v = e.getValue();

                        // "this" is the magic value for a removal mutation. In addition,

                        // setting a value to "null" for a given key is specified to be

                        // equivalent to calling remove on that key.

                        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

                    mModified.clear();

 

                    if (changesMade) {

                        mCurrentMemoryStateGeneration++;

                    }

 

                    memoryStateGeneration = mCurrentMemoryStateGeneration;

                }

            }

            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,

                    mapToWriteToDisk);

        }

 

commit()方法调用enqueueDiskWrite的时候, 这个postWriteRunnable传为空。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

private void enqueueDiskWrite(final MemoryCommitResult mcr,

                              final Runnable postWriteRunnable) {

    // 注意这里的postWriterRunnable,如果是apply()方法,传进来的参数是不为null的,如果是commit(),

    // 这里传进来的为null,isFromSyncCommit的值决定了是立即同步还是稍后再同步

    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(); } }

        };

    // 如果是commit(),那么就会执行到这里,在当前线程中同步数据

    if (isFromSyncCommit) {

        boolean wasEmpty = false;

        synchronized (mLock) {

            wasEmpty = mDiskWritesInFlight == 1;

        }

        if (wasEmpty) {

            writeToDiskRunnable.run();

            return; }

    }

    // 如果是apply(),那么就会执行到这里,

    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

}

 

这里会创建一个writeToDiskRunnable里面会执行当writeToFile()把数据写入文件。

调用commit的时候, isFromSyncCommit = true, 所以会在当前线程执行同步数据,你在主线程调用该方法,就会直接在主线程进行 IO 操作,如果数据过大,可能就会再次阻塞,这也就是为什么android现在不推荐使用这个方法了。

调用apply的时候会把写入文件的Runnable加入QueuedWork.queue()中执行, 其实是把Runnable发送到HandlerThread的子线程中执行,

接下来看看queue()里面是如何做的处理:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@UnsupportedAppUsage

public static void queue(Runnable work, boolean shouldDelay) {

    Handler handler = getHandler();

 

    synchronized (sLock) {

        sWork.add(work);

 

        if (shouldDelay && sCanDelay) {

            handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);

        } else {

            handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);

        }

    }

}

 

先获取到一个handler,然后将同步数据的任务添加到了sWork这个集合中,那后面处理肯定也是去这个集合里面拿了,根据上面的分析,这里的shouldDelay是为false的,所以执行的是下面这个方法,看来重点是这个handler了,那就去看看这个getHandler()了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

@UnsupportedAppUsage

private static Handler getHandler() {

    synchronized (sLock) {

        if (sHandler == null) {

            // 这里使用HandlerThread是为了获取子线程的looper对象,handler中处理消息就是通过这个looper

            HandlerThread handlerThread = new HandlerThread("queued-work-looper",

                    Process.THREAD_PRIORITY_FOREGROUND);

            handlerThread.start();

 

            sHandler = new QueuedWorkHandler(handlerThread.getLooper());

        }

        return sHandler;

    }

}

 

private static class QueuedWorkHandler extends Handler {

    static final int MSG_RUN = 1;

 

    QueuedWorkHandler(Looper looper) {

        super(looper);

    }

 

    public void handleMessage(Message msg) {

        if (msg.what == MSG_RUN) {

            processPendingWork();

        }

    }

}

 

这里就是构建一个handler,如果已经存在就直接返回了,不过这个handler处理消息是在子线程中执行的,要看明白这里,首先你得知道Handler和HadlerThread的原理,构建Handler在子线中处理消息时,一定要为他准备一个looper对象,否则是会有异常的,看这里的handleMessage(),将执行的逻辑交给了processPendingWork(),来看看:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

    private static void processPendingWork() {

        long startTime = 0;

        if (DEBUG) {

            startTime = System.currentTimeMillis();

        }

        synchronized (sProcessingWork) {

            LinkedList<Runnable> work;

 

            synchronized (sLock) {

                // 将前面添加的任务全部clone出来,然后清除

                work = (LinkedList<Runnable>) sWork.clone();

                sWork.clear();

 

// 将任务取出来后,就可以取消后面执行任务的消息了,这里取消的只是上面clone出来的任务

                getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);

            }

            // 这里就是开始将数据同步到内存中

            if (work.size() > 0) {

                for (Runnable w : work) {

                    w.run();

                }

 

                if (DEBUG) {

                    Log.d(LOG_TAG, "processing " + work.size() + " items took " +

                            +(System.currentTimeMillis() - startTime) + " ms");

                }

            }

        }

    }

 

这里执行的逻辑就是将前面添加的数据clone出来作为局部变量,然后将clone的集合清空,再移除发送任务的消息(这个任务已经添加到集合中了),这样数据同步就在子线程中完成了。

到这,主要的逻辑都捋了一遍,但是还有一个点,就是一开始说的第三点,退出activity时,数据没同步完是会阻塞线程的,现在就看看为什么会阻塞线程?这里要先知道一点,Activity生命周期都是通过ActivityThread来控制的,在ActivityThread的handleStopActivity方法中,这里控制的就是Activity的stop,在这个方法中有调用到:QueuedWork.waitToFinish();这里就是查看QueuedWork中的任务是否都执行完了,如果没有执行完,那么就会处于等待状态,看下它里面是一个怎样的逻辑:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

public static void waitToFinish() {

    long startTime = System.currentTimeMillis();

    boolean hadMessages = false;

 

    Handler handler = getHandler();

    // 先看下Handler中是否还有等待的消息,如果有那么就移除

    synchronized (sLock) {

        if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {

            // Delayed work will be processed at processPendingWork() below

            handler.removeMessages(QueuedWorkHandler.MSG_RUN);

 

            if (DEBUG) {

                hadMessages = true;

                Log.d(LOG_TAG, "waiting");

            }

        }

 

        // We should not delay any work as this might delay the finishers

        sCanDelay = false;

    }

 

    StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();

    try {

        // 这里就是将本该在子线程中执行的任务直接拿到主线程中来执行了

        processPendingWork();

    } finally {

        StrictMode.setThreadPolicy(oldPolicy);

    }

 

    try {

        // 这里是为了确保所有的任务确实已经执行完了

        while (true) {

            Runnable finisher;

 

            synchronized (sLock) {

                finisher = sFinishers.poll();

            }

 

            if (finisher == null) {

                break;

            }

 

            finisher.run();

        }

    } finally {

        sCanDelay = true;

    }

 

    synchronized (sLock) {

        long waitTime = System.currentTimeMillis() - startTime;

 

        if (waitTime > 0 || hadMessages) {

            mWaitTimes.add(Long.valueOf(waitTime).intValue());

            mNumWaits++;

 

            if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {

                mWaitTimes.log(LOG_TAG, "waited: ");

            }

        }

    }

}

 

这里会判断添加的任务是否全部执行完了,如果没有执行完,那么就会将本该在子线程中执行的任务全部移到主线程中来执行。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HMP*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值