本系列为 Android 技术职场题材虚构小说,所有登场人物、公司名称、组织架构及相关情节均为创作所需虚构而来,若有雷同,纯属偶然。书中涉及的技术知识经专业梳理,仅供参考。
十一)Bitmap优化套路深 多级缓存定乾坤
新的一周,林卓早早来到公司,心情格外舒畅。阳光透过玻璃幕墙洒在大厅地砖上,折射出暖融融的光斑,难得避开早高峰拥堵的他,脚步都带着轻快。
他径直走向公司楼下的咖啡店,点了一杯热美式,指尖握着温热的杯壁,慢悠悠走回工位。此时办公室里还没几个人,键盘敲击声稀疏,只有中央空调的轻微声响,这份难得的闲暇在紧张的项目周期里显得格外珍贵。
林卓拉开椅子坐下,抿了一口咖啡,正准备梳理本周的工作安排,身后就传来了熟悉的脚步声。
“来得挺早啊,小林。”老杨背着背包走过来,随手把背包放在邻座空位上,语气透着几分轻松。他瞥了眼林卓桌上的热美式,笑着拉过一把椅子坐下,“还挺会享受,这会儿办公室都没几个人,倒让你占了份清净。”
林卓笑着点头回应:“嘿嘿,这周还没有什么新的安排嘛!这会儿能偷个闲,对了老杨,你们的大模型工作主要是干啥呀?”
老杨笑着摆了摆手,语气随意又带着点无奈:“还能啥,瞎忙活呗。车机端为了减少云端压力,想跑一个离线大模型,可车机算力有限,得把模型压缩到合适大小,还得保住识别准确率,磨人的很。”
他瞟了一眼林卓的工位,目光落在林卓的电脑屏幕上,话锋一转,“趁这会儿不忙,我倒想问问你个技术问题——如果给你一张超大分辨率的图片,比如 4000×3000 的,要在车机界面上显示,还得保证不卡顿、不崩内存,你怎么处理?”
林卓握着咖啡杯的手一顿,瞬间收起了放松的心思。他沉吟片刻,缓缓说道:“超大图直接加载肯定不行,会占用大量内存,大概率触发内存溢出错误。应该先对图片进行压缩处理,再考虑显示和优化。”
“没错,核心就是 Bitmap 的高效处理。”老杨点点头,语气里带着几分引导,“具体怎么压缩?怎么保证压缩后显示清晰,还不影响性能?你好好琢磨下。”说完便起身回了工位,椅子拖动的轻响过后,只留下林卓一人坐在原地。
林卓脸上还带着几分茫然,握着咖啡杯愣了两秒,心里暗自纳闷:好端端的闲聊,怎么突然问这个问题?
疑惑归疑惑,他没再多想,指尖无意识地敲击着桌面,脑子里已经开始飞速梳理超大图显示的核心逻辑。
正琢磨着,办公区入口传来脚步声,张磊拿着平板快步走来,目光扫过工位喊道:“林卓,到会议室开个短会,同步下新闻模块的新需求。”林卓连忙收起思绪,锁屏电脑,跟着张磊走向会议室。
会议室里只有三四个人,张磊开门见山,直接点开需求文档:“今天同步个紧急需求,给新闻模块加个《早间要闻》功能,核心是展示前一日的焦点新闻,内容由 AI 总结生成,数量不固定,多则十到二十条,少则五六条。”
他顿了顿,补充道:“因为是 AI 生成内容,特殊情况会比较多——比如图文排版错乱、图片尺寸不规则、文字长度差异大,都要做好兼容。功能优先级很高,周五正式提测,你们各司其职,林卓,你负责新闻的图文显示与适配开发,测试那边同步跟进。”
会议只开了十分钟就结束,林卓走出会议室,心里嘀咕着早上老杨问的问题,隐约觉得或许会用到,但也没深想,转头就全身心投入到新需求的梳理中。
林卓顺着需求细节仔细梳理,很快摸清了核心要点:AI 生成的内容没有固定模板,新闻数量、图文排版、图片尺寸全是变量,界面绘制的关键是兼容性和灵活性,必须先把 UI 显示的基础打牢。
他打开需求文档和 UI,在办公区的白板上,快速勾勒《早间要闻》的界面框架,明确了两大核心模块——新闻摘要列表和专题大图封面区,随后便聚焦 UI 绘制的解决方案。
他指尖轻点白板,顺便用记号笔勾勒几处重点,逐一拆解 UI 难题:新闻摘要卡片要统一样式,还得适配长短不一的 AI 生成文字,背景样式必须灵活;列表分隔线要简洁,还不能因新闻数量多变增加布局负担;专题区的封面大图,感觉没什么难度,直接加载显示就行,顶多有几个文字,这个好办。
思索片刻,他心里有了答案——用 Drawable 家族的不同子类针对性解决,刚好能覆盖这些 UI 适配场景。
时间来到下午,林卓全身心投入 UI 的开发。
他清楚 Drawable 作为可绘制内容的抽象基类,每个子类都有专属适配场景,正好能应对 AI 内容的不确定性,于是有条不紊地为三个核心 UI 场景敲定技术方案。
第一个是新闻摘要卡片,卡片大小不固定(要适配不同长度的 AI 新闻内容),背景还需要带阴影效果,用 .9 最合适。这种特殊格式能手动标记可拉伸区域,同时保留阴影细节,无论卡片被拉伸到多大,阴影边缘都不会模糊变形,完美适配 AI 内容的不确定性。林卓用工具处理好带阴影的 .9 图,直接导入项目就能复用,省去了反复调整布局的麻烦。
第二个是新闻附带的文字标签,标签没有阴影,但要做圆角处理,而且 AI 生成的新闻分类不同,标签颜色也得跟着变,总跟 UI 对接调色太耗时。林卓选用 ShapeDrawable 解决,通过 XML 直接定义圆角大小,颜色则预留代码接口动态设置,无需修改资源文件,既能满足样式要求,又大幅提升了开发效率,还能灵活应对标签颜色的多变需求。
至于专题区的封面大图,林卓反倒觉得没什么复杂的——直接把 AI 返回的图片下载到本地,再加载显示就行,哈哈。
不知不觉两天时间过去,林卓全身心扑在 UI 开发上,终于把 Drawable 适配逻辑全部落地,还完成了整体布局的整合调试,昨儿提测这部分的内容。
此时的他,正等着测试——小安的测试报告,看看有没有新的问题。
没过多久,小安抱着测试机走了过来,脸上带着几分纠结:“林卓,UI 走查和核心功能都过了,样式、适配全符合设计稿。”
她顿了顿:“但有两个明显问题,我已经提交 Bug 和日志了,你赶紧看看。问题集中在专题封面图,一是加载特别慢,进入专题区要等一两秒才显示;二是多打开几个车机软件再切回来,滚动几下,APP 直接崩溃了,日志里好像提示内存溢出。”
林卓心里一紧,打开 Bug 管理平台查看日志。他快速扫完日志内容,眉头瞬间皱了起来——崩溃原因果然是内存溢出,加载慢也和图片资源有关。他亲自复现了一遍问题,果然多打开几个新闻专题封面,直接闪退,封面图加载时也明显有卡顿感。
“不愧是你,小安,日志我先分析下。”林卓支走小安,心里泛起疑惑:怎么会内存溢出?
他反复核对代码,又对照日志里的内存占用记录,心里猜测大概率是图片本身过大导致的。
为了验证猜想,他找到代码中加载图片的逻辑,提取出 AI 返回的图片 URL,逐一复制到浏览器查看。
这不看不要紧,一看吓了一跳——这些图片分辨率参差不齐,小的才 480p,大的竟然直接是 4K 分辨率,体积远超预期。
林卓对着屏幕低声暗骂一句:“服务端这帮人,也不做下图片处理,净给前端填坑!” 此刻他才彻底明白,问题根源就在这无节制的图片分辨率上。
林卓突然恍然大悟,仿佛一道闪电劈进脑海!周一早上老杨问的问题,原来坑在这里!
明确问题根源后,林卓立刻动起手来。他先翻出老杨之前分享过的《Android 核心知识点》文档,找到内存优化相关的章节——那是老杨整理多年项目经验的精华,里面刚好有超大图处理的相关思路。
接着又在技术社区搜了不少车机端图片加载的实战案例,结合自己对需求的理解,慢慢定制出一套针对性的解决思路。
首先是解决“只读尺寸不加载”的核心问题。林卓利用 BitmapFactory.Options 的 inJustDecodeBounds 属性,将其设为 true 后,解码图片时只会返回宽高信息,不会真正分配内存。这样就能在不占用资源的前提下,摸清 AI 生成图片的实际尺寸,避免盲目加载导致内存压力。
摸清尺寸后,下一步计算采样率。采样率直接决定图片缩放比例,比如采样率为 2 时,图片宽高各缩为原来的二分之一,内存占用可降至四分之一。
林卓参照标准算法编写逻辑,确保缩放后的图片尺寸不小于车机控件显示尺寸,既控制内存占用,又避免图片模糊,还开启了抖动优化提升显示质感。
他用一张 4K 的测试图模拟 AI 超大图场景,对应车机专题区 720P 的显示尺寸,优化后内存占用从近 20MB 降至 1.6MB 左右,内存占用有明显优化。
但林卓测试时发现,第一次进入页面仍有加载延迟,快速滑动列表时图片会闪烁,这里就有两个原因了:
- 大图片下载慢
- 重新压缩图片,也需要一番时间
要解决这个问题,必须搭建缓存策略。林卓采用“内存缓存 + 磁盘缓存”的组合方案。
内存缓存用 LruCache 实现,分配大概 10mb 作为容量,自动回收最少使用的图片对象,供封面图快速访问;磁盘缓存用 DiskLruCache,将解码后的图片存储在本地,设置 30MB 缓存上限,避免下次进入页面重复解码。
为了不阻塞主线程,同时更加稳定的加载图片,他用 WorkManager 创建后台任务,在子线程中处理图片解码和缓存逻辑,还封装了统一的加载方法:优先从内存缓存读取,内存无则查磁盘缓存,两者都无再启动后台任务解码,解码完成后同步存入两级缓存。同时他还添加了淡入动画,让图片从透明过渡到不透明,掩盖加载间隙,提升视觉体验。
缓存策略上线后,林卓喊来小安复测。这次页面加载迅速,快速滑动无闪烁,小安逐条核对后露出笑容:“丝滑!“
”不过还有个小问题,专题大图点击放大时动画生硬,感觉是跳过去的,而不是过渡过去的。这个倒不是你的问题,我直接提给产品了”
林卓闻言心里一紧,暗自腹诽:“我的小祖宗!这都要上线了,别再给我加需求了!”
嘴上却只能笑着应下:“行,你先提给产品,我看看能不能顺手优化了,尽量不耽误上线进度。”
虽说心里吐槽,但他也清楚动画体验会影响产品质感,转头就收起杂念,投入到动画知识点的补充学习中。
他找到 Android 动画相关的文档——这内容可真不少!
不过现在的他不可同日而语,历经好几次的产品需求实现和优化,现在的他早已驾轻就熟了。
很快他就梳理完了 AnimatorSet 的使用方法,重点琢磨缩放与平移动画的时序衔接、插值器参数调整。
加之之前 Bitmap 优化和 Drawable 适配的基础,动画知识点上手很快,短短一个下午就吃透了核心逻辑,还针对性调整了动画时长与过渡曲线,让大图放大效果更丝滑。
第二天一早,产品拿着优化建议找到他,明确提出要优化专题大图的放大过渡动画,强调“生硬跳转影响用户体验”,要求上线前搞定。
林卓心里虽有“临时加需求”的无奈,但也清楚体验优先级,立刻收起杂念投入开发。
嘿嘿,幸好昨儿学习的差不多了,把昨儿写的大部分示例代码,移植过来,针对 UI 优化一下就行了!
上午优化完成后,林卓第一时间同步给小安提测,仅用半天就完成了验收。
......
周五下午,《早间要闻》UI 相关需求全部正式提测,忙得脚不沾地的林卓长舒一口气,当即约了老杨和小安去吃小火锅,算是给这一周的奋战收尾。
火锅店堂里人声鼎沸,烟火气十足,三人找了个靠窗的小桌坐下,肥牛卷刚下锅翻滚,林卓就对着两人大倒苦水:“你们是不知道,这次最坑的就是服务端那帮人,返回的图片大小没个准头,小的才 480p,大的直接 4K,害得我额外花了两天优化 Bitmap 和缓存,差点就耽误进度要周末加班了。”
他说着端起饮料碰了碰老杨的杯子,语气满是庆幸:“还好老杨你周一提前问了我超大图的问题,不然我压根没往这方面想,周末指定得泡在公司了,谢了啊,我先干了!”
老杨夹着一片毛肚涮着,漫不经心地抬眼笑道:“嘻嘻,那图片本来就是我这边返回的。”
见林卓瞬间愣住,他才慢悠悠补充,“车机端这个离线大模型,前期数据都是直接从网上爬的,没做过滤和压缩,细节优化得等功能跑通了再搞,先把核心流程跑起来再说。”
林卓脸上的感激之情瞬间僵住,嘴里的饮料还没喝完,差点一口呛住,等稳住心神,随即气不打一处来,伸手拍了下老杨的胳膊:“合着坑我的人就是你啊!我说你怎么突然问那问题,原来早知道有这茬!”
小安在一旁笑得直不起腰,握着筷子的手都跟着发颤,刚夹起一片毛肚要往锅里放,又忍不住缩回手按住肚子,眼泪都快笑出来了。
林卓瞪了老杨一眼,干脆摆起架子:“老杨,那这顿,你请客!”
老杨乐呵呵地应下:“没问题,想吃啥随便点,我请客。”
酒足饭饱,老杨结完账,拍了拍林卓的肩膀随口提了句:“对了,你这次做的大图放大动画还行,后续可以研究研究 MotionLayout,更丝滑,还能和布局联动做动画。”
林卓一听,当即垮了脸,吐槽道:“什么意思?这刚忙完又给我埋坑是吧?”
老杨笑着拍拍肩,语气带着点过来人式的调侃:“不自己踩踩坑,怎么把知识点吃透、长记性?下次遇到类似问题,不就轻车熟路了?”
三人并肩走出火锅店,晚风带着凉意,却吹不散并肩作战后的轻松惬意。
林卓嘴上抱怨着老杨挖坑,心里却清楚,这一周的磕磕绊绊,早已让他把图片优化的知识点刻进了心里,这份成长,远比少踩一个坑更珍贵。