Android15适配之edge-to-edge和16kb到底咋适配

前一篇写了废了好大的功夫才把编译环境弄好跑通,然后高高兴兴的提测了,没想到提测后的第二天,测试小姐姐就跑来大叫:"你看下为什么那么多页面顶部那么靠上了,都没法点返回键了,我要给你提Bug!!!",吓我一跳,心想着着我刚接手的项目,业务代码一行都没有动过,就只改了些gradle文件啊,怎么会出问题呢?马上解释"先别激动,我先看下啊,没改啥东西啊"结果人家把测试机放下了,人早就溜回去提Bug了,我把测试机拿来一看,果然标题栏一大半都被状态栏给挡住了,好几个页面都这样,奇怪了啊,随后一琢磨,怕是Android15的某个新特性在作妖吧,果然最终定位到了是edge-to-edge

edge-to-edge

对edge-to-edge一无所知的我只好先从了解它开始,只有知道它做了些什么事情,才能再慢慢去处理产生的兼容问题,上官网查询了下,发现这段介绍

说的还是蛮直白的,把点都说到了,就是个无边框设计,谷歌把这种页面全撑开了,同时把导航栏,状态栏之类的都弄成透明的,他们还把这个改动当成是一次体验上的优化,真是谢谢他们了,让我刚进公司就喜提个p0级的bug,搞不好也要被优化

增加视图边距

首先来看下问题,如下图所示,大致可以展现出实际现象

界面上顶部与底部各有一个按钮,他们都被系统的状态栏和导航栏给挡住了,如果你的应用已经升级到了targetSdk35并且运行在不低于Android15的设备上,如果不做处理的话,你的应用也会有这样的问题,如果想让被遮挡的区域回到可操作范围的话,就必须给界面留出一定的边距,官方给出的解决方案如下

为避免在手势模式或按钮模式下出现此类视觉重叠,您可以使用 getInsets(int) 和 WindowInsetsCompat.Type.systemBars()增加视图的边距

那么就来尝试下,在Activity中添加如下处理

root就是页面的根视图,给根视图设置边距,这个边距是系统状态栏的边距, WindowInsets.Type.systemBars()表示系统的所有状态栏,包括顶部状态栏以及底部导航栏,如果单纯只想设置状态栏的边距或者导航栏的边距,可以使用如下方式

现在我们的界面被遮挡的部分就出来了

处理挖孔屏

细心的人会发现WindowInsets.Type.systemBars()获取的值里面其实并不包含挖孔屏或者刘海屏

那么这是不是代表着我们就可以不用考虑这类设备了呢?怎么可能,就比如上述代码,我们如果将模拟器调成带刘海的,我们就会发现...

好像也没啥问题,但是尝试下转个屏呢

就会发现刘海区域已经将我们界面的一部分给遮挡住了,并没有预留出边距,所以为了将这种场景也考虑进去,我们的代码必须改一下

增加了WindowInsets.Type.displayCutout(),这样的话当设备是挖孔或者刘海屏的时候,我们的界面也会根据刘海或者挖孔所占的区域大小,留出相应边距

现在界面展示效果就好些了,bug改完了,打个包给测试验一下吧

隐藏或展示systemBar

包给到测试后,我继续去了解了下edge-to-edge,发现除了给系统栏增加边距之外,还可以隐藏系统栏,通过先获取WindowInsetsControllerCompat,然后调用hide函数就可以,至于想隐藏什么东西,就将参数传给hide函数就好

上述代码就是将状态栏,导航栏全部隐藏

如果只想要单独隐藏状态栏或者导航栏,只需要传入WindowInsets.Type.statusBars()或者 WindowInsets.Type.navigationBars()即可,同样的,如果想在隐藏后再展示的话,可以调用show函数,传参逻辑同hide一样

改变状态栏icon颜色

很多时候,页面都需要根据不同主题去切换状态栏icon的颜色,比如淡颜色主题的,icon需要呈现黑色,而一些深色主题的,icon需要显示成白色,而这种功能同样可以使用WindowInsetsControllerCompat来实现,isAppearanceLightStatusBars设置成true就是黑色icon,false就是白色icon

导航栏透明

开头我们在Android 15的介绍中有注意到,三按钮导航栏变成了半透明,而且从之前的展示效果中也注意到,底部三按钮导航栏的确不是全透明的

如果我们想把导航栏设置成全透明的,只需要一行代码即可

isNavigationBarContrastEnforced属性设置成false就可以获得一个全透明的导航栏

16kb内存页面

当我在适配完edge-to-edge的某个下午,运营小姐姐给我弹了个消息出来

  • 运营:听说你是新来的Android,在做我们产品的Android15适配是哇
  • 我:是的,有什么事吗?
  • 运营:没啥大事,你可能要多加一件事情,就是要做一下16kb内存页面对齐的事情,之前谷歌就要求做了,我们给推迟了
  • 我:16kb?那是啥?
  • 运营:你不是做Android的吗?这个都不知道?
  • 我:我是不知道呀,以前都没听过
  • 运营:自己查资料去吧

看起来像是个耐心不怎么好的运营,我只能自己去查了,去官网上看了下,发现一段对于16kb的介绍

从历史上看,Android 仅支持 4 KB 内存页面大小,这优化了系统内存性能,以适应 Android 设备通常拥有的平均总内存量。从 Android 15 开始,AOSP 支持配置为使用 16 KB 页面大小的设备(16 KB 设备)。如果您的应用直接或通过 SDK 间接使用任何 NDK 库,则需要重新构建应用,才能在这些 16 KB 设备上运行

是一项内存方面的优化,但是要做这件事之前,需要重新构建应用,好家伙这么麻烦的吗,可以不构建吗?然后下面就看到了另一句话

如果不重新编译,应用将无法在未来 Android 版本的 16 KB 设备上运行

还是谷歌狠啊,你不做直接没法在手机上运行,那么问题来了,如何知道自己的应用用到了NDK的库并且都是些什么库呢,我这里有两种方式

Analyze Apk

在Android Studio中的菜单栏里面选择Build->Analyze Apk,选择一个构建好的apk文件,或者直接generate apk构建一个apk文件,然后从右下角弹出来的框子中点击analyze选项

都会跳出来一个页面来展示出你的apk里面是否包含了不符合16kb的so文件,像这样

上图的意思就是这个apk不支持16kb的设备,并且指出了是项目中用到的mmkv这个库不支持

AnalyzeSoGradlePlugin

另一种方式是在项目中使用AnalyzeSoGradlePlugin这个插件,它可以帮你检测出哪些库不支持16kb,这个是项目地址,使用方式很简单,首先第一步在app/build.gradle文件中引入插件,版本使用最新版本就好

同步之后,在终端执行以下命令

最终就会生成一个本地的html文件,这个文件就生成在/app/build/reports/analyze-so/aggregate/analyze-so-report.html目录下

这个插件不但会告诉你哪个so库不支持,而且还会指出是哪个库引入的并且引入版本是多少

以上两种方式都可以用,方法一无代码侵入,但是信息量较少,方法二需要有代码侵入,但是可以知道so是否支持16kb,so库的来源以及版本,下面来看下如何对齐16kb

三方库对齐

通过上述使用AnalyzeSoGradlePlugin插件的方式我们可以很容易知道哪些三方库没有对齐,那么针对这些库我们能做的只能去他们官方文档中去寻找是否有版本已经做过对齐工作,下面是我列举的几个三方库以及他们做过16kb对齐的版本

标题 对齐的版本 官方地址
MMKV v1.3.14 mmkv更新日志
android-gif-drawable v1.2.29 android-gif-drawable更新日志
firebase-crashlytics-ndk v19.0.2 Firebase更新日志
Flutter v3.27.0 Flutter更新日志
AndroidPdfViewer v3.2.0-beta.1 Github地址
腾讯TRTC v12.2 官方更新日志
声网RTC v4.5.0 官方更新日志

其实单从三方库对齐这件事情上看,没什么难度,有网就行,但是其中运气成份占很大比重,有的幸运儿,可能只需要更新几个库就完成了整个16kb对齐工作,但是有的倒霉孩子可能会遇到有些库官方并没有给出对应的适配版本,咋办呢?

  1. 找其他三方库来代替:缺点就是这种方式多少得侵入一些业务代码
  2. 把源码搞下来自己编译:这种方式前提人家是开源的,得拿到人家完整的c层代码

两种方案都不是什么轻松的事情,如果前期不做好调研工作,那么很容易会让你的任务延期,等待而来的就是领导跟项目经理的盘问

本地库

除了三方库,项目中还会有一些需要自己去编译的库,这些库相对来讲是最保险的,因为主动权就在自己的手里,你只需要去重新编译下就好了,官网也给出了对应的编译方式

r28以及以上

ndk版本在28或以上的,应用会默认编译为16kb对齐

r27

ndk版本是27的需要添加如下配置

r26或更低版本

ndk版本是26或者更低版本,配置如下

但是光编译还不行,还要检查一下代码当中某些位置是否存在假定设备使用了特定的页面大小,如果有的话,运行时仍旧会报错,排查分为以下两个步骤

  1. 移除代码逻辑中引用 PAGE_SIZE 常量或假设设备内存页大小为 4 KB (4096) 的任何硬编码依赖项。请改用getpagesize()sysconf(_SC_PAGESIZE)
  2. 查找mmap()的用法以及需要页对齐实参的其他 API,并在必要时替换为替代方案

二方库

有些项目当中会依赖一些其他研发组或者部门所开发的库,这类库称为二方库,这种库明面上看似不需要自己动手最轻松,但是个人认为是最难对齐的,原因如下

  1. 有的库的年代比较久远,"maintainer是谁"这个问题会成为你一段时间内特别想知道的事情,并且还要期盼着不要出现最坏情况,maintainer没交接完就离职了
  2. 一定的沟通成本,what,why,how可能需要重复不止一遍,就算都拉到一个会议室,一个群里面,每个库的对齐方式也是不一样的,一次性也说不完
  3. 排期增加了太多不确定因素,有的人需要花三天,有的人需要花两天,有的人可以立即开始,有的人可能这周任务排满了得下周开始,有的人交付后没达到效果得回炉重造......一系列因素堆在一起,问题就来了,咱这任务得排到哪一天截止才能让产品领导项目经理对你点头微笑呢?

总结

折腾了俩特性,感觉edge-to-edge虽说展现出的现象挺吓人,一会这个看不见了一会那个点不了了,但是真不难适配,知道怎么整边衬区,怎么控制那些系统栏就可以了,16kb说心里话,是真的不管人死活,它本身出来就是一个优化体验的事情,做成可配置的就好了啊,为什么一定非要强制,不做还不让上架,app都不让人用,实在是不明白

相关推荐
前端码农一枚2 小时前
前端框架性能优化全攻略
前端
小魔女千千鱼2 小时前
运行小程序遇到的各种问题
前端·javascript·小程序
dly_blog3 小时前
ref 与 reactive 的本质区别(第3节)
前端·javascript·vue.js
前端不太难10 小时前
从 Navigation State 反推架构腐化
前端·架构·react
Java猿_11 小时前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端
前端程序猿之路11 小时前
Next.js 入门指南 - 从 Vue 角度的理解
前端·vue.js·语言模型·ai编程·入门·next.js·deepseek
大布布将军11 小时前
⚡️ 深入数据之海:SQL 基础与 ORM 的应用
前端·数据库·经验分享·sql·程序人生·面试·改行学it
川贝枇杷膏cbppg11 小时前
Redis 的 RDB 持久化
前端·redis·bootstrap
JIngJaneIL12 小时前
基于java+ vue农产投入线上管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot