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

Asp.net 手机网站制作会员卡营销策划方案

Asp.net 手机网站制作,会员卡营销策划方案,易点租电脑租赁官网,一个人做公司管理网站SP的使用及存在的问题 SharedPreferences(以下简称SP)是Android本地存储的一种方式,是以key-value的形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里,SP的使用示例及源码解析参见:Android本地存储之SharedPreferences源码解析。以…

SP的使用及存在的问题

SharedPreferences(以下简称SP)是Android本地存储的一种方式,是以key-value的形式存储在/data/data/项目包名/shared_prefs/sp_name.xml里,SP的使用示例及源码解析参见:Android本地存储之SharedPreferences源码解析。以下是SP的一些结论:

  • SharedPreferences读取xml文件时,会以DOM方式解析(把整个xml文件直接加载到内存中解析),在调用getXXX()方法时取到的是内存中的数据,方法执行时会有个锁来阻塞,目的是等待文件加载完毕,没加载完成之前会wait()
  • SP第一次初始化到读取到数据存在一定延迟,因为需要到文件中读取数据,因此可能会对UI线程流畅度造成一定影响,严重情况下会产生ANR
  • SharedPreferences写文件时,如果调用的commit(),会将数据同步写入内存中,内存数据更新,再同步写入磁盘中; 如果调用的apply(),会将数据同步写入内存中,内存数据更新,然后异步写人磁盘,也就是说可能写磁盘操作还没有完成就直接返回了。在UI线程中建议使用apply(),因为同步写磁盘,当文件较大时,commit()会等到写磁盘完成再返回,可能会有ANR问题。
  • 写文件时即使用的是apply()方法,依然有可能会造成ANR问题,这是为什么呢?先看下apply()的流程。
SharedPreferencesImpl#apply()流程分析(基于8.0以上版本)
SharedPreferencesImpl$EditorImpl
@Override
public void apply() {final long startTime = System.currentTimeMillis();// 写入内存(更新修改的字段)final MemoryCommitResult mcr = commitToMemory();// 使用CountDownLatch实现等待写入文件操作完成final Runnable awaitCommit = new Runnable() {@Overridepublic void run() {try {// writtenToDiskLatch初始化为CountDownLatch(1)mcr.writtenToDiskLatch.await();} catch (InterruptedException ignored) {}}};// 将awaitCommit添加到等待队列中,后续Activity/Servicede的onStop()会执行该Runnable等待文件写入完成QueuedWork.addFinisher(awaitCommit);Runnable postWriteRunnable = new Runnable() {@Overridepublic void run() {awaitCommit.run();QueuedWork.removeFinisher(awaitCommit);}};// 将待写入文件的集合添加到工作任务队列中SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);notifyListeners(mcr);
}

QueuedWork.addFinisher(awaitCommit)awaitCommit加入到等待队列中,awaitCommit在执行时利用CountDownLatch机制可以实现对当前线程的阻塞效果,后续ActivityonStop()中会将这里的awaitCommit取出来执行,即UI线程会阻塞等待sp文件写入磁盘,写入操作是通过SharedPreferencesImpl#enqueueDiskWrite()完成的,写入成功后会通过writtenToDiskLatch.countDown()释放awaitCommit中的锁,如果写入操作比较耗时,就会造成ANR问题。

SharedPreferencesImpl.java

private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {final boolean isFromSyncCommit = (postWriteRunnable == null);final Runnable writeToDiskRunnable = new Runnable() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {// 写入硬盘操作writeToFile(mcr, isFromSyncCommit);}synchronized (mLock) {mDiskWritesInFlight--;}if (postWriteRunnable != null) {postWriteRunnable.run();}}};// commit()场景下会在当前线程进行写入硬盘操作if (isFromSyncCommit) {boolean wasEmpty = false;synchronized (mLock) {wasEmpty = mDiskWritesInFlight == 1;}if (wasEmpty) {writeToDiskRunnable.run();return;}}// 添加到写入硬盘的工作队列QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

QueuedWork.java

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);}}
}// 构造一个Handler并传入HandlerThread的Looper,即Handler会在工作线程中处理消息
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;}
}private static class QueuedWorkHandler extends Handler {static final int MSG_RUN = 1;QueuedWorkHandler(Looper looper) {super(looper);}public void handleMessage(Message msg) {if (msg.what == MSG_RUN) {// (1) 消息队列的工作线程中执行processPendingWork();}}
}// 该方法存在两种执行路径: (1)在消息队列对应的工作线程中执行、(2)当前线程执行(执行前会将任务队列克隆并清空)
private static void processPendingWork() {synchronized (sProcessingWork) {LinkedList<Runnable> work;synchronized (sLock) {// a. 拷贝工作队列中的任务集合,然后将原任务集合清理,当(2)场景主线程执行到这里时因为集合没有任务直接跳过,进入等待写入磁盘任务完成work = (LinkedList<Runnable>) sWork.clone();sWork.clear();// b. 移除队列中的所有消息,下面立即处理getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);}if (work.size() > 0) {// 取出Runnable并执行for (Runnable w : work) {w.run();}}}
}

QueuedWork.waitToFinish

Activity的onStop()Service的onDestroy()执行时,都会调用到QueuedWork.waitToFinish()方法:

ActivityThread.java

private void handleStopService(IBinder token) {Service s = mServices.remove(token);if (s != null) {try {if (localLOGV) Slog.v(TAG, "Destroying service " + s);s.onDestroy();s.detachAndCleanUp();// 看这里QueuedWork.waitToFinish();//......} catch (Exception e) {}}
}@Override
public void handleStopActivity(IBinder token, int configChanges,PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {final ActivityClientRecord r = mActivities.get(token);r.activity.mConfigChangeFlags |= configChanges;final StopInfo stopInfo = new StopInfo();performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,reason);// 大于API11的时候执行if (!r.isPreHoneycomb()) {// 看这里QueuedWork.waitToFinish();}//......
}

Activity的onStop()Service中的onDestroy()都是间接在ActivityThread中的handleStopService()、handleStopActivity()执行的,这两个方法里都会执行到QueuedWork.waitToFinish()

public static void waitToFinish() {long startTime = System.currentTimeMillis();boolean hadMessages = false;Handler handler = getHandler();synchronized (sLock) {if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {// Delayed work will be processed at processPendingWork() belowhandler.removeMessages(QueuedWorkHandler.MSG_RUN);}// We should not delay any work as this might delay the finisherssCanDelay = false;}StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();try {// (2) 把任务取出来,直接在当前线程处理文件操作 8.0之后的逻辑(文件操作容易导致anr),因为之前清理任务集合,这里可能会立即执行完成进入下面执行等待状态processPendingWork();} finally {StrictMode.setThreadPolicy(oldPolicy);}try {while (true) {Runnable finisher;synchronized (sLock) {// 重点finisher = sFinishers.poll();}if (finisher == null) {break;}finisher.run();}} finally {sCanDelay = true;}}
}

这里的sFinishers中取的Runnable就是在写文件之前通过QueuedWork.addFinisher(awaitCommit)添加的,当取出awaitCommit执行时即会阻塞当前线程,如果apply()中写入磁盘时间过长导致awaitCommit的锁没有及时释放,UI线程就会因为长时间被阻塞得不到执行而出现ANR了。

总结如下图:

图片来自:今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待,所以结论是:使用apply()依然有可能会造成ANR问题。

8.0以下 写文件流程

public void apply() {final MemoryCommitResult mcr = commitToMemory();// 这里的操作是为了CountDownLatch实现等待效果final Runnable awaitCommit = new Runnable() {public void run() {try {mcr.writtenToDiskLatch.await();} catch (InterruptedException ignored) {}}};QueuedWork.add(awaitCommit);Runnable postWriteRunnable = new Runnable() {public void run() {awaitCommit.run();QueuedWork.remove(awaitCommit);}};SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}

QueuedWork.waitToFinish()

public static void waitToFinish() {Runnable toFinish;while ((toFinish = sPendingWorkFinishers.poll()) != null) {toFinish.run();}
}

8.0以下的流程相对更简单一些,但核心流程是一样的,当在UI线程中调用到QueuedWork.waitToFinish()时,如果写入磁盘的操作还未完成且耗时比较长,都会引起UI线程ANR

如何优化

Jetpack DataStore替代

Jetpack DataStore 是一种改进的新数据存储解决方案,允许使用协议缓冲区存储键值对或类型化对象。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences(以下统称为SP)的一些缺点DataStore基于Kotlin协程和Flow实现,并且可以对SP数据进行迁移,旨在取代SP

DataStore提供了两种不同的实现:Preferences DataStoreProto DataStore,其中Preferences DataStore用于存储键值对Proto DataStore用于存储类型化对象DataStore更详细的介绍参见:Android Jetpack系列之DataStore

MMKV替代

MMKV 是基于 mmap 内存映射的key-value 组件,底层序列化/反序列化使用 protobuf实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win32 / POSIX 平台,一并开源。

注:mmap 内存映射,可以提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

MMKV地址:https://github.com/tencent/mmkv

apply()使用优化

主要是优化UI线程中执行QueuedWork.waitToFinish(),当队列执行poll()时,通过反射修改poll()的返回值,将其设为null,这样UI线程会继续往下执行而不会原地阻塞等待了。示例如下(注意8.0以上8.0以下处理不一样)

object SPHook {fun optimizeSpTask() {if (Build.VERSION.SDK_INT < 26) {reflectSPendingWorkFinishers()} else {reflectSFinishers()}}/*** 8.0以上 Reflect finishers**/private fun reflectSFinishers() {try {val clz = Class.forName("android.app.QueuedWork")val field = clz.getDeclaredField("sFinishers")field.isAccessible = trueval queue = field.get(clz) as? LinkedList<Runnable>if (queue != null) {val linkedListProxy = LinkedListProxy(queue)field.set(queue, linkedListProxy)log("hook success")}} catch (ex: Exception) {log("hook error:${ex}")}}/*** 8.0以下 Reflect pending work finishers*/private fun reflectSPendingWorkFinishers() {try {val clz = Class.forName("android.app.QueuedWork")val field = clz.getDeclaredField("sPendingWorkFinishers")field.isAccessible = trueval queue = field.get(clz) as? ConcurrentLinkedQueue<Runnable>if (queue != null) {val proxy = ConcurrentLinkedQueueProxy(queue)field.set(queue, proxy)log("hook success")}} catch (ex: Exception) {log("hook error:${ex}")}}/*** 在8.0以上apply()中QueuedWork.addFinisher(awaitCommit), 需要代理的是LinkedList,如下:* # private static final LinkedList<Runnable> sFinishers = new LinkedList<>()*/private class LinkedListProxy(private val sFinishers: LinkedList<Runnable>) :LinkedList<Runnable>() {override fun add(element: Runnable): Boolean {return sFinishers.add(element)}override fun remove(element: Runnable): Boolean {return sFinishers.remove(element)}override fun isEmpty(): Boolean = true/*** 代理的poll()方法,永远返回空,这样UI线程就可以避免被阻塞,继续执行了*/override fun poll(): Runnable? {return null}}/*** 在8.0以下代理* // The set of Runnables that will finish or wait on any async activities started by the application.* private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = new ConcurrentLinkedQueue<Runnable>();*/private class ConcurrentLinkedQueueProxy(private val sPendingWorkFinishers: ConcurrentLinkedQueue<Runnable>) :ConcurrentLinkedQueue<Runnable>() {override fun add(element: Runnable?): Boolean {return sPendingWorkFinishers.add(element)}override fun remove(element: Runnable?): Boolean {return sPendingWorkFinishers.remove(element)}override fun isEmpty(): Boolean = true/*** 代理的poll()方法,永远返回空,这样UI线程就可以避免被阻塞,继续执行了*/override fun poll(): Runnable? {return null}}
}
http://www.dinnco.com/news/49842.html

相关文章:

  • 集约化建设政府网站在线网站分析工具
  • 招聘网站建设方案模板北京百度关键词排名
  • 网站产品后台界面怎么做优化大师人工服务电话
  • 网站制作 番禺新闻头条今日最新消息
  • c2b做的好的网站销售怎么做
  • 网站备案域名所有人360优化大师app
  • wordpress去掉链接中的分类郑州seo技术服务顾问
  • 性价比最高网站建设电话口碑营销的步骤
  • 网站建设与网页设计制作教程seo怎么优化方法
  • 网站滚动效果怎么做的企业管理培训视频免费
  • 网站建设的软文素材电脑优化大师官方免费下载
  • 网站建设中怎么设置默认页烟台seo
  • 龙湾网站建设北京seo运营
  • 免费ps软件手机版武汉seo外包平台
  • 做泌尿科网站价格公司宣传推广方案
  • 新化网站建设友情贴吧
  • ping网站怎么做什么叫外链
  • office365做企业网站如何做seo搜索引擎优化
  • 学做网站论坛vip账户企业网站建设规划
  • wordpress如何超过2M南宁seo教程
  • ur网站建设seo厂家电话
  • 网站上做的vi设计是怎么做的今日时事新闻
  • 平面设计师如何做网站网站排名工具
  • 天津做家政的网站哪里有网络推广
  • 供应链管理系统的功能aso优化榜单
  • 什么网站可以做片头2024年3月新冠肺炎
  • 徐州网站建设案例深圳网站开发公司
  • 张家口网站建设百度搜索的优势
  • 哪里有帮做微课的网站在线网页生成器
  • 建设网站如何赢利新闻热搜榜 今日热点