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

搭建企业网站流程河南旅游网站建设

搭建企业网站流程,河南旅游网站建设,做网站后台主要负责什么,如何模板建站前提:基于 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/556733.html

相关文章:

  • 杭州网站建设市场wordpress多用途主题排行
  • 专业网站排名优化小程序appld
  • 网站建设中的注册和登录页面网页传奇哪个最火
  • 妇科医院网站设计高端酒店网站模板
  • 哪里有网站制作价格开发外贸产品的网站
  • 大连网站建设报价优质商家用asp.net开发网站的优势
  • 信息门户网站建设报价沈阳网站优化推广方案
  • 做网站补贴网站建设的原则
  • 营销型网站建设方案书欧米茄表价格官网报价
  • 网站建设速度如何解决购物网站设计会员管理模块
  • 电子商务网站建设重要性百度怎么自己做网站吗
  • 网站优化的好处建立网站站点的过程
  • 哪个网站可以做ppt赚钱济南网络品牌推广
  • 国外有没有专门做靶材的网站网站文案优化
  • 搭建淘宝客网站源码包子店vi设计
  • 大型网站开发企业重庆做木门网站公司
  • dtcms网站开发网站内容更新外包
  • 网站建设是自己做好还是外包江西网站建设推广
  • 成都网站设计常凡云简要叙述如何规划建设一个企业网站
  • 建凡网站html代码换行
  • 青海建设兵团网站小院phton可以做网站吗
  • 网站建设的学习商务网站主页设计公司
  • 在工作室上班网站建设会好吗河北邯郸做移动网站
  • 本溪网站建设公司苏州最大的网站建设公司
  • 网站怎么做动态图片房地产销售好做吗
  • 访问自己做的网站吗在线推广企业网站的方法是
  • 化州+网站建设wordpress插件哪里下载
  • 网站快排是怎么做的站长工具的使用seo综合查询排名
  • 网站建设个人工作总结湖南营销型网站建设 要上磐石网络
  • 安徽省建设工程质量安全监督总站网站wordpress费用