目录

  • 一、概叙
  • 二、基本用法
  • 1、获取SharedPreferences对象
  • Context 类中的 getSharedPreferences方法
  • Activity 类中的 getPreferences()方法
  • PreferenceManager 类中的 getDefaultSharedPreferences()方法
  • 2、SharedPreferences的基本使用
  • 写入数据
  • 读取数据
  • 删除指定数据
  • 清空数据
  • 二、commit方法和apply方法的不同
  • 1、commit
  • 2、apply
  • 3、总结
  • 三、QueuedWork
  • 1、关于延迟磁盘写入
  • 2、主线程堵塞ANR问题


一、概叙

  1. SharedPreferences是一种轻量级的数据存储方式,采用键值对的存储方式。
  2. SharedPreferences只能存储少量数据,大量数据不能使用该方式存储,支持存储的数据类型有booleans, floats, ints, longs, strings。
  3. SharedPreferences存储到一个XML文件中的,路径在/data/data//shared_prefs/下,文件名以及存储后面详细讲述。

二、基本用法

1、获取SharedPreferences对象

要创建存储文件或访问已有数据,首先要获取SharedPreferences才能进行操作。
获取SharedPreferences对象有下面三种方式:

Context 类中的 getSharedPreferences方法

@Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return mBase.getSharedPreferences(name, mode);
    }

它有两个参数,第一个参数 name 指定了SharedPreferences存储的文件的文件名,第二个参数 mode 指定了操作的模式。这种方式获取的对象创建的文件可以被整个应用所有组件使用,有指定的文件名。
操作模式(mode)

  1. Context.MODE_PRIVATE:指定该SharedPreferences数据只能被本应用程序读、写。这是默认模式
public static final int MODE_PRIVATE = 0x0000;
  1. Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。该模式已弃用
@Deprecated
    public static final int MODE_WORLD_READABLE = 0x0001;
  1. Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序写。该模式已弃用
@Deprecated
    public static final int MODE_WORLD_WRITEABLE = 0x0002;
  1. Context.MODE_APPEND:该模式会检查文件是否存在,存在就将数据写到文件末尾,否则就创建新文件。
public static final int MODE_APPEND = 0x8000;

Activity 类中的 getPreferences()方法

public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }

这个方法只接收一个操作模式参数,使用这个方法时会自动将当前活动的类名作为 SharedPreferences 的文件名。

PreferenceManager 类中的 getDefaultSharedPreferences()方法

public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
                getDefaultSharedPreferencesMode());
    }
private static int getDefaultSharedPreferencesMode() {
        return Context.MODE_PRIVATE;
    }

这是一个静态方法,它接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。

2、SharedPreferences的基本使用

写入数据

  1. 创建一个SharedPreferences对象
    SharedPreferences sharedPreferences= getSharedPreferences(“data”,Context.MODE_PRIVATE);
  2. 实例化SharedPreferences.Editor对象
    SharedPreferences.Editor editor = sharedPreferences.edit();
  3. 将获取过来的值放入文件
    editor.putString(“name”, “Tom”);
    editor.putInt(“age”, 28);
    editor.putBoolean(“marrid”,false);
  4. 提交
    editor.commit();或者editor.apply();

读取数据

SharedPreferences sharedPreferences= getSharedPreferences(“data”, Context .MODE_PRIVATE);
String userId=sharedPreferences.getString(“name”,"");

删除指定数据

editor.remove(“name”);
editor.commit();或者editor.apply();

清空数据

editor.clear();
editor.commit();

二、commit方法和apply方法的不同

1、commit

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 {
            	//利用CountDownLatch等待写入磁盘执行完毕
                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;
        }
        
private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {
		//如果postWriteRunnable为空表示来自commit()方法调用
        final boolean isFromSyncCommit = (postWriteRunnable == null);
        final Runnable writeToDiskRunnable = new Runnable() {
                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;
            }
            //mDiskWritesInFlight表示正在执行的写入磁盘操作的数量
            //当commit提交,且mDiskWritesInFlight为1的时候,直接在当前所在线程执行写入磁盘操作
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }
        //如果正在执行的写入磁盘的操作数量大于1,则把当前任务加入到队列,异步排队执行
		//交给QueuedWork,QueuedWork内部维护了一个HandlerThread,一直执行写入磁盘操作。
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

注释:
9. 当调用commit()方法之后,首先将编辑的结果同步到内存中。
10. commit()方法调用 enqueueDiskWrite() 方法时,第二个参数postWriteRunnable传入空。这样在enqueueDiskWrite()方法中通过第二个参数是不是空来判断是不是cmomit提交。
11. 当mDiskWritesInFlight(正在执行的写入磁盘操作的数量)为1的时候,直接在当前所在线程执行写入磁盘操作。否则还是异步到QueuedWork中去执行。commit()时,写入磁盘操作会发生在当前线程的说法是不准确的
12. 每当有一次写入内存操作,既commitToMemory()方法,mDiskWritesInFlight值就会增加1。每当完成一次写入磁盘,既enqueueDiskWrite()方法,mDiskWritesInFlight的值就减1。

2、apply

public void apply() {
            final long startTime = System.currentTimeMillis();
			//写入到内存
            final MemoryCommitResult mcr = commitToMemory();
            //创建等待写入到磁盘的Runnable
            final Runnable awaitCommit = new Runnable() {
                    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");
                        }
                    }
                };
			//跟踪排队任务,保证任务执行完成
            QueuedWork.addFinisher(awaitCommit);
            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
			//加入到队列,排队执行
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
            
            notifyListeners(mcr);
        }

3、总结

  1. 除非你需要关心xml是否写入文件成功,否则你应该在所有调用commit的地方改用apply。
  2. SharedPreferences在实例化的时候会把SharedPreferences对应的xml文件内容全部读取到内存。
  3. 对于非多进程兼容的SharedPreferences的读操作是从内存读取的,不涉及IO操作。写入的时候由于内存已经保存了完整的xml数据,然后新写入的数据也会同步更新到内存,所以无论是用commit还是apply都不会影响立即读取。

三、QueuedWork

1、关于延迟磁盘写入

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);
            }
        }
    }
  1. 当apply()方式提交的时候,默认消息会延迟发送100毫秒,避免频繁的磁盘写入操作。
  2. 当commit()方式,调用QueuedWork的queue()时,会立即向handler()发送Message。
  3. 在enqueueDiskWrite()方法中,调用QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit)加入队列。isFromSyncCommit表示是不是同步提交。
  4. boolean isFromSyncCommit = (postWriteRunnable == null);从判断方式来看commit是同步提交,apply则不是同步提交。因为commit方法传入的postWriteRunnable为空。

2、主线程堵塞ANR问题

//QueuedWork.java
    public static void waitToFinish() {
        ...
          processPendingWork();//执行文件写入磁盘操作
        ....
    }
    private static void processPendingWork() {
        long startTime = 0;
      ....
     if (work.size() > 0) {
         for (Runnable w : work) {
             w.run();
         }
      ...  
    }

waitToFinish()会将,储存在QueuedWork的操作一并处理掉。在Activity的 onPause()、BroadcastReceiver的onReceive()以及Service的onStartCommand()方法之前都会调用waitToFinish()。大家知道这些方法都是执行在主线程中,一旦waitToFinish()执行超时,就会跑出ANR。