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

news2024/10/7 18:30:51

背景

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

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

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

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

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

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

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

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

SP阻塞问题解决

反射替换 finishers队列对象

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


这里 sFinishers.poll 函数的调用在整个类中,只有这一个地方调用,因此 通过动态代理替换该对象,重写poll函数实现 使其总是返回null对象,并不会对其他流程造成影响

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

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

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

再通过反射替换掉该实现类。

解决processPendingWork调用

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

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

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

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

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


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

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

这样,替换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

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

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

《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 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/774422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Android] Android Studio 找不到一些包,编辑器显示红色

起因 此前写了一个项目里面用了 org.apache.commons.cli 这个库,在本地一段时间,因为其他业务影响就没有编写。今天突发奇想加点东西,发现—— 原本完好的项目,通过Android studio打开之后,org.apache.commons引入…

FPGA课程设计——数字电子时钟VERILOG(基于正点原子新起点开发板,支持8位或6位共阳极数码管显示时分秒毫秒,可校时,可设闹钟,闹钟开关,led指示)

2019 级 电子科学与技术 专业FPGA课程设计 报 告 2022 年 5 月 20 日 多功能数字电子钟的设计 摘要 电子设计自动化(EDA)是一种实现电子系统或电子产品自动化设计的技术,使用EDA技术设计的结果既可以用FPGA / CPLD来实施验证,…

k8s基础概念:port ,targetport,nodeport

在Kubernetes中,有三种类型的端口与Service相关:port、targetPort和NodePort。它们分别用于不同的用途: port:port字段定义了Service暴露给集群内部和外部的端口号。当你创建一个Service时,其他应用或服务可以通过该端…

配置右键点击文件夹通过IDEA打开项目

0、 前言 你是不是每次打开idea项目时,都需要走一遍这样的流程: 1、先启动idea 2、然后手动选择项目路径 3、打开项目 于是在打开项目的路上就耗费了大量的时间。 这篇文章会教你通过配置,让项目可以直接通过右键打开,大大提升项…

[新人向]MySQL和Navicat下载、安装及使用详细教程

MySQL和Navicat下载和安装及使用详细教程 因为这些软件的安装很多都是纯英文,作为新手安装真的需要摸索好久,包括我自己,所以Pipi酱就把自己的经验分享给大家~ MySQL的安装教程 一、下载安装包链接: 1.下载MySQL:ht…

aidl的学习(1)aidl中java.lang.RuntimeException: Didn‘t create service “XXX“

1、build中版本号为30及以上时&#xff0c;aidl无效&#xff0c;解决方案 ①在客户端的manifest.xml中添加一下代码&#xff0c;其中代码中的包名为服务端的包名 <manifest> ... <application> ....</application> <queries ><package android:na…

【Docker】了解和使用Docker

文章底部有投票活动&#xff0c;赶快参与进来吧&#x1f603; 相信大家在开发过程中都听说过 Docker 一词&#xff0c;至于 Docker 在开发中扮演的角色&#xff0c;估计好多人都说不上来&#xff0c;今天就让阿Q带大家一起揭开它神秘的面纱&#xff01; 文章目录 什么是容器&a…

SQL对决MySQL全面对比:差异、相似性、用途和优点!

SQL和MySQL被用于编程和管理关系型数据库。了解SQL和MySQL之间的主要区别&#xff0c;以及在使用这些数据库管理工具的工作中所需的技能。 SQL和MySQL是与数据库相关的语言。SQL是一种用于与关系型数据库中的数据交互的编程语言&#xff0c;而MySQL是一种实现SQL标准的开源数…

Android平台下的cpu利用率优化实现

背景 为了进一步优化APP性能&#xff0c;最近针对如何提高应用对CPU的资源使用、以及在多线程环境下如何提高关键线程的执行优先级做了技术调研。本文是对技术调研过程的阶段性总结&#xff0c;将分别介绍普通应用如何调控App频率、如何将指定线程绑定到特定CPU、如何通过提升…

迅为龙芯2K0500开发板-资料目录

基础资料 01 开发板硬件资料 02_开发及烧写工具 开发资料 03开发所需软件及驱动 04_pmon&Linux内核源码 05_内核镜像 06_pmon镜像 07_buildroot文件系统源码&镜像 08_测试文件 使用手册 01[北京迅为]iTOP-LS2K0500开发板快速启动手册_v1.0 02[北京迅为]iTOP-LS2K0500开…

抖音小程序开发常见问题

抖音小程序 问题1 抖音小程序调试预留白屏 解决 &#xff0c;连接wifi出现无法打开&#xff0c;用手机流量可以正常访问 抖音小程序 web-view 上传后白屏 抖音小程序使用 webview 白屏&#xff0c;使用web-vew打开h5页面白屏 解决&#xff1a;配置web-view域名 服务器域名配…

mysql获取上月月份

查看上个月数据 SELECT * FROM table WHERE date_format(time, %Y %m) date_format(DATE_SUB(curdate(), INTERVAL 1 MONTH),%Y %m)查询上个月 select date_format(DATE_SUB(curdate(), INTERVAL 1 MONTH), %m)行转列 – 行转列&#xff1a;方法① SELECT id, name, GROU…

申请阿里云服务器并搭建公网可支持数据上传下载的HTTP服务器

1. 前言 拥有一台自己的云服务器可以做很多事情。阿里云服务器毫无疑问是国内最好的。 阿里云服务器可以用于各种互联网应用的搭建和运行&#xff0c;提供稳定、高性能的服务。 阿里云服务器的用途&#xff0c;包括但不限于以下几个方面&#xff1a; 网站托管&#xff1a;可以将…

比较好用的洗地机品牌有哪些?家用洗地机推荐

近两年洗地机可谓是大热门清洁家电&#xff0c;源于它在清洁使用上面&#xff0c;不仅效率高过于传统清洁工具&#xff0c;同时在清洁地面的时候&#xff0c;干湿垃圾同步清洁&#xff0c;完全不需要手动清洁&#xff0c;使用体验非常棒&#xff01;那关于洗地机该如何挑选才能…

【Java】练手小项目|用Java实现一个简单的图书管理系统

博主简介&#xff1a;努力学习的预备程序媛一枚~博主主页&#xff1a; 是瑶瑶子啦所属专栏: Java岛冒险记【从小白到大佬之路】 文章目录 一、项目简介&#xff08;流程图&#xff09;二、设计思路&#xff08;UML图&#xff09;三、代码四、总结&反思&#xff1a; 一、项目…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(7 月 18 日论文合集)

文章目录 一、检测相关(19篇)1.1 Implementation of a perception system for autonomous vehicles using a detection-segmentation network in SoC FPGA1.2 Monocular 3D Object Detection with LiDAR Guided Semi Supervised Active Learning1.3 Active Learning for Object…

亚马逊在人工智能领域的投资机会

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 亚马逊股票的关键指标 猛兽财经认为亚马逊&#xff08;AMZN&#xff09;在拥抱生成式人工智能方面是非常认真的&#xff0c;以下指标就证明了这一点。 上周&#xff0c;亚马逊透露&#xff0c;它已经建立了“AWS生成人工智…

uniapp中axios封装和环境配置

axios版本 最好锁定版本&#xff0c;避免bug axios-miniprogram-adapter这个依赖主要是适配小程序网络请求的适配器&#xff0c;为了解决uniapp 适配axios请求&#xff0c;避免报adapter is not a function错误 cnpm i axios0.26.0 axios-miniprogram-adapter 配置adapter函…

leaflet 沿河流流向显示河流名字

1.效果图&#xff1a; 2.代码块 首先需要借助一个插件3mapslab-Leaflet.streetlabels&#xff08;这个插件也可用于显示街道名字用的&#xff09; 插件可在leaflet官网上下载及案例&#xff01; --- layout: default ---<div idmap style"width:100%;min-height:30…