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

一篇文章搞定《RecyclerView缓存复用机制》

------《RecyclerView缓存复用机制》

  • 前言
  • 零、为什么要缓存
  • 一、RecyclerView如何构建我们的列表视图
  • 二、缓存过程
  • 三、缓存结构
    • 1、mChangedScrap/mAttachedScrap
    • 2、mCachedViews
    • 3、mViewCacheExtension
    • 4、mRecyclerPool
  • 四、总结

前言

本篇文章,暂时不加入预加载进行讲解。先了解缓存机制再加入预加载会很快的进行理解。如果混淆在一起会觉得很乱。

零、为什么要缓存

RecyclerView是Android 5.0提出的新的UI控件,顾名思义,它会回收其列表项视图以供重用。
具体而言,当一个列表项被移出屏幕后,RecyclerView并不会销毁其视图,而是会缓存起来,以提供给新进入屏幕的列表项重用,这种重用可以:

  • 避免重复创建不必要的视图
  • 避免重复执行昂贵的findViewById
    从而达到的改善性能、提升应用响应能力、降低功耗的效果。而要了解其中的工作原理,我们还得回到RecyclerView是如何构建动态列表的这一步。

一、RecyclerView如何构建我们的列表视图

与RecyclerView构建动态列表相关联的几个重要类中,Adapter与ViewHolder负责配合使用,共同定义RecyclerView列表项数据的展示方式,其中:

  • ViewHolder是一个「包含列表项视图(itemView)的封装容器」,同时也是「RecyclerView缓存复用的主要对象」。
  • Adapter则提供了「数据<->视图」 的“绑定”关系,其包含以下几个关键方法:
    • onCreateViewHolder:负责创建并初始化ViewHolder及其关联的视图,但不会填充视图内容。
    • onBindViewHolder:负责提取适当的数据,填充ViewHolder的视图内容。
      然而,这2个方法并非每一个进入屏幕的列表项都会回调,相反,由于视图创建及findViewById执行等动作都主要集中在这2个方法,每次都要回调的话反而效率不佳。因此,我们应该通过对ViewHolder对象积极地缓存复用,来尽量减少对这2个方法的回调频次。
  1. 最优情况是——取得的缓存对象正好是原先的ViewHolder对象,这种情况下既不需要重新创建该对象,也不需要重新绑定数据,即拿即用。
  2. 次优情况是——取得的缓存对象虽然不是原先的ViewHolder对象,但由于二者的列表项类型(itemType)相同,其关联的视图可以复用,因此只需要重新绑定数据即可。
  3. 最后实在没办法了,才需要执行这2个方法的回调,即创建新的ViewHolder对象并绑定数据。 实际上,这也是RecyclerView从缓存中查找最佳匹配ViewHolder对象时所遵循的优先级顺序。而真正负责执行这项查找工作的,则是RecyclerView类中一个被称为「回收者」的内部类——Recycler。

二、缓存过程

那我们就从头去看到底是怎么去查找到我们的视图的(因为更直观去看所以先不加预加载的逻辑了)
还记得我们在创建我们的RecyclerView时设置了我们的layoutManager吗。其实我们的列表所有View的添加、构建都是我们设置的LayoutManager去启动的。这里只针对LinearLayoutManager这种布局管理器做一个讲解。
在这里插入图片描述
当然我们的RecyclerView支持LinearLayoutManager普通、GridLayoutManager网格、StaggeredGridLayoutManager瀑布流、FlexboxLayoutManager流式的、自定义的LayoutManager。
但是最后都会去使用我们的Recycle中的返回机制,也就是重要的方法:tryGetViewHolderForPositionByDeadline
在这里插入图片描述
这个方法会尝试通过从Recycler scrap、cache、RecycledViewPool查找或直接创建的形式来获取指定位置的ViewHolder。

public final class Recycler {.../*** Attempts to get the ViewHolder for the given position, either from the Recycler scrap,* cache, the RecycledViewPool, or creating it directly.* * 尝试通过从Recycler scrap缓存、RecycledViewPool查找或直接创建的形式来获取指定位置的ViewHolder。* ...*/@NullableViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {if (mState.isPreLayout()) {// 0 尝试从mChangedScrap中获取ViewHolder对象holder = getChangedScrapViewForPosition(position);...}if (holder == null) {// 1.1 尝试根据position从mAttachedScrap或mCachedViews中获取ViewHolder对象holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);...}if (holder == null) {...final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) {// 1.2 尝试根据id从mAttachedScrap或mCachedViews中获取ViewHolder对象holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);...}if (holder == null && mViewCacheExtension != null) {// 2 尝试从mViewCacheExtension中获取ViewHolder对象final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);...}}if (holder == null) { // fallback to pool// 3 尝试从mRecycledViewPool中获取ViewHolder对象holder = getRecycledViewPool().getRecycledView(type);...}if (holder == null) {// 4.1 回调createViewHolder方法创建ViewHolder对象及其关联的视图holder = mAdapter.createViewHolder(RecyclerView.this, type);...}}if (mState.isPreLayout() && holder.isBound()) {...} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {...// 4.1 回调bindViewHolder方法提取数据填充ViewHolder的视图内容bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);}...return holder;}...}    

结合RecyclerView类中的源码及注释可知,Recycler会依次从mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中尝试获取指定位置或ID的ViewHolder对象以供重用,如果全都获取不到则直接重新创建。这其中涉及的几层缓存结构分别是:

三、缓存结构

1、mChangedScrap/mAttachedScrap

mChangedScrap/mAttachedScrap主要用于「临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项」,其均以ArrayList的形式持有着每个列表项的ViewHolder对象,大小无明确限制,但一般来讲,其最大数就是屏幕内总的可见列表项数。

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;

但问题来了,既然是当前屏幕可见的列表项,为什么还需要缓存呢?又是什么时候列表项会被标记为「移除」或「重用」的呢?
这2个缓存结构实际上更多是为了避免出现像「局部刷新」这一类的操作,导致所有的列表项都需要重绘的情形。
区别在于,mChangedScrap主要的使用场景是:

  1. 开启了列表项动画(itemAnimator),并且列表项动画的canReuseUpdatedViewHolder(ViewHolder viewHolder)方法返回false的前提下;
  2. 调用了notifyItemChanged、notifyItemRangeChanged这一类方法,通知列表项数据发生变化;
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,viewHolder.getUnmodifiedPayloads());}public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,@NonNull List<Object> payloads) {return canReuseUpdatedViewHolder(viewHolder);}public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {return true;}

canReuseUpdatedViewHolder方法的返回值表示的不同含义如下:

  • true,表示可以重用原先的ViewHolder对象
  • false,表示应该创建该ViewHolder的副本,以便itemAnimator利用两者来实现动画效果(例如交叉淡入淡出效果)。

简单讲就是,mChangedScrap主要是为列表项数据发生变化时的动画效果服务的。
而mAttachedScrap应对的则是剩下的绝大部分场景,比如:

  • 像notifyItemMoved、notifyItemRemoved这种列表项发生移动,但列表项数据本身没有发生变化的场景。
  • 关闭了列表项动画,或者列表项动画的canReuseUpdatedViewHolder方法返回true,即允许重用原先的ViewHolder对象的场景。

下面以一个简单的notifyItemRemoved(int position)操作为例来演示:
notifyItemRemoved(int position)方法用于通知观察者,先前位于position的列表项已被移除, 其往后的列表项position都将往前移动1位。
为了简化问题、方便演示,我们的范例将会居于以下限制:

  • 列表项总个数没有铺满整个屏幕——意味着不会触发mCachedViews、mRecyclerPool等结构的缓存操作
  • 去除列表项动画——意味着调用notifyItemRemoved后RecyclerView只会重新布局子视图一次

recyclerView.itemAnimator = null

理想情况下,调用notifyItemRemoved(int position)方法后,应只有位于position的列表项会被移除,其他的列表项,无论是位于position之前或之后,都最多只会调整position值,而不应发生视图的重新创建或数据的重新绑定,即不应该回调onCreateViewHolder与onBindViewHolder这2个方法。
为此,我们就需要将当前屏幕内的可见列表项暂时从当前屏幕剥离,临时缓存到mAttachedScrap这个结构中去。
在这里插入图片描述
等到RecyclerView重新开始布局显示其子视图后,再遍历mAttachedScrap找到对应position的ViewHolder对象进行复用。
在这里插入图片描述

2、mCachedViews

mCachedViews主要用于「存放已被移出屏幕、但有可能很快重新进入屏幕的列表项」。其同样是以ArrayList的形式持有着每个列表项的ViewHolder对象,默认大小限制为2。

final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
int mViewCacheMax = DEFAULT_CACHE_SIZE;
static final int DEFAULT_CACHE_SIZE = 2;

比如像朋友圈这种按更新时间的先后顺序展示的Feed流,我们经常会在快速滑动中确定是否有自己感兴趣的内容,当意识到刚才滑走的内容可能比较有趣时,我们往往就会将上一条内容重新滑回来查看。
这种场景下我们追求的自然是上一条内容展示的实时性与完整性,而不应让用户产生“才滑走那么一会儿又要重新加载”的抱怨,也即同样不应发生视图的重新创建或数据的重新绑定。
我们用几张流程示意图来演示这种情况:
同样为了简化问题、方便描述,我们的范例将会居于以下限制:

  • 关闭预拉取——意味着之后向上滑动时,都不会再预拉取「待进入屏幕区域」的一个列表项放入mCachedView了

recyclerView.layoutManager?.isItemPrefetchEnabled = false

  • 只存在一种类型的列表项,即所有列表项的itemType相同,默认都为0。
    我们将图中的列表项分成了3块区域,分别是被滑出屏幕之外的区域、屏幕内的可见区域、随着滑动手势待进入屏幕的区域。
    在这里插入图片描述

1、当position=0的列表项随着向上滑动的手势被移出屏幕后,由于mCachedViews初始容量为0,因此可直接放入;
在这里插入图片描述
2、当position=1的列表项同样被移出屏幕后,由于未达到mCachedViews的默认容量大小限制,因此也可继续放入;
在这里插入图片描述
3、此时改为向下滑动,position=1的列表项重新进入屏幕,Recycler就会依次从mAttachedScrap、mCachedViews查找可重用于此位置的ViewHolder对象;
4、mAttachedScrap不是应对这种情况的,自然找不到。而mCachedViews会遍历自身持有的ViewHolder对象,对比ViewHolder对象的position值与待复用位置的position值是否一致,是的话就会将ViewHolder对象从mCachedViews中移除并返回;
5、此处拿到的ViewHolder对象即可直接复用,即符合前面所述的「最优情况」。
在这里插入图片描述
6、另外,随着position=1的列表项重新进入屏幕,position=7的列表项也会被移出屏幕,该位置的列表项同样会进入mCachedViews,即RecyclerView是双向缓存的。
在这里插入图片描述

3、mViewCacheExtension

mViewCacheExtension主要用于提供额外的、可由开发人员自由控制的缓存层级,属于非常规使用的情况,因此这里暂不展开讲。

4、mRecyclerPool

mRecyclerPool主要用于「按不同的itemType分别存放超出mCachedViews限制的、被移出屏幕的列表项」,其会先以SparseArray区分不同的itemType,然后每种itemType对应的值又以ArrayList的形式持有着每个列表项的ViewHolder对象,每种itemType的ArrayList大小限制默认为5。

  public static class RecycledViewPool {private static final int DEFAULT_MAX_SCRAP = 5;static class ScrapData {final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();int mMaxScrap = DEFAULT_MAX_SCRAP;long mCreateRunningAverageNs = 0;long mBindRunningAverageNs = 0;}SparseArray<ScrapData> mScrap = new SparseArray<>();...}

由于mCachedViews默认的大小限制仅为2,因此,当滑出屏幕的列表项超过2个后,就会按照先进先出的顺序,依次将ViewHolder对象从mCachedViews移出,并按itemType放入RecycledViewPool中的不同ArrayList。
这种缓存结构主要考虑的是随着被滑出屏幕列表项的增多,以及被滑出距离的越来越远,重新进入屏幕内的可能性也随之降低。于是Recycler就在时间与空间上做了一个权衡,允许相同itemType的ViewHolder被提取复用,只需要重新绑定数据即可。
这样一来,既可以避免无限增长的ViewHolder对象缓存挤占了原本就紧张的内存空间,又可以减少回调相比较之下执行代价更加昂贵的onCreateViewHolder方法。
同样我们用几张流程示意图来演示这种情况,这些示意图将在前面的mCachedViews示意图基础上继续操作:
1、假设目前存在于mCachedViews中的仍是position=0及position=1这两个列表项。
2、当我们继续向上滑动时,position=2的列表项会尝试进入mCachedViews,由于超出了mCachedViews的容量限制,position=0的列表项会从mCachedViews中被移出,并放入RecycledViewPool中itemType为0的ArrayList,即图中的情况①;
在这里插入图片描述
3、同时,底部的一个新的列表项也将随着滑动手势进入到屏幕内,但由于此时mAttachedScrap、mCachedViews、mRecyclerPool均没有合适的ViewHolder对象可以提供给其复用,因此该列表项只能执行onCreateViewHolder与onBindViewHolder这2个方法的回调,即图中的情况②;
4、等到position=2的列表项被完全移出了屏幕后,也就顺利进入了mCachedViews中。
在这里插入图片描述
5、我们继续保持向上滑动的手势,此时,由于下一个待进入屏幕的列表项与position=0的列表项的itemType相同,因此我们可以在走到从mRecyclerPool查找合适的ViewHolder对象这一步时,根据itemType找到对应的ArrayList,再取出其中的1个ViewHolder对象进行复用,即图中的情况①。
6、由于itemType类型一致,其关联的视图可以复用,因此只需要重新绑定数据即可,即符合前面所述的「次优情况」。
在这里插入图片描述
7、②③ 情况与前面的一致,此处不再赘述。

四、总结

RecyclerView缓存复用机制
对象ViewHolder(包含列表项视图(itemView)的封装容器)
目的减少对onCreateViewHolder、onBindViewHolder这2个方法的回调
好处1.避免重复创建不必要的视图 2.避免重复执行昂贵的findViewById
效果改善性能、提升应用响应能力、降低功耗
核心类Recycler、RecyclerViewPool
缓存结构mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool
缓存结构容器类型容量限制缓存用途是否回调createView是否回调bindView
mChangedScrap/mAttachedScrapArrayList无,一般为屏幕内总的可见列表项数存放已被移出屏幕、但有可能很快重新进入屏幕的列表项
mCachedViewsArrayList默认为2临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项
mViewCacheExtension开发者自己定义提供额外的可由开发人员自由控制的缓存层级
mRecyclerPoolSparseArray每种itemType默认为5按不同的itemType分别存放超出mCachedViews限制的、被移出屏幕的列表项

相关文章:

一篇文章搞定《RecyclerView缓存复用机制》

------《RecyclerView缓存复用机制》 前言零、为什么要缓存一、RecyclerView如何构建我们的列表视图二、缓存过程三、缓存结构1、mChangedScrap/mAttachedScrap2、mCachedViews3、mViewCacheExtension4、mRecyclerPool 四、总结 前言 本篇文章&#xff0c;暂时不加入预加载进行…...

Elasticsearch概述

1.Elasticsearch干啥的&#xff1f; Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;用于实时搜索、分析和存储大规模数据。它可以帮助用户在海量数据中快速进行全文搜索、聚合分析、地理空间分析等操作&#xff0c;并支持水平扩展以应对高并发访问需求。 Elasti…...

停车场收费系统

1.系统的开发工具 1.1 AppServe集成应用 Mysql&#xff1a;MySQL 是一款安全、跨平台、高效的&#xff0c;并与 PHP、Java 等主流编程语言紧密结合的数据库系统。该数据库系统是由瑞典的 MySQL AB 公司开发、发布并支持&#xff0c;由 MySQL 的初始开发人员 David Axmark 和 Mi…...

nodejs+vue+elementui学生毕业生离校系统

学生毕业离校系统的开发过程中。该学生毕业离校系统包括管理员、学生和教师。其主要功能包括管理员&#xff1a;首页、个人中心、学生管理、教师管理、离校信息管理、费用结算管理、论文审核管理、管理员管理、留言板管理、系统管理等&#xff0c;前台首页&#xff1b;首页、离…...

儿童用灯哪个品牌好?推荐专业的儿童护眼台灯

一款好的儿童台灯&#xff0c;主要是从5个方面决定&#xff0c;照度及均匀度&#xff0c;蓝光&#xff0c;色温&#xff0c;显指&#xff0c;频闪 ① 照度及均匀度最高是国AA级&#xff0c;其次就是国A级 ② 蓝光一定要选择RG0无危险级&#xff0c;蓝光能量最强&#xff0c;…...

探究Android插件化开发的新思路——Shadow插件化框架

Shadow插件化框架是什么&#xff1f; Shadow是一种Android App的插件化框架&#xff0c;它利用类似于ClassLoader的机制来实现应用程序中的模块化&#xff0c;并让这些模块可以在运行时灵活地进行加载和卸载。Shadow框架主张将一个大型的Android App拆分成多个小模块&#xff…...

SimpleDateFormat和DateTimeFormatter的区别及使用详解

目录 1.简介2.区别3.SimpleDateFormat3.1 字符串转日期3.2 日期转字符串 4.DateTimeFormatter4.1 字符串转日期4.2 日期转字符串 扩展 1.简介 DateTimeFormatter 和 SimpleDateFormat 都是用于格式化日期和时间的类&#xff0c;但是它们有一些区别。 SimpleDateFormat 是 Jav…...

边缘人工智能——nanodet模型实践指引,从标注数据集到实现部署文件

内容概述 首先获得一个合适的nanodet模型版本&#xff0c;配置nanodet适用的环境&#xff0c;然后对网上公开的生数据集进行重新标注&#xff0c;配置nanodet并进行训练&#xff0c;.pth到.onnx的模型转化及简化&#xff0c;编写推理文件。 文章着重于实践方向指引&#xff0c;…...

SASS的用法指南

一、什么是SASS SASS是一种CSS的开发工具&#xff0c;提供了许多便利的写法&#xff0c;大大节省了设计者的时间&#xff0c;使得CSS的开发&#xff0c;变得简单和可维护。 本文总结了SASS的主要用法。我的目标是&#xff0c;有了这篇文章&#xff0c;日常的一般使用就不需要去…...

MCSM面板一键搭建我的世界服务器-外网远程联机【内网穿透】

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 转载自远程穿透文章&…...

( 数组和矩阵) 565. 数组嵌套 ——【Leetcode每日一题】

❓565. 数组嵌套 难度&#xff1a;中等 索引从 0 开始长度为N的数组 A&#xff0c;包含 0 到 N - 1 的所有整数。找到最大的集合 S并返回其大小&#xff0c;其中 S[i] {A[i], A[A[i]], A[A[A[i]]], ... } 且遵守以下的规则。 假设选择索引为 i 的元素 A[i] 为 S 的第一个元…...

linux内核网络子系统初探---概述

linux内核网络子系统初探—概述 一、网络模型 简单介绍 学习网络时&#xff0c;必定能在各种教材资料里见到以下三种网络模型&#xff1a; 三种模型间的差异&#xff1a; OSI七层模型是理论上的网络模型&#xff0c;从功能方面分成了相对独立的7个层次&#xff0c;由于太复…...

java版工程项目管理系统源代码-功能清单 图文解析

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…...

【chapter30】【PyTorch】[动量与学习率衰减】

前言&#xff1a; SGD的不足&#xff1a; ①呈“之”字型&#xff0c;迂回前进&#xff0c;损失函数值在一些维度的改变得快&#xff08;更新速度快&#xff09;&#xff0c;在一些维度改变得慢&#xff08;速度慢&#xff09;- 在高维空间更加普遍 ②容易陷入局部极小值和鞍点…...

【键入网址到网页显示】

文章目录 HTTPDNS五层协议TCPIPMAC网卡&#xff08;物理层&#xff09;交换机路由器 HTTP 对 URL 进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#xff0c;接下来就是根据这些信息来生成 HTTP 请求消息了。 http://www.server.com/dir1/file1.html http:访问数…...

Nacos配置中心、配置热更新、及配置共享的记录

Nacos除了提供了注册中心的功能,同样也提供了配置中心的功能,用于管理一些叫常改动的配置 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#xff0c;可以集中…...

MATLAB | 绘图复刻(八) | 堆叠柱状图+哑铃图

本次复刻的是Nature Communications中Friedman, S.T., Muoz, M.M. A latitudinal gradient of deep-sea invasions for marine fishes. Nat Commun 14, 773 (2023). https://doi.org/10.1038/s41467-023-36501-4的Fig1图像&#xff1a; 复刻效果&#xff1a; 文章可在如下网站下…...

Scala之集合(2)

目录 集合基本函数&#xff1a; &#xff08;1&#xff09;获取集合长度 &#xff08;2&#xff09;获取集合大小 &#xff08;3&#xff09;循环遍历 &#xff08;4&#xff09;迭代器 &#xff08;5&#xff09;生成字符串 &#xff08;6&#xff09;是否包含 衍生集合…...

【图像分割】视觉大模型SEEM(Segment Everything Everywhere All at Once)原理解读

文章目录 摘要&#xff08;效果&#xff09;二、前言三、相关工作四、method4.1 多用途4.2 组合性4.3 交互式。4.4 语义感知 五、实验 论文地址&#xff1a;https://arxiv.org/abs/2304.06718 测试代码&#xff1a;https://github.com/UX-Decoder/Segment-Everything-Everywher…...

Linux: command: ibstat; infiniband

文章目录 如何在Linux上安装infiniband相关的软件。ibstat相关资料 如何在Linux上安装infiniband相关的软件。 https://access.redhat.com/solutions/301643 https://docs.oracle.com/cd/E19436-01/820-3522-10/ch3-linux.html yum groupinstall “Infiniband Support” Pack…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…...

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法

用神经网络读懂你的“心情”:揭秘情绪识别系统背后的AI魔法 大家好,我是Echo_Wish。最近刷短视频、看直播,有没有发现,越来越多的应用都开始“懂你”了——它们能感知你的情绪,推荐更合适的内容,甚至帮客服识别用户情绪,提升服务体验。这背后,神经网络在悄悄发力,撑起…...