一.简介
SharedPreferences 是 Android 中一种轻量级的数据存储方式,数据以键值对,文件以 xml 的形式存储在 /data/data/
在源码中 SharedPreferences 是一个接口,具体的实现是
SharedPreferencesImpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
 * Interface for accessing and modifying preference data returned by {@link
 * Context#getSharedPreferences}.  For any particular set of preferences,
 * there is a single instance of this class that all clients share.
 * Modifications to the preferences must go through an {@link Editor} object
 * to ensure the preference values remain in a consistent state and control
 * when they are committed to storage.  Objects that are returned from the
 * various <code>get</code> methods must be treated as immutable by the application.
 *
 * <p><em>Note: This class does not support use across multiple processes.</em>
 *
 * ...
 */
public interface SharedPreferences {
    ...
}
final class SharedPreferencesImpl implements SharedPreferences {
    ...
}
在注释中对 SharedPreferences 以下称(SP)解释如下:
- 对于任何特定的 SP ,所有客户端共享此类的单个实例(也就是应该使用单例模式)。对 SP 数据的修改必须通过一个 SharedPreferences.Editor 对象来确保 SP 数据保持一致状态,并在它们提交存储时进行控制。从各种get 方法返回的对象必须被应用程序视为不可变的。
 
注意:此类提供强大的一致性保证。它使用昂贵的操作可能会减慢应用程序的速度。经常改变可以容忍损失的属性或属性应该使用其他机制。有关详细信息读取上的评论 SharedPreferences.Editor.commit() 和SharedPreferences.Editor.apply()。
(换句话说就是 commit 和 apply 用于对数据进行保存,为了保证一致性这个过程可能会减慢应用程序的速度,如果对一致性要求不高则可以使用其他数据存储机制。)
==注意:此类不支持跨多个进程使用。==
二.获取 Sp
获取一个 Sp 有三种方式
- 在一个 Activity 中调用 getPreferences(int mode)
 - 使用 PreferenceManager.getDefaultSharedPreferences(Context context)
 - context.getSharedPreferences(String name, int mode)
 
第一种方式和第二种方式最后都会使用 第三种方式,不同的使用的名字不同,模式现在都是为 MODE_PRIVATE 其他的都已经废弃不使用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//第一种
//Activity 
//
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
//第二种
//PreferenceManager
public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
                getDefaultSharedPreferencesMode());
    }
    /**
     * Returns the name used for storing default shared preferences.
     *
     * @see #getDefaultSharedPreferences(Context)
     * @see Context#getSharedPreferencesPath(String)
     */
    public static String getDefaultSharedPreferencesName(Context context) {
        return context.getPackageName() + "_preferences";
    }
    private static int getDefaultSharedPreferencesMode() {
        return Context.MODE_PRIVATE;
    }
下面到  context 的具体实现类 contextImpl 中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@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.
       
       // 允许名字为 null, 即文件的名字为 null.xml 
       if (mLoadedApk.getApplicationInfo().targetSdkVersion <
               Build.VERSION_CODES.KITKAT) {
           if (name == null) {
               name = "null";
           }
       }
       File file;
       synchronized (ContextImpl.class) {
       //mSharedPrefsPaths 的数据结构为  ArrayMap<String, File> 
       //用于保存名字 和对应的文件
           if (mSharedPrefsPaths == null) {
               mSharedPrefsPaths = new ArrayMap<>();
           }
           file = mSharedPrefsPaths.get(name);
           //如果没有这个文件就创建这个文件
           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 进行线程安全的保证
       synchronized (ContextImpl.class) {
           //一个 SP xml 文件对应 一个 SharedPreferencesImpl 实例
           final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
           sp = cache.get(file);
           if (sp == null) {
               checkMode(mode);
               ...
               //创建 SharedPreferencesImpl 
               sp = new SharedPreferencesImpl(file, mode);
               cache.put(file, sp);
               return sp;
           }
       }
       ....
       return sp;
   }
  // 获取用于缓存 SharedPreferencesImpl 的 packagePrefs, 一个包名对应一个 
  // packagePrefs 
   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;
   }
下面就到 SharedPreferencesImpl.java 中 查看创建一个 SharedPreferencesImpl 的过程。
1  | 
  | 
获取 SharedPreferences 总结:
- 获取 SP 的过程是通过 synchronized 关键字保证多线程安全的。
 - 通过 Map 进行缓存 Sp 实例,因此多次调用 getSharedPreferences 几乎没有性能上的差别。
 - 获取 Sp 的时候就会通过一个线程将 xml 数据从磁盘加载到内存中。这个过程会加锁,加载完成后会设置 mLoaded 标志,并唤醒其他线程。
三.get 方法
Sp 支持的数据类型为 int , long , float, boolean ,String 和 Set.  
下面以 getString 为例1
2
3
4
5
6
7
8
9
10
11
12//SharedPreferencesImpl.java 
  @Override
    @Nullable
    public String getString(String key, @Nullable String defValue) {
    //进行线程安全保证 
        synchronized (mLock) {
        //等待加载完成后才能读
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
1  | @GuardedBy("mLock")  | 
getXXX 方法 总结:
- 通过 synchronized 进行线程安全保证
 - 在主线程进行获取,但是需要等加载的完成后才能进行读,所以get 方法可能造成主线程阻塞,从而导致 ANR 。
 - 加载完成后读的过程只涉及内存的读。
 
四.putXXX 和 apply/commit
提交数据的时候首先要获取 Editor 对象1
2
3
4
5
6
7
8
9
10@Override
   public Editor edit() {
       //这里也是需要等待加载 xml 文件到内存完成后
       //才能创建 EditorImpl
       synchronized (mLock) {
           awaitLoadedLocked();
       }
       return new EditorImpl();
   }
以 putString 为例1
2
3
4
5
6
7
8
9@Override
       public Editor putString(String key, @Nullable String value) {
       //使用 synchronized 进行线程安全保证
           synchronized (mEditorLock) {
           //将数据暂时保存到 mModified 这个 Map 中
               mModified.put(key, value);
               return this;
           }
       }
1.apply
1  | @Override  | 
1  | private MemoryCommitResult commitToMemory() {  | 
回到 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@Override
        public void apply() {
            final long startTime = System.currentTimeMillis();
            //将修改先写入内存 
            final MemoryCommitResult mcr = commitToMemory(); 
            //这里只是创建了一个 Runnable ,并不是一个线程
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                           //注意这里会进行等待也就是 需要 MemoryCommitResult 的 setDiskWriteResult 方法执行后
                           //才能返回 
                           
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                        if (DEBUG && mcr.wasWritten) {
                            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                    + " applied after " + (System.currentTimeMillis() - startTime)
                                    + " ms");
                        }
                    }
                };
            
            //添加到队列中
            QueuedWork.addFinisher(awaitCommit);
            //这里也只创建一个 Runnable 
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                    //这里执行了上面的 awaitCommit 的 run 方法
                    //不是 start 
                    //并将队列中的 awaitCommit 移除
                        awaitCommit.run();
                        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);
        }
1  | private void enqueueDiskWrite(final MemoryCommitResult mcr,  | 
在 QueuedWork.java 中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
30public static void queue(Runnable work, boolean shouldDelay) {
   //getHandler 获取的是一个 handlerThread 的hanlder ,也就是一个子线程
    Handler handler = getHandler();
    synchronized (sLock) {
        sWork.add(work);
        if (shouldDelay && sCanDelay) {
        //发送一个消息 MSG_RUN 到 handler 所在线程,也就是 handlerThread 子线程中去
            handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
        } else {
            handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
        }
    }
}
private static Handler getHandler() {
    synchronized (sLock) {
        if (sHandler == null) {
        //创建一个 handlerThread ,并执行 start 方法
        //这就是 apply 写到磁盘的线程
            HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                    Process.THREAD_PRIORITY_FOREGROUND);
            handlerThread.start();
            sHandler = new QueuedWorkHandler(handlerThread.getLooper());
        }
        return sHandler;
    }
}
1  | // Handler 的处理  | 
QueuedWork.addFinisher(awaitCommit);
在上面的方法中注意到 对每个 apply 都会创建一个相应的 awaitCommit,并添加到 QueuedWork 的一个队列中,但是在 QueuedWork 注意到有这样一个方法 waitToFinish1
2
3
4
5
6
7
8
9
10
/**
 * Trigger queued work to be processed immediately. The queued work is processed on a separate
 * thread asynchronous. While doing that run and process all finishers on this thread. The
 * finishers can be implemented in a way to check weather the queued work is finished.
 *
 * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
 * after Service command handling, etc. (so async work is never lost)
 */
public static void waitToFinish() {}
这个方法会在 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理后被调用,并且调用这个方法的目的是为了确保异步任务被及时完成。
可以看到 waitToFinish 都是在 ActivityThread 中,也就是主线程调用的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
47public static void waitToFinish() {
       
        boolean hadMessages = false;
        Handler handler = getHandler();
        synchronized (sLock) {
            if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
                // Delayed work will be processed at processPendingWork() below
                handler.removeMessages(QueuedWorkHandler.MSG_RUN);
            }
            // We should not delay any work as this might delay the finishers
            sCanDelay = false;
        }
        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
        //这个方法就会执行 所有的 Runnable 的run 返回
        //这个时候 processPendingWork 是执行在主线程中
        
            processPendingWork();
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
        try {
            while (true) {
                Runnable finisher;
                synchronized (sLock) {
                    finisher = sFinishers.poll();
                }
                if (finisher == null) {
                    break;
                }
                finisher.run();
            }
        } finally {
            sCanDelay = true;
        }
       ...
    }
这种设计是为了保证 SP 可靠的、保证写入完成的存储机制。
apply 总结
- apply 没有返回值
 - apply 是在主线程将修改数据提交到内存, 然后再子线程(HandleThread)提交到磁盘
 - apply 会将 Runnble 添加到 QueueWork 中,如果主线程 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理,就会在主线程执行 提交到硬盘的方法,这个过程就会造成主线程 ANR
 
2.commit
1  | public boolean commit() {  | 
1  | private void enqueueDiskWrite(final MemoryCommitResult mcr,  | 
commit 总结
- commit 有返回值
 - commit 是在主线程将修改数据提交到内存, 然后再在主线程提交到磁盘
 - 用 commit 方法最保险。如果担心在主线程调用 commit 方法会出现 ANR,可以将所有的 commit 任务放到单线程池的线程里去执行。
 
总结
- Sp 主线程 getXX 方法会 ANR
 - Sp apply 方法会 ANR
 - Sp 主线程调用 commit 方法 ANR