当前位置: 首页 > wzjs >正文

荔湾做网站公免费的黄冈网站有哪些平台

荔湾做网站公,免费的黄冈网站有哪些平台,天津做网站外包公司有哪些,b站网页入口免费不收费前提:基于 Android API 30 1. 认识 SharedPreference SharedPreference 是 Android 提供的轻量级的,线程安全的数据存储机制,使用 key-value 键值对的方式将数据存储在 xml 文件中,存储路径为 /data/data/yourPackageName/share…

前提:基于 Android API 30

1. 认识 SharedPreference

SharedPreference 是 Android 提供的轻量级的,线程安全的数据存储机制,使用 key-value 键值对的方式将数据存储在 xml 文件中,存储路径为

/data/data/yourPackageName/shared_prefs

存储的数据是持久化(persistent)的,应用重启后仍然可以获取到存储的数据。由于SharedPreference 是轻量级的,所以不适合存储过多和过大的数据的场景,这种情况下应该考虑数据库

2. 获取 SharedPreference

SharedPreference 是一个接口,实现类是 android.app.SharedPreferencesImpl,获取 SharedPreference 的方法由 Context 提供

val sp = context.getSharedPreferences("FreemanSp", MODE_PRIVATE)

第一个参数是 name,无论哪个 context 对象,只要传入的 name 一样,获取到的就是同一个SharedPreference 对象

第二个参数是 mode,Android 提供 4 中模式可选,其中三种已明确标识为过期

  • MODE_PRIVATE,私有模式,只能由当前应用读写
  • MODE_WORLD_READABLE,其他应用可读,已过期
  • MODE_WORLD_WRITEABLE,其他应用可写,已过期
  • MODE_MULTI_PROCESS,同一应用存在多进程情况下共享,不可靠,已过期

声明 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 这两中模式,其他应用可以完全读写自己应用的数据,这是及其危险的,谷歌推荐使用 ContentProvier 方式去做数据共享,共享的数据可以完全由 provider 定义的 uri 做出限制。
甚至在 android N 后指定这两种方式,应用直接报错

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

基于 mode 的限制,本文一切都已 MODE_PRIVATE 展开

创建 SharedPreferencesImpl 对象时,会起一个子线程,将 xml 文件解析的数据全量保存在内存中

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

如果存储着大量数据时会有占用较大内存

3. 写入数据

SharedPreference通过调用edit()方法获取到一个Editor对象,其实现类是android.app.SharedPreferencesImpl.EditorImpl。写入数据提供了同步 commit 和异步 apply 两个提交方法

3.1 commit()

调用方式

 sp.edit().putLong("currentTime", System.currentTimeMillis()).commit()

该方法是个同步方法,IDE 会出现警告提示

Commit your preferences changes back from this Editor to the SharedPreferences object it is editing. This atomically performs the requested modifications, replacing whatever is currently in the SharedPreferences.
Note that when two editors are modifying preferences at the same time, the last one to call commit wins.
If you don’t care about the return value and you’re using this from your application’s main thread, consider using apply instead

结论是如果不考虑返回结果或者在主线程,应该考虑用 apply 方法替代,原因来看下原码

        @Overridepublic 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 {// 当前线程 await,确保 mcr 已经提交到了磁盘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;}

再详细看下 commitToMemory(),这个方法的主要作用有两个:

  1. 写入内存
  2. 构造写入磁盘的map,需要注意的是这个map是全量写入的
        private MemoryCommitResult commitToMemory() {long memoryStateGeneration;boolean keysCleared = false;List<String> keysModified = null; // 需要提交存储的键值对Set<OnSharedPreferenceChangeListener> listeners = null;Map<String, Object> mapToWriteToDisk; // 需要写入磁盘的键值对,这里会是整个 xml 文件的全量数据synchronized (SharedPreferencesImpl.this.mLock) {// We optimistically don't make a deep copy until// a memory commit comes in when we're already// writing to disk.if (mDiskWritesInFlight > 0) { // 多线程提交时,对 mMap 做深拷贝,这个 mMap 就是整个 xml 文件的全量键值对// We can't modify our mMap as a currently// in-flight write owns it.  Clone it before// modifying it.// noinspection uncheckedmMap = new HashMap<String, Object>(mMap);}mapToWriteToDisk = mMap;// 同时要写入磁盘的提交计数标记为 +1,如果同时有异步多个提交,后面的 commit 有可能会提交到子线程,并且由于版本控制,最后一个 commit 的才会写入到磁盘mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0;if (hasListeners) {keysModified = new ArrayList<String>();listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());}synchronized (mEditorLock) {boolean changesMade = false;if (mClear) { // 只有在调用了 Editor#clear() 方法才会进入这段代码,因为 mEditorLock 锁if (!mapToWriteToDisk.isEmpty()) {changesMade = true;mapToWriteToDisk.clear();}keysCleared = true;mClear = false;}// 将用户要修改的数据合并到 mapToWriteToDiskfor (Map.Entry<String, Object> e : mModified.entrySet()) {String k = e.getKey();Object v = e.getValue();// "this" is the magic value for a removal mutation. In addition,// setting a value to "null" for a given key is specified to be// equivalent to calling remove on that key.if (v == this || v == null) {if (!mapToWriteToDisk.containsKey(k)) {continue;}mapToWriteToDisk.remove(k);} else {if (mapToWriteToDisk.containsKey(k)) {Object existingValue = mapToWriteToDisk.get(k);if (existingValue != null && existingValue.equals(v)) {continue;}}mapToWriteToDisk.put(k, v);}changesMade = true;if (hasListeners) {keysModified.add(k);}}mModified.clear();if (changesMade) {mCurrentMemoryStateGeneration++;}// 写入的版本管理,优化写入磁盘的写入次数,避免资源浪费和数据覆盖memoryStateGeneration = mCurrentMemoryStateGeneration;}}return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,listeners, mapToWriteToDisk);}

写入内存后接着就要执行写入磁盘

  SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);

enqueueDiskWrite 方法的主要作用是

  1. 同步写入磁盘
  2. 如果此时有新的 commit 提交,旧提交将会被抛弃,新提交则有可能提交到子线程
    private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {final boolean isFromSyncCommit = (postWriteRunnable == null); // 同步commit,isFromSyncCommit = truefinal Runnable writeToDiskRunnable = new Runnable() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {/** 真正执行写入到磁盘的操作,但是 writeToFile 会有 memoryStateGeneration 控制,如果当前的 mcr 版本和 memoryStateGeneration 不一致,则当前的写入会被对其* 这也是 commit 方法注释上写的:* Note that when two editors are modifying preferences at the same time, the last one to call commit wins*/ 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) { // apply 方法不会进入到这个代码块boolean wasEmpty = false;synchronized (mLock) {// 如果此时有新的 commit 执行到 commitToMemory ,则 mDiskWritesInFlight 则有可能大于 1,此时 wasEmpty = falsewasEmpty = mDiskWritesInFlight == 1;}if (wasEmpty) { // 如果 wasEmpty 为 true,则直接在当前线程执行写入磁盘writeToDiskRunnable.run();return;}}// 将写入磁盘的操作加入到子线程QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);}

总结

  • 如果主线程执行commit会在主线程执行 IO 操作,数据量太大可能会造成 ANR
  • edit() 方法每次都会返回一个新的Editor对象,应该缓存一个Editor对象重复使用,SP 是线程安全的
  • 如果实在需要使用commit,可以考虑将频繁写入的key单独放到一个 SP,这样避免资源浪费
  • 尽量避免多次调用commit,可以多次put数据,在合适实际执行一次commit
  • 避免使用commit
3.2 apply()

调用方式

 sp.edit().putLong("currentTime", System.currentTimeMillis()).apply()

该方法是个异步方法,Android 中推荐使用这个方法存储数据,直接看源码

public void apply() {final long startTime = System.currentTimeMillis();final MemoryCommitResult mcr = commitToMemory(); // 写入内存,构造写入磁盘的map,同commit一样,这个map是全量写入的final Runnable awaitCommit = new Runnable() { // 这个 Runnable 的作用就是为了统计写入磁盘的耗时@Overridepublic void run() {try {mcr.writtenToDiskLatch.await(); // block 待 mcr 写入磁盘完成后 writtenToDiskLatch 降为0,await()立刻返回,awaitCommit 继续执行} catch (InterruptedException ignored) {}if (DEBUG && mcr.wasWritten) {Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration+ " applied after " + (System.currentTimeMillis() - startTime)+ " ms");}}};QueuedWork.addFinisher(awaitCommit); // 将 awaitCommit 加入到 QueuedWork 子线程的消息队列// postWriteRunnable 会在写入磁盘完成后触发执行Runnable postWriteRunnable = new Runnable() {@Overridepublic void run() {awaitCommit.run(); // 触发 awaitCommit ,统计写入耗时QueuedWork.removeFinisher(awaitCommit);}};// postWriteRunnable 会在真正执行写入磁盘的 runnable 调用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也是先写入内存后并构建全量写入磁盘的map。不同的是在调用 enqueueDiskWrite时传入了一个postWriteRunnableenqueueDiskWrite 通过判断这个参数来区别是否是异步执行,该方法在commit流程中已分析过一部分,这里再来看apply的流程

   private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) { // apply 传入的 postWriteRunnable 不为 nullfinal boolean isFromSyncCommit = (postWriteRunnable == null); // isFromSyncCommit = falsefinal Runnable writeToDiskRunnable = new Runnable() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {// 如果 memoryStateGeneration 一致,将完成磁盘的写入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;}}// writeToDiskRunnable 直接加入子线程消息队列QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);}

总结

  • edit() 方法每次都会返回一个新的Editor对象,应该缓存一个Editor对象重复使用,SP 是线程安全的
  • 多次 apply 也不会造成多次写入磁盘,因为有版本控制
  • 推荐使用apply

4. SharedPreference 造成ANR的原因

  1. 在主线程 commit 写入大量的数据
  2. 在 ActivityThread 共用四个地方会调用QueuedWork.waitToFinish();,分别是:
  • handleServiceArgs
  • handleStopService
  • handlePauseActivity
  • handleStopActivity

这些都是Android组件的生命周期管理,如果QueuedWork有遗留的runnable没有执行完,则会将剩余的runnable在主线程执行,导致产生了ANR的风险

解决办法

  1. 避免使用commit,使用 apply
  2. 将频繁写入的key放到独立的 SharedPreference
  3. 使用 MMKV 迁移或者代替SharedPreference

5. MMKV 简单分析

  1. mmkv 提供了 importFromSharedPreference 方法用来做数据迁移
  2. mmkv 不支持 getAll 方法,因为通过 protoBuf 二进制存取,在其他存取操作时都会指定数据类型,比如getInt, getBoolean。但是如果使用getAll,mmkv不知道每个key对应的数据类型,所以无法decode。为了能够代理 getAll,并避免第三方sdk有使用该方法,可以将类型转换上移,在使用mmkv存取时统一用 String 类型,再拿出值时再由业务自身去判断(因为业务知道自己在写入时,每个key对应的数据类型),SharedPreference 自身也只是返回了一个 Map<String, Object> 对象
  3. 支持多进程读写
  4. 每次都是增量写入,本身不提供缓存。当写入时,发现内存不够会动态申请内存空间
http://www.dtcms.com/wzjs/21918.html

相关文章:

  • 公司网站建设的费用万网域名注册
  • 网站前端开发培训西安百度搜索关键词数据
  • 论吉林省网站职能建设海阳seo排名优化培训
  • 网站建设在线培训青岛网络优化代理
  • 域名注册网站建设谷歌外贸平台推广需要多少钱
  • 基于web的电子商务网站开发山东工艺美术学院网站建设公司
  • 二手书网站的建设规模南京疫情最新情况
  • 网站漂浮窗口代码江苏营销型网站建设
  • 做网站一年的费用产品seo是什么意思
  • 品牌微信网站定制什么是sem推广
  • 深圳蕾奥规划设计公司网站网站seo怎么做
  • 安卓应用开发工具抖音seo查询工具
  • 昆山网站建设百度搜索关键词优化
  • wordpress默认模板网站关键词如何优化上首页
  • 手机设计房子的软件北京seo关键词排名
  • 专业云南做网站品牌运营方案
  • 如何在网站后台备份数据库济南seo网站优化
  • 苹果自带建设网站百度app最新版本
  • 服务器安装WordPress没有权限访问关键词如何优化排名
  • 小型企业网站开发价格百度关键词统计
  • 菏泽网站建设天津百度
  • 网站建设的重点难点个人网站推广怎么做
  • xuezuo网站建设南宁网站建设网络公司
  • 织梦做网站要多长时间优化网站排名
  • 网页打不开是什么问题搜索引擎优化涉及的内容
  • 品牌商品怎么做防伪网站夸克浏览器网页版入口
  • 做网站编辑有人带吗韶关网站seo
  • 电商网站后台深圳竞价托管公司
  • 网站部署到终端机怎么做seo资料网
  • 淘乐惠网站怎么做深圳网站建设服务