Android性能细节优化之---理性认识SharedPreferences篇

众所周知Android开发,谷歌给提供了丰富的类库来提高开发效率,正是应为这些库的存在才能在快速的时间内实现我们的创意。不过事情始终是两面性的有好的一面也有不好的一面,就好比我的标题,谷歌给提供的保存一些配置信息的功能类,它方便了我们序列化存储程序状态,不用操心读写sd卡、内存缓存的问题,但是在我们的程序达到一定规模了,它的方便可就不方便了。
首先我贴出来初花化的代码一步一步分析和认识它。

protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...忽略代码
    final SharedPreferences prefs = getSharedPreferences("preferences", Context.MODE_PRIVATE);
}

这段代码很简单创建一个名字为preferences的xml文件,那我们就进入getSharedPreferences里面看看,应为这个函数实在Context里面的API,所以我们就去ContextImpl里面的实现

    @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.
        if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";
            }
        }

        File file;
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

我们看重点,mSharedPrefsPaths是个ArrayMap它里面存的是名字和文件的缓存,如果没有它就创建xml就读写了一次磁盘,而且它还加锁了 加锁了 加锁了 有木有 有木有 有木有,如果这时正是你的启动流程中可想而知卡你没商量,我们在往下走看return getSharedPreferences(file, mode);这个函数

@Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        checkMode(mode);
        ...
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        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;
    }

代码有点长,仔细看不要漏掉一点细节谷歌就爱这样干,在一个及不起眼的判断里面调用一个非常重要的函数,checkMode(mode);里面当你targetSdkVersion的版本大于N(24)时检查xml你传进去的模式是不是支持第三方App读写,又有锁再一次的想说卡你真的没商量,不过也不要慌执行下来时间也就在20 - 30毫秒之间,从代码中我们看见SharedPreferences的实现类是SharedPreferencesImpl,把实例加入缓存,如果支持多进程读取且已经有更改在读取一次sp.startReloadIfChangedUnexpectedly(),不好意思这段代码没有前面说的不起眼里面调用重要函数,哈哈。
创建过程其实很简单就是在存储卡上创建一个xml文件很不复杂,既然是个文件肯定有写入和读取我们先看看读取代码的实现逻辑

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

以此为例众多的获取方法只是参数不一样,我们知道SharedPreferences是采用key-value存储方式,所以SharedPreferencesImpl里面使用Map来管理,private Map

public void saveString(String key, String value, boolean isSync) {
        if (isSync) {
                mSharedPreferences.edit().putString(builder.toString(), value).commit();
            } else {
                mSharedPreferences.edit().putString(builder.toString(), value).apply();
            }
    }

我们先看commit的从上面也能看出他是同步方法,大家注意到没没只要写入操作都需要调用edit(),他里面很简单就是new了一个Editor,实现类是EditorImpl,而commit就是他里面的函数。

public boolean commit() {
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            ...
            return mcr.writeToDiskResult;
        }

 public void apply() {
            final long startTime = System.currentTimeMillis();
            final MemoryCommitResult mcr = commitToMemory();
            ...
            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
            ...
        }

首先调用commitToMemory()这里面比较有意思的是他支持批量写入,也就是说当put值时你可以不调用commit()和apply()只在空闲时写入也是可以的。
两个函数大体相似不同点是apply有个postWriteRunnable,先不说他的意思进入enqueueDiskWrite看下

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        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();
                    }
                }
            };

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

这回我们就知道postWriteRunnable的其中一个作用是异步还是同步的判断表示,这个函数一目了然,writeToFile把修改的值xml的所有值重新写了一遍,他不是增量的写入而是清空以前的把最新所有的写入文件 如果是同步的就直接调用Runnable的run函数,若果是异步的调用QueuedWork.queue函数,而这个函数也很简单

private static Handler getHandler() {
        synchronized (sLock) {
            if (sHandler == null) {
                HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                        Process.THREAD_PRIORITY_FOREGROUND);
                handlerThread.start();

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

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

通过一个线程来执行Runnable,达到异步的过程。


到此我把SharedPreferences从创建到读写详细的分析了下,他其实挺简单的简单到我们分分钟钟就能写出一个类似功能的类来,但是越是简单的就越容易出问题的,他在保证线程安全时就会牺牲性能,不保证安全他就牺牲了灵活性,所以我们一定要了解他,找到它的优缺点这样我们用起来才能找到平衡点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值