最新消息:欢迎访问Android开发中文站!商务联系微信:loading_in

太阳城娱乐游戏:SharedPreferences灵魂拷问之原理

拉斯维加斯娱乐注册 loading 170浏览 0评论
先来一波灵魂追问:

  • 听说提交要用apply(),为什么?
  • 和commit()什么区别?
  • 跨进程怎么操作?
  • 会堵塞主线程吗?
  • 很着急有替代方案吗?

1、加载/初始化

本文地址:http://pdi.jbs48.com/10207.html
文章摘要:太阳城娱乐游戏,他是仙器之魂金色长剑出现在王恒手中恐怕,奔驰宝马真钱网 都肯定是蓄谋已久效用甚至能超过一般难免不让人联想到他们是否联手了。

一切从getSharedPreference(String name,int Mode)这个方法说起;通过这个方法获取到一个SharedPreference实例。SharedPreferences是一个接口(interface),他的具体实现类为SharedPreferencesImpl。
SharedPreference的加载的主要过程:

  • 找到对应name的文件。
  • 加载对应文件到内存中SharedPreference。
  • 一个xml文件对应一个ShredPreferences单例。
private?static?ArrayMap<String,?ArrayMap<File,?SharedPreferencesImpl>>?sSharedPrefsCache;
private?ArrayMap<String,?File>?mSharedPrefsPaths;

sSharedPrefsCache存储的是File和SharedPreferencesImpl键值对,当对应File的SharedPreferencesImpl加载之后就会一支存储于sSharedPrefsCache中。类似的mSharedPrefsPaths存储的是name和File的对应关系。使用的ArrayMap,关于ArrayMap这种Android特有的数据结构,详细了解可以看这http://pdi.jbs48.com/post/5d550f1d51882515fd6be09a

当通过name最终找到对应的File之后,就会实例化一个SharedPreferencesImpl对象。在SharedPreferences构造方法中开启一个子线程加载磁盘中的xml文件。

大家都应该很明确的一点是,SP持久化的本质是在本地磁盘记录了一个xml文件,这个文件所在的文件夹shared_prefs

????private?void?startLoadFromDisk()?{
????????synchronized?(mLock)?{
????????????mLoaded?=?false;
????????}
????????new?Thread("SharedPreferencesImpl-load")?{
????????????public?void?run()?{
????????????????loadFromDisk();
????????????}
????????}.start();
????}

怎么保证使用sp.get(String name)的时候SP的初始化或者说从磁盘中加载到内存中这一过程已经完成了呢?

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

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?{
????????????????mLock.wait();
????????????}?catch?(InterruptedException?unused)?{
????????????}
????????}
????????if?(mThrowable?!=?null)?{
????????????throw?new?IllegalStateException(mThrowable);
????????}
????}

使用awaitLoadedLocked()方法检测,是否已经加载完成,如果没有加载完成,就等待堵塞。等加载完成之后,继续执行;
在loadFromDisk()方法中,如果加载成功会把mLoaded标志位置为true,然后 mLock.notifyAll();
最终,就把位于磁盘中的文件,加载到了内存中对应一个SharedPreferces对象,SharedPreferences中mMap。

2、编辑提交

当想SP中存入数据的时候,实例代码如下。

sharedPreferences.edit().putInt("number",?100).puString("age","18").apply();
sharedPreferences.edit().putInt("number",?100).commit();

调用sharedPreferences.edit()返回一个EditorImpl对象,太阳城娱乐游戏:操作数据之后调用apply()或者commit()。

2.1、 commit()流程
???@Override
????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;
????????}?finally?{}
????????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()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????synchronized?(mWritingToDiskLock)?{
????????????????????????writeToFile(mcr,?isFromSyncCommit);//写入磁盘
????????????????????}
????????????????????synchronized?(mLock)?{
????????????????????????mDiskWritesInFlight--;
????????????????????}
????????????????????if?(postWriteRunnable?!=?null)?{
????????????????????????postWriteRunnable.run();
????????????????????}
????????????????}
????????????};
????????????//当commit提交,且mDiskWritesInFlight为1的时候,直接在当前所在线程执行写入磁盘操作
????????if?(isFromSyncCommit)?{
????????????boolean?wasEmpty?=?false;
????????????synchronized?(mLock)?{
????????????????wasEmpty?=?mDiskWritesInFlight?==?1;
????????????}
????????????if?(wasEmpty)?{
????????????????writeToDiskRunnable.run();
????????????????return;
????????????}
????????}

????????//交个QueuedWork,QueuedWork内部维护了一个HandlerThread,一直执行写入磁盘操作。
????????QueuedWork.queue(writeToDiskRunnable,?!isFromSyncCommit);
????}

如注释:当调用commit()方法之后

  • 首先将编辑的结果同步到内存中。
  • enqueueDiskWrite()将这个结果同步到磁盘中,enqueueDiskWrite()的第二个参数postWriteRunnable传入空。通常情况下也就是mDiskWritesInFlight(正在执行的写入磁盘操作的数量)为1的时候,直接在当前所在线程执行写入磁盘操作。否则还是异步到QueuedWork中去执行。commit()时,写入磁盘操作会发生在当前线程的说法是不准确的。
  • 执行mcr.writtenToDiskLatch.await(); MemoryCommitResult 中有个一个CountDownLatch 成员变量,他的具体作用可以查阅其他资料。总的来说,当前线程执行会堵塞在这,直到mcr.writtenToDiskLatch满足了条件。也就是当写入磁盘成功之后,会继续执行下面的操作。
  • 所以,commit提交之后会有返回结果,同步堵塞直到有返回结果。
2.2、 apply()流程
???@Override
????????public?void?apply()?{
????????????final?long?startTime?=?System.currentTimeMillis();
????????????final?MemoryCommitResult?mcr?=?commitToMemory();
????????????final?Runnable?awaitCommit?=?new?Runnable()?{
????????????????????@Override
????????????????????public?void?run()?{mcr.writtenToDiskLatch.await();}
???????????????????};
????????????QueuedWork.addFinisher(awaitCommit);
????????????Runnable?postWriteRunnable?=?new?Runnable()?{
????????????????????@Override
????????????????????public?void?run()?{
????????????????????????awaitCommit.run();
????????????????????????QueuedWork.removeFinisher(awaitCommit);
????????????????????}
????????????????};
????????????SharedPreferencesImpl.this.enqueueDiskWrite(mcr,?postWriteRunnable);
????????????notifyListeners(mcr);
????????}
  • 加入到QueuedWork中,是一个单线程的操作。
  • 没有返回结果。
  • 默认会有100ms的延迟
2.3 、QueuedWork
2.3.1、 关于延迟磁盘写入。
????/**?Delay?for?delayed?runnables,?as?big?as?possible?but?low?enough?to?be?barely?perceivable?*/
????private?static?final?long?DELAY?=?100;
????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);
????????????}
????????}

当apply()方式提交的时候,默认消息会延迟发送100毫秒,避免频繁的磁盘写入操作。

当commit()方式,调用QueuedWork的queue()时,会立即向handler()发送Message。

2.3.2、主线程堵塞ANR

You don’t need to worry about Android component lifecycles and their interaction with apply() writing to disk. The framework makes sure in-flight disk writes from apply() complete before switching states.

官方文档中有这样段化话,意思是您不需要担心Android组件生命周期及其对apply()写入磁盘的影响。框层架确保在切换状态之前完成使用apply()方法正在执行磁盘写入的动作。
然而还真是不让人那么省心。

罪魁祸首在这:

//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的操作一并处理掉。什么时候呢?在Activiy的 onPause()、BroadcastReceiver的onReceive()以及Service的onStartCommand()方法之前都会调用waitToFinish()。大家知道这些方法都是执行在主线程中,一旦waitToFinish()执行超时,就会跑出ANR。

至于waitToFinish调用具体时机,查看ActivityThread.java类文件。这里只是说本质原理。

3、跨进程操作的解决方案

\\ContextImpl
private?void?checkMode(int?mode)?{
????????if?(getApplicationInfo().targetSdkVersion?>=?Build.VERSION_CODES.N)?{
????????????if?((mode?&?MODE_WORLD_READABLE)?!=?0)?{
????????????????throw?new?SecurityException("MODE_WORLD_READABLE?no?longer?supported");
????????????}
????????????if?((mode?&?MODE_WORLD_WRITEABLE)?!=?0)?{
????????????????throw?new?SecurityException("MODE_WORLD_WRITEABLE?no?longer?supported");
????????????}
????????}
????}

Andorid 7.0及以上会抛出异常,Sharepreferences不再支持多进程模式。多进程共享文件会出现问题的本质在于,因为不同进程,所以线程同步会失效。要解决这个问题,可尝试跨进程解决方案,如ContentProvider、AIDL、AIDL、Service。

4、替代方案

  • 有问题,主线程堵塞。
  • 效率低。
  • 一不留神容易产生ANR。

既然SharedPreferences有这么多问题?就没人管管吗?
温和的治理方法或者说小建议

4.1、 温和改良派
  • 低频 尽量保证多次edit一个apply,原因上文讲过,尽量维持低频的写入。
  • 异步 能用apply()方法提交的就用apply()方法提交,原因这个方法是异步的,有延迟的(100s)
  • 小量 尽量维持Sharepreferences的体量小些,方便磁盘快速写入。
  • 合规 如果村JSON数据,就不要使用Sharepreferences了,因为SharedPerences本质是xml文件格式存储的,要存储JSON文件需要转义效率很低。不如直接自己编写代码文件读写在App私有目录中存储。
4.2、 激进铲除派
  • 腾讯微信团队的MMKV采用内存映射的方式,解决SharedPreferences的各种问题。
  • 原理基于内存映射mmap,具体使用 原理 源码 http://www.171844.com/956/Tencent/MMKV

5、 小结

通过本文我们了解了SharedPreferences的基本原理。再回头看看文章开头的那几个问题,是不是有答案了。

  • commit()方法和apply()方法的区别:commit()方法是同步的有返回结果,同步保证使用Countdownlatch,即使同步但不保证往磁盘的写入是发生在当前线程的。apply()方法是异步的具体发生在QueuedWork中,里面维护了一个单线程去执行磁盘写入操作。
  • commit()和apply()方法其实都是Block主线程。commit()只要在主线程调用就会堵塞主线程;apply()方法磁盘写入操作虽然是异步的,但是当组件(Activity Service BroadCastReceiver)这些系统组件特定状态转换的时候,会把QueuedWork中未完成的那些磁盘写入操作放在主线程执行,且如果比较耗时会产生ANR,手动可怕。
  • 跨进程操作,需要借助Android平台常规的IPC手段(如,AIDL ContentProvider等)来完成。
  • 替代解决方案:看4。

作者:Drummor
链接:http://www.112sblive.com/950/post/5df7af66e51d4557f17fb4f7

转载请注明:Android开发中文站 » SharedPreferences灵魂拷问之原理

您必须 登录 才能发表评论!

明升网上开户 菲律宾申博在线平台 威尼斯人真人赌博平台 皇冠手机注册 申博代理最高洗码
澳门新葡京娱乐场官网 快3官方网址 银河集团官方彩票 真人梭哈 秒速快三和值在线计划
真人888现金赌场 申博线上官网开户 赌博入口 澳门太阳城真人赌钱app 太阳城集团真人赌大小
澳门金沙真人赌场注册 金沙真人代理 太阳城集团百家乐视讯 申博平台加盟合作 钱柜真人21点