SP 导致 ANR 原因分析
经常遇到两种类型的SharedPreference 问题。以下是这两类ANR问题的原因及优化解决方案。
问题1:sp文件创建后,会使用一个单独的线程来加载并解析对应的sp文件。但是当UI线程尝试访问sp中的内容时,如果sp文件还没有完全加载并解析到内存中,那么UI线程将会被阻塞,直到sp文件完全加载到内存中。具体ANR线程栈如下:
主要原因是SP文件还没有加载或解析到内存中,此时无法直接使用sp提供的接口。创建sp时,会同时启动一个线程来加载对应的sp文件,并执行startLoadFromDisk();
当startLoadFromDisk()时,将sp标记为不可用。无论稍后尝试读取还是写入数据,读写线程都会被阻塞,直到所有sp 文件加载并解析完毕。
当线程在读或者写的时候,就会进入awaitLoadedLocked()逻辑。上图中,mLoaded为false,即sp文件还没有被加载解析到内存中。此时读写线程会直接阻塞到mLock,直到执行loadFromDisk()方法。完全的。
sp文件完全加载并解析到内存中,直接唤醒所有等待当前sp的读写线程。
问题2:Google系统中为了保证数据的跨进程完整性,早期的应用程序可以使用sp进行跨进程通信。当组件被销毁或者其他生命周期被销毁时,为了保证当前的写入任务必须在当前组件的生命周期内完成。写。此时主线程会在组件销毁或者组件挂起的生命周期中等待sp完全写入对应的文件。如下图所示,UI线程阻塞在QueuedWork.waitToFinish()处,然后根据源码,应用从头到尾梳理出写文件的整体流程,找出问题的根源。
具体需要等待文件写入的消息在ActiveThread的H中。具体消息类型如下:
publicstaticfinalintSERVICE_ARGS=115; publicstaticfinalintSTOP_SERVICE=116; publicstaticfinalintPAUSE_ACTIVITY=101; publicstaticfinalintSTOP_ACTIVITY_SHOW=103; publicstaticfinalintSLEEPING=137;由于Google官方设计最初是作为一种轻量级的数据存储方式来设计的,所以这种等待行为不会产生任何问题,但在实际使用中由于sp的过度使用,这种等待时间会不受控制地延长,直到最终出现ANR。这个问题在业务量大的应用中变得更加明显。具体问题栈如下,均为系统栈。接下来我们就从waitToFinish入手,分析一下这个ANR的根本原因。具体ANR堆栈如下:
早期的sp接口只有commit接口,同步写文件。这个接口直接影响了开发者的使用,因此Google官方提供了异步apply接口。因为开发者认为这个异步才是真正的异步,所以sp被大规模使用。 appy接口是apply的实现方式,导致业务量较大的APP深受此apply设计缺陷带来的ANR问题的影响。
apply接口整体详细设计思路如下(基于Android 8.0及以下分析):
总体思路简单概括如下:
1. sp.apply(),写入内存并同步获取需要写入文件的数据集MemoryCommitResult:
2、将MemoryCommitResult封装成Runnable,扔到子线程queued-work-looper中;
3、将MemoryCommitResult中mapToWriteToDisk对应的key-value写入子线程中的文件中;
4、文件写入完成后,会执行MemoryCommitResult的setDiskWriteResult方法,出现关键步骤writeToDiskLatch.countDown();
5、如下,当主线执行QueuedWork.waitToFinish()时;
6、主线程在做什么?这时候就得从QueuedWork.add(Runnable finisher)开始了。具体的Runnable如下所示。这里什么也没做。它直接等待mcr.writingToDiskLatch.await()。这里大家应该有一个印象,就是第4步写文件后neutron线程直接释放的锁。
结论:虽然整体API的流程分析极其复杂,但是一层层封装了一个Runnable,从这个线程抛出到那个线程。子线程写完文件后,锁会被释放,主线程会执行到某些地方。需要等待子线程写完文件,但是整体思路比较简单。此问题的根本原因是太多待处理的应用操作未写入文件。主线程在执行指定消息时会等待。如果等待时间过长,就会出现ANR。
虽然谷歌官方在Android 8.0及以后的版本中优化了sp的写入逻辑,希望是在上面的第6步中,UI线程不会傻傻地等待,而是会帮助子线程一起写入,但因为是保守辅助,没有太多办法很好地解决这个问题。
解决方案
问题2:至于Google为什么要这样设计,我提出了自己的几个猜想:
Google希望数据能尽快写入文件,但等待是没有意义的。主线程直接等待不会提高写入效率;它希望sp实时写入文件,方便跨进程实时处理。在访问sp中的文件时,这种异步写入方式本身无法保证实时性;可能是组件切换状态时。此时,如果进程中没有任何组件,则进程的优先级可能会降低,现有进程在系统资源紧张时被系统杀死的概率极低,几乎可以忽略不计。最有可能的可能是Google官方希望从commit无缝切换到apply,并且仍然模拟原来的commit行为。原来每次一次写入文件改为多次提交行为。最后的apply是在主线程中,等待所有的写入行为一次性全部写入。通过上面的假设,发现主线程等待子线程写入根本没有任何意义。因此,我希望可以通过一些必要的手段来跳过这种无用的等待行为。在研究了所有SharedPreference 相关逻辑后,我发现了以下起点。以下是8.0以下版本的优化策略。 8.0及以上版本处理类似:
如果需要主线程在waitToFinish期间直接跳转,让toTinish.run()完成执行,显然是不可能的。如果可以让sPendingWorkFinishers.poll()返回null,那么这里的等待行为就会直接跳过。 sPendingWorkFinishers 是一个ConcurrentLinkedQueue 集合,你可以直接动态代理这个集合并重写poll 方法,使其始终返回null。此时,UI永远不会等待子线程写完文件。事实证明,这种方法简单有效。
为了解决这种写等待ANR问题,还有一种全局替换写方式的方法,即通过instrumentation替换所有API实现,并使用其他存储方式。这种方式修复成本和风险较高,但后期会修复。存储方式可随意更换,使用更加灵活。
方案收益
经过字节系统多个产品验证,方案稳定有效,消除了对应堆栈带来的ANR问题,ANR效益明显,对应界面跳转等场景的流畅度得到提升得到显着改善。
展望
Google 添加了新的Jetpack 成员DataStore,主要用于替换SharedPreferences。 DataStore应该是开发者期待已久的一个库。 DataStore是基于新的数据存储解决方案Flow实现的。网上有很多参考资料有详细的介绍。
优化实践更多参考:
今日头条ANR优化实践系列-设计原理及影响因素
今日头条ANR优化实践系列——监控工具与分析思路
今日头条ANR优化实践系列——Barrier导致主线程死掉
标题:ANR优化实践系列告别SharedPreference和等待
链接:https://www.gbbxw.com/news/rj/20294.html
版权:文章转载自网络,如有侵权,请联系删除!
用户评论
ANR 优化后,游戏启动速度真是快了不少,再也不需要慢慢等待SharedPreference加载了呢。
有8位网友表示赞同!
<p>告别SharedPreference等待,游戏运行流畅了很多,玩家体验提升了一个档次。</p>
有14位网友表示赞同!
游戏的反应速度好像变了个样子,不再卡顿,感觉就像换了新设备一般爽快。
有11位网友表示赞同!
<p>优化ANR之后,再也没有漫长的预加载过程了,游戏直接进入正题。</p>
有9位网友表示赞同!
共享偏好问题的解决,让游戏里的各种设置响应更快速,操作起来很顺手。
有15位网友表示赞同!
不再因为SharedPreference慢而影响游戏乐趣,这次的更新简直是神来之笔。
有9位网友表示赞同!
<p>优化后的ANR系列游戏,流畅度和之前的相比提升明显,玩家纷纷表示好评。</p>
有14位网友表示赞同!
告别了长时间等待,现在一打开就是玩,这感觉简直太好了!
有10位网友表示赞同!
<p>这个改进简直拯救了我的耐心,再也不会被 SharedPreference 等待拖慢游戏节奏了。</p>
有7位网友表示赞同!
自从 ANR 优化之后,我发现游戏中的各种操作更加流畅自然,体验提升了不少。
有9位网友表示赞同!
<p>(ANR) 优化后,再也不用等待 SharedPreference 加载,直接享受更沉浸的游戏过程。</p>
有13位网友表示赞同!
更新后的效果太明显了,玩游戏的时候完全感觉不到明显的等待时间,爽快!
有19位网友表示赞同!
<p>这次优化让 ANR 游戏摆脱了 SharedPreference 的拖累,整体体验顺畅无比。</p>
有11位网友表示赞同!
终于告别了漫长的启动和加载过程,现在可以专注游戏了,真的挺感激这样的改进。
有16位网友表示赞同!
(ANR) 优化后直接提升了我的游戏体验,流畅度提高了不止一点半点。
有8位网友表示赞同!
<p>共享偏好问题的改善让整个游戏运行起来更加高效平滑,太棒了!</p>
有14位网友表示赞同!
告别等待的痛苦,现在 ANR 的系列游戏简直是快到飞起!
有19位网友表示赞同!
(ANR) 优化系列里最直观的变化就是速度提升,感觉像是开启了全新的模式。
有17位网友表示赞同!
<p>改善了SharedPreference加载问题后,这个游戏直接从等死状态跃升到了流畅体验,点赞!</p>
有19位网友表示赞同!
不再受 SharedPreference 缓冲时间影响的 ANR 系列游戏太让我满意了!
有13位网友表示赞同!
(ANR) 优化处理之后,游戏的整体表现有了质的飞跃,值得所有玩家试试看!
有18位网友表示赞同!