SharedPreferencesCompat的使用

 
 

原文:https://www.jianshu.com/p/56b37a12f8ed

写在前面

最近看了不少大牛的推文,再加上最近工作的一些心得,发现做了这么久的Android开发,到最后还是最基础的知识点才是往高级进阶的重中之重。因此也萌生了做一个介绍基础知识的系列文章。目前来说先从support包里的一些控件,工具类开始然后延伸出去。 前几天写项目无意间IDE提示了SharedPreferencesCompat类,然后就多看了两眼...

关于SharedPreferencesCompat

SharedPreferencesCompat是V4包里的类,讲真,这个类并没有什么卵用。它本身对SharedPreferences并没有什么操作,还是需要通过传入SharedPreferences.Editor来实现数据的存储。 既然没有什么卵用,那为什么要出现这么一个鸡肋呢?还得从Editor.apply()Editor.commit()的区别说起。这个后面再讲,先来看下SharedPreferencesCompat的用法

SharedPreferencesCompat的用法

   var editor = getPreferences(Context.MODE_PRIVATE).edit()
   editor.putString(DEFAULT_KEY,value_sharedpref.text.toString())
   SharedPreferencesCompat.EditorCompat.getInstance().apply(editor)

上面是用kotlin写的,java的实现差不多。可以看到的是,还是需要获取Editor的对象,然后将键值对存放在Editor中,而SharedPreferencesCompat的使用仅仅是代替了最后一步apply或者是commit的操作。那是不是呢?答案是肯定的!

SharedPreferencesCompat源码解析

 public void apply(@NonNull SharedPreferences.Editor editor) {
       try {
            editor.apply();
        } catch (AbstractMethodError unused) {
            // The app injected its own pre-Gingerbread
            // SharedPreferences.Editor implementation without
            // an apply method.
            editor.commit();
       }
}

核心的代码部分就是上面这些了,可以看到,实际上就是先调用了editor.apply()方法,如果发生异常的话,则再调用editor.commit()的方法。简单说就是优先使用apply()方法。

Commit与Apply的对比

在看源码之前,我们先来分别实现下,来看下在时间效率上的差异
commit存储数据

        var editor = getPreferences(Context.MODE_PRIVATE).edit()
        for (i in 1..1000){
            editor.putString(DEFAULT_KEY+i,"value$i")
        }
        var startTime = System.currentTimeMillis()
        editor.commit()
        Toast.makeText(this@SharedPreferencesCompatActivity,"commit use time ${System.currentTimeMillis()-startTime}",Toa

apply存储数据

        var editor = getPreferences(Context.MODE_PRIVATE).edit()
        for (i in 1..1000){
            editor.putString(DEFAULT_KEY+i,"value$i")
        }
        var startTime = System.currentTimeMillis()
        editor.apply()
        Toast.makeText(this@SharedPreferencesCompatActivity,"apply use time ${System.currentTimeMillis()-startTime}",Toast.LENGTH_LONG).show()

在三星S6 edge plus上对比发现,1000条数据,commit大概需要10ms左右,而apply只需要1ms

居然有将近10倍的差距,那么commit和apply究竟是怎么实现的呢?

源码分析

SharedPreferences本身是一个接口,其实现的代码都在android.app.SharedPreferencesImpl类中,而Editor的实现都在EditorImpl类中,这是SharedPreferencesImpl的内部类。commit 和 apply 函数本身并不复杂,先来看源码:

  • commit
        public boolean commit() {
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            }
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }
  • apply
        public void apply() {
            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                    public void run() {
                        try {
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };

            QueuedWork.add(awaitCommit);

            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.remove(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);
        }

从上面的实现就可以看出为什么效率有这么大差距了,原因就是commit是同步的,而apply是异步执行的

commit的操作是在主线程中执行的,在enqueueDiskWrite方法中,如果第二个参数为null,并且mDiskWritesInFlight为1的时候,那么写操作则在当前线程中直接完成:

        final boolean isFromSyncCommit = (postWriteRunnable == null);

        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (SharedPreferencesImpl.this) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }

等写操作完成后再继续下面的步骤通知listener,最后返回写操作成功与否的标记

apply的实现是直接把写任务抛给了子线程,然后就通知listener,然后就没有然后了。所有的写操作都在子线程了,函数并不关心是否成功

这里有个疑问的地方,就是notifyListeners(mcr)。之前一直认为apply的时候,写磁盘和notify是在两个线程同时运行的,应该会出现读取的时候,还没写入的情况;但实际测试中并没有出现这种情况。原因是因为在获取内容值的时候,会先判断当前的SharepdPreferences是否加载,加载了,则直接返回mMap中的值,因为apply函数中在获取MemoryCommitResult的时候,已经把需要修改的值放到mMap中了,因此这个值并没有从磁盘里读出来,而是直接从内存里返回的。
主要的代码如下:

    public String getString(String key, @Nullable String defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

再看下awaitLoadedLocked()方法里

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();
        }
        while (!mLoaded) {
            try {
                wait();
            } catch (InterruptedException unused) {
            }
        }
    }

可以看到,如果mLoaded==true的话这个方法是直接跳过的;而mLoaded的赋值是在loadFromDisk()的方法里,而这个在SharedPreferencesImpl初始化的时候,或者在调用getSharedPreferences的时候都会调用的。因此回调里是可以接受到正确的值

总结

  • 对于存储结果不需要太关注时,可以使用apply的方法,否则使用commit的方法
  • 如果使用apply的方法,则可以考虑使用SharedPreferencesCompat来避免兼容的问题,毕竟apply的方法是API9 才添加的。


作者:seph_von
链接:https://www.jianshu.com/p/56b37a12f8ed
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值