性能优化:如何彻底解决SharedPreferences造成的卡顿

这篇具有很好参考价值的文章主要介绍了性能优化:如何彻底解决SharedPreferences造成的卡顿。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

在上线 ANR 监控平台后,线上收集到了较多的ANR日志 ,从火焰图信息上看,函数阻塞在了QueuedWork 相关函数上 ,本文主要介绍的这一现象的原因以及如何解决这一问题。

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

本文介绍的解决方案,已放到github 上https://github.com/Knight-ZXW/SpWaitKiller , 供参考实现

SP任务 阻塞主线程导致ANR的原理

首先简单介绍下 QueuedWork这个类,QueuedWork主要是用来执行和跟踪一些进程全局的工作,但目前主要调度的SP相关的异步任务 ,当调用 SharedPreferences的 applay方法时,其所要执行的SP文件变更操作会被转化成对应的任务 并调用QueuedWork.queue 方法发送到QueuedWork类中进行执行。

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

同时,系统为了保证这些任务在一些关键动作触发前(如 页面跳转启动Activity) 已经被执行完成, 设计了一个等待机制。简单描述一下这个机制

•SP的apply操作 会产生2个Runnable对象,其实一个为具体文件修改的工作任务(Work Runnable),另一个为 等待任务(awitCommit), 当工作任务被执行完成时,会通过一个CountDownLatch[1]对象通知 等待任务,而awitCommit内部主要就是等待这个CountDownLatch计数器
•工作任务最终会通过 QueueWork的queue被发送到异步线程执行
•等待任务(awitCommit)会通过QueueWork的addFinisher函数被添加到 QueueWork内部的等待队列中
•最后,在系统的一些关键流程,比如ActivityThread执行handleStopActivity时会通过waitToFinish保证这些异步任务都已经被执行完成

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

而此时如果系统资源(cpu、io)比较紧张、或者是提交的异步任务较多,则可能导致onStop执行时间较长,从而导致ANR。

另外 在Android 8.0.0 以上的版本,google 在 waitToFinish的实现中做了一些改动,在原有的等待所有异步任务执行完成的基础上,会通过调用 processPendingWork 将QueueWork中未执行的任务直接取出在当前线程直接执行。这个变更的原因是waitToFinish调用的时机一般是主线程, 主线程的优先级会比QueueWork内部线程的优先级更高,因此未执行的任务重新分发到主线程直接执行,提高执行效率。

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

SP阻塞问题解决

反射替换 finishers队列对象

解决SP 造成的阻塞问题,有很多方式,比如将应用内使用SP的代码 通过字节码插桩改为MMKV或其他更高效键值存储库实现。另一种方式是 字节跳动在一篇分享的文章[2]中提出的 通过代理替换Queuework类内部的sFinishers对象,保证执行 waitToFinish时 队列长度为空实现的。

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
这里 sFinishers.poll 函数的调用在整个类中,只有这一个地方调用,因此 通过动态代理替换该对象,重写poll函数实现 使其总是返回null对象,并不会对其他流程造成影响

sFinishers对象在不同的版本具体使用的类不同

•android 8.0以下版本使用的是 ConcurrentLinkedQueue
•android 8.0 之后 使用的是LinkedList

以8.0以上版本为例,创建一个代理类,修改poll的实现

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

再通过反射替换掉该实现类。性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

解决processPendingWork调用

之前介绍过在 8.0及以上版本 调用waitToFinish 时,除了在执行等待finishers队列之前,会在当前线程直接调用processPendingWork函数。以下是程序运行时主线程 和 异步工作线程之间的关系图。

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

因此processPendingWork可能在主线程执行 也可能在异步线程中执行, 在 8.0~11.0下 processPendingWork的调用可能存在两个block点

1.异步线程正在执行 processPendingWork函数,异步工作线程持有 sProcessingWork锁,因此主线程执行 processPendingWork时 ,因为获取不到 sProcessingWork锁 ,出现锁等待

2.当主线程成功获取到 sProcessingWork锁,调用clone函数时,sWork队列中 确实存在未执行的任务,这部分任务将在主线程直接执行,如果此时IO操作较慢,则主线程因为慢IO出现阻塞甚至ANR

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

由于这两个原因,因此只代理clone函数是不可行的,因为如果异步线程正在执行processPendingWork函数,并且执行得比较慢,那么主线程还是会出现等待的情况。最终 采取的方式是,无论是在哪个线程执行,代理的clone函数都返回空队列,这样保证了processPendingWork的调用不会出现互相阻塞,相当于processPendingWork实际上没有执行任何操作, 并且通过反射获取QueuedWork的mHandler的Looper对象,创建一个新的Hander,并将sWork中的任务提交到这个Handler去执行,从而实现了无阻塞运行。
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

需要注意的是,由于hidden API的限制, sWork成员变量 只能在 target sdk version小于以下的app中被反射得到,因此如果希望在target大于28 的app正常工作,还需要 突破系统hidden api的限制,这里可以使用 LSPosed 提供 hiddenApiBypass[3]库。

另外 在Android 12 版本,这部分代码又发生了变更, 不再使用clone 和 clear的方式 拷贝集合副本,而是直接替换 sWork的引用来实现.

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

这样,替换clone函数的方案就不可行了,并且由于 sWork变量指向的对象在每次调用processPendingWork 都会发生变更,因此动态代理替换sWork对象的操作不能只执行一次。继续寻找可以hook的点, 对于

for (Runnable v: work)

这个代码 在字节码层面其实会被转换为迭代器的调用,因此 可以将之前的操作 转换到 iterator函数中执行,返回一个空的迭代器对象,因此将之前的方案从 代理 clone函数 改为代理 iterator函数,并且需要保证 每次调用获取迭代器函数后 再次将sWork对象重新代理掉。

最后

上述 方案代码量其实不多,因此我 在github上建了一个工程用来模拟并解决QueueWork任务阻塞造成的ANR问题, 可供参考 https://github.com/Knight-ZXW/SpWaitKiller . 在上线时,应当对使用到SP的业务进行相应的测试,比如如果存在跨进程组件依赖同一个SP文件的情况,由于我们取消了Activity 在Stop时的 SP文件变更的刷盘行为,因此如果跳转到其他进程的组件,而该组件又依赖于跳转前的SP变更的最新配置值,那么可能会出现问题。另外事实上,从收集ANR的其他上下文信息来看,虽然SP的操作阻塞导致了ANR操作,但是并不能说明真正的原因是因为SP导致的,比如可能由于物理内存紧张、频繁发生swa 操作影响了正常的io操作,影响了SP的刷盘速度,最终导致了ANR出现.

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
内存优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
UI优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
网络优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
Bitmap优化与图片压缩优化https://qr18.cn/FVlo89
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
多线程并发优化与数据传输效率优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构
体积包优化
性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

《Android 性能监控框架》:https://qr18.cn/FVlo89

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

性能优化:如何彻底解决SharedPreferences造成的卡顿,性能优化,Android,Framework,性能优化,binder,android,Framework,架构文章来源地址https://www.toymoban.com/news/detail-600407.html

到了这里,关于性能优化:如何彻底解决SharedPreferences造成的卡顿的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 解决前端性能问题:如何优化大量数据渲染和复杂交互?

    ✨✨祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心!✨✨  🎈🎈作者主页: 喔的嘛呀🎈🎈 目录 引言 一、分页加载数据 二、虚拟滚动 三、懒加载 四、数据缓存 五、减少重绘和回流 六、优化图片和资源: 七、合并压缩文件 八、使用Web Workers  在前端开发

    2024年03月10日
    浏览(50)
  • 一本书让你彻底搞懂安卓系统性能优化(文末送书5本)

    🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 前言 作者简介 内容简介  本书特色 读者对象 直播预告 文末福利         为什么

    2024年02月08日
    浏览(40)
  • 如何解决Git合并分支造成的冲突

    一、造成冲突的场景         在我们在参与项目开发的时候,通常会创建公共的文件,但是当我们编码完成,使用git进行分支合并时,往往会出现合并冲突,也就是负责不同部分的开发人员会对同一个文件的同一个部分进行修改,这个时候就需要我们解决合并造成的冲突

    2023年04月23日
    浏览(48)
  • 如何解决进行git合并造成的冲突详细的很

    如何解决进行 git 合并造成的冲突 场景: 在我们在参与项目开发的时候,通常会创建公共的文件,但是当我们编码完成,使用 git 进行分支合并时,往往会出现合并冲突,也就是负责不同部分的开发人员会对同一个文件的同一个部分进行修改,这个时候就需要我们解决合并造成

    2024年02月06日
    浏览(26)
  • 什么是性能优化?如何性能优化?

    场景说明 :当用户访问一个新网站时,首次绘制(FP)就像是网站的大门第一次打开。用户期待着的不只是白色的加载屏幕;他们希望有个迹象表明网站正在努力加载。 为何重要 :即使内容还未完全到位,FP给了用户一个信号——你的网站在工作。在竞争激烈的互联网上,每

    2024年04月23日
    浏览(29)
  • 元数据规范与性能优化:如何优化元数据规范的性能和可维护性?

    作者:禅与计算机程序设计艺术 在企业级应用中, 元数据 对业务的运行和数据分析至关重要,比如 基于元数据的报表、搜索、推荐 等功能都依赖于元数据信息。随着互联网应用数量的不断增加,越来越多的企业在存储元数据上花费更多的资源,如何提高元数据性能并保证

    2024年02月07日
    浏览(44)
  • 如何优化 Elasticsearch 查询性能

    优化 Elasticsearch 查询性能需要从多个方面入手。通过合理的索引设计、优化查询语句、优化硬件资源和集群架构等方面的优化,可以显著提高 Elasticsearch 的查询性能。 良好的索引设计是优化 Elasticsearch 查询性能的关键。可以通过以下几个方面来优化索引设计: 索引字段优化

    2024年01月21日
    浏览(33)
  • 前端如何做好性能优化?

    1、谈谈你对重构的理解。 网站重构是指在不改变外部行为的前提下,简化结构、添加可读性,且在网站前端保持一致的行为。也就是说,在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。 对于传统的网站来说,重构通常包括以下方面。 把表格( table)布

    2024年02月11日
    浏览(34)
  • C++高性能优化编程之如何测量性能(一)

    C++高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C++并发线程编程 不好的编程习惯,不重视程序性能测量分析让代码跑的更快,会导致 浪费大量的CPU周期、程序响应时间慢以及卡顿,用户满意度下降,进而浪费大量的时间返工去重构本应该一开始就

    2024年02月06日
    浏览(48)
  • 如何⽤webpack 来优化前端性能

    如何⽤webpack 来优化前端性能? ⽤webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。 压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-lo

    2024年02月13日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包