背景
SP操作问题
SP是非常好用的轻量存储方式,对少量数据的存储性能出众。但是对SP错误的使用,也会带来各种各样的问题,甚至会导致ANR。
目前我们代码中所有的SP操作都封装在PreferencesHelp这个工具类。
SP性能优化点
SP性能变差的原因有很多。
1.原生API的限制主要有以下两方面:
(1)IO瓶颈
(2)锁性能差
2.对SP的不当封装也会间接造成数据读写性能差。(我们没有跨进程问题,并没有用到CP操作SP)
IO瓶颈
IO瓶颈造成SP性能差是最大的原因,解决了IO瓶颈,80%的性能问题就解决了。
SP的IO瓶颈包括读取数据到内存与数据写入磁盘两部分。
1.读取数据到内存有两个场景会触发:
(1)SP文件没有被加载到内存时,调用getSharedPreferences方法会初始化文件并读入内存。
(2)版本低于android_H或使用了MULTI_PROCESS标志时,每次调用getSharedPreferences方法时都会读入。
2.数据写入磁盘也有两个场景会触发:
(1)Editor的commit方法,每次执行时同步写入磁盘。
(2)Editor的apply方法,每次执行时在单线程池中加入写入磁盘Task,异步写入。
commit和apply的方法区别在于同步写入和异步写入,以及是否需要返回值。
在不需要返回值的情况下,使用apply方法可以极大的提高性能。
同时,多个写入操作可以合并为一个commit/apply,将多个写入操作合并后也能提高IO性能。
如下的写法尽量避免:
锁性能差
SP的get操作,会锁定SharedPreferences对象,互斥其他操作。
SP的put操作,getEditor及commitToMemory会锁定SharedPreferences对象,put操作会锁定Editor对象,写入磁盘更会锁定一个写入锁。
由于锁的缘故,SP操作并发时,耗时会徒增。减少锁耗时,是另一个优化点。
由于读写操作的锁均是针对SP实例对象的,将数据拆分到不同的sp文件中,便是减少锁耗时的直接方案。
目前我们所有的sp键值对都放在一个xml文件中,我们需要降低单文件访问频率,多文件均摊访问,以减少锁耗时。
SP优化的建议
1.将SP作为耗时操作对待,尽量减少无谓的调用。
譬如以下代码,SP读一次即可:
if(sp.getUserId()>0){ int id=sp.getUserId(); } |
2.多个写入操作合并后统一commit/apply,提高IO性能。
3.拆分SP文件,降低锁耗时。
4.如果需要可以提供跨进程访问SP的接口。不过我们当前并没有跨进程的问题。
IO操作问题
在UI线程中存在许多不合理的IO操作:例如
这非常容易导致ANR,因此类似文件IO操作必须异步的方式,不能心存侥幸...
现在我们引入了Rxjava,我们需要逐渐适应Rxjava响应式编程的思想,替代目前代码中AsyncTask、new Thread或者handler这种处理异步操作的方式。
但是有时候用Rxjava,为了方便我们会这样写:
//错误写法 Observable.just(FileUtil.readBeanFromInputStream(mContext,mContext.getResources().openRawResource(R.raw.xueche_danmu),TanmuBeanList.class)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<TanmuBeanList>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(TanmuBeanList tanmuBeanList) { //处理TanmuBeanList } }); |
问题:业务操作是在当前线程执行
推荐的写法:
Observable.create(new Observable.OnSubscribe<TanmuBeanList>() { @Override public void call(Subscriber<? super TanmuBeanList> subscriber) { // 业务功能代码开始 TanmuBeanList beanList = FileUtil.readBeanFromInputStream(mContext,mContext.getResources().openRawResource(R.raw.xueche_danmu),TanmuBeanList.class); // 业务功能代码结束 if(!subscriber.isUnsubscribed()) { subscriber.onNext(beanList); subscriber.onCompleted(); } } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<TanmuBeanList>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(TanmuBeanList tanmuBeanList) { } }); |
Storage框架介绍
缺点:
1.Kvcache工厂类里方法比较多,这里为了统一SP操作和普通文件IO操作,牺牲了一些SP的api方法...
2.框架内部支持跨进程SP操作,但是已经被屏蔽掉,不过目前并不需要这部分功能
示例用法
//设置文件扩展信息,包括新鲜度日期,过期时间,路径(路径不推荐设置) final StorageFileConfig config = new StorageFileConfig(); config.setFreshTime(Long.MAX_VALUE)//设置新鲜度日期,如果超过此日期会删除文件,并返回文件内容 .setExpireTime(Long.MAX_VALUE);//设置过期时间,如果过期会删除文件并返回null String fileName = "sample_file"; String content = "这是要保存的文件内容,一个String字符串"; //写入文件操作 RxDataManager.getInstance().createFilePersistent(config) .putStringAsync(fileName,content) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Boolean>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Boolean aBoolean) { } }); //读取文件操作 RxDataManager.getInstance().createFilePersistent() .getStringAsync(fileName) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String s) { } }); |