Window 内外藏机巧 旧岗新页见真章

本系列为 Android 技术职场题材虚构小说,所有登场人物、公司名称、组织架构及相关情节均为创作所需虚构而来,若有雷同,纯属偶然。书中涉及的技术知识经专业梳理,仅供参考。

十二)Window 内外藏机巧 旧岗新页见真章

《早间要闻》功能顺利上线的第二周,一大早,林卓刚喝完咖啡打开 Android Studio,张磊就带着通知走进办公区:"车机项目核心开发收尾,部分 Android 岗位调回原岗位,离线模型相关岗位留下再支持两周适配优化。"

消息一出,研发区里先是安静了两秒。紧接着,不少人都露出了"终于能缓口气"的神情。

"林卓,小安,你们去接手《智慧社区》App 的维护工作。工作依然向我汇报,预计这周三进入到工作中去,OK?"

林卓回复了个"收到",便一直盯着屏幕,心里说不上来是什么滋味。

车机项目那几个月,他几乎是被问题推着往前跑。View 绘制、RecyclerView 优化、Bitmap 压缩、缓存策略、动画过渡,一个坑接一个坑,却也真真切切把他从"能写功能"磨成了"能扛问题的人"。

可《智慧社区》这名字一出来,林卓脑子里立刻浮现出另一个词------维护

大多数开发都明白,这两个字通常不意味着轻松,反而意味着陈年旧账、历史包袱、没人愿碰的烂摊子。

张磊像是看出了林卓的心思,站在过道补了一句:"别觉得维护不重要。车机项目是新业务,《智慧社区》是老盘子,线上用户很多的,投诉很直接。这个 App 出问题,不是在日报里难看,是物业客服的电话会被打爆。"

他说完,目光落在林卓身上。

"我知道,最近这一阵子做复杂 UI 和性能优化,手感正热。但是这个《智慧社区》项目里有几个老问题,你去啃最合适。"

林卓点点头:"好,张哥。"

小安抱着测试机,从另一排工位探出头来,冲他弯了弯眼睛:"又成搭档咯。林工!"

她尾音还是那样微微上扬。

林卓本来还有点发沉的心情,被她这一句拎得轻了几分,忍不住笑了笑:"这会儿叫我林工,等你测出 Bug 来,就开始叫林卓了。再说了,什么叫又成搭档,你不一直跟我是搭档吗?"

小安腼腆一下,躲开了。

上午十点,《智慧社区》的研发负责人拉了个短会。

会议室投影上,《智慧社区》四个字下面挂着一串长得吓人的问题清单:

  • 公告详情页偶发白屏。
  • 活动页点开后跳到外部浏览器,用户误以为退出了 App。
  • 访客通行证弹窗挡住底部按钮,偶发无法关闭。
  • ....等等

还有一条,是产品专门标红的:社区运营准备上线一个"便民服务中心"改版,把公告、活动、访客、报修入口整合到同一页,下周就要先出可提测版本。

林卓听完,眉头一点点皱了起来------这那是在维护一个模块,这是在给一栋年久失修的楼做边住人边翻修。

会后,研发负责人把他单独留了两分钟。

"这个项目,你才是主力呀,张磊跟我打过招呼了,说尽量多找你商量。"

"这个项目先看公告和访客这两块。"研发负责人继续说道,"用户投诉最多,产品也最急。公告详情是 H5,访客通行证那块是悬浮层,两个都和界面交互体验强相关。你最近对 UI 比较敏感,优先把这两块稳住。"

下午,林卓吃完午饭,回到工位,想在小眯一会儿之前,拉下代码看看。

这一看,太阳穴都开始发紧。

项目目录像年轮一样一圈套一圈。

旧版 Activity、新版 Fragment、若干工具类、半套自己封装的基类、半套外包时代留下来的公共库,全混在一起。

最让他头疼的是主题。

有的页面继承的是普通 Activity,有的继承 AppCompatActivity,还有几个页面的按钮直接用系统原生控件,颜色、圆角、按压态各不相同,同一个 App 看起来像三个团队各做各的。

"这玩意儿能活到今天,真是物业用户心胸宽广。"林卓小声嘀咕了一句。

管他的,先眯会儿!

大概过了半小时,刚醒,小安就把一台 Android 8 的测试机放到他桌上。

"先给你热个身。"她说,"公告页在这台机器上最容易复现问题。你点社区活动,进去后再点详情链接,有时候会跳系统浏览器,有时候直接白屏。还有这个访客二维码弹窗,点外面不消失,底下按钮还会被挡住。"

她说着,俯身给他演示操作路径。

发尾从肩边滑下来,带着淡淡洗发水清香,额前的发须轻轻晃着,杏眼专注得微微发亮,嘴角还噙着一点笑。

林卓一边盯着屏幕,一边不动声色把椅子往后退了半寸,免得自己分心。

问题复现得竟然如此稳定,公告详情页里嵌的是 WebView

旧代码几乎没做什么限制,javaScriptEnabled 直接开着,链接跳转也没仔细拦截,页面里只要有外链,系统就可能拉起外部浏览器。更离谱的是,桥接代码里还挂了个大而全的 addJavascriptInterface(),暴露了好几个原生方法,连他看了都觉得心惊。

林卓把代码往下翻,脸色慢慢变了:"这不是埋雷,这是在雷区上开蹦迪大会。这以前的老项目到底有人管没管,这问题现在才发现?"

小安没听清,偏过头问:"你说什么?"

"我说,问题不算少。"林卓迅速改口。

下午三点左右,老杨拎着他的搪瓷杯过来转了一圈。

车机项目虽然已经收尾,但他还在帮忙处理收尾适配,顺带看看这边的情况。

他站在林卓工位旁,瞄了两眼代码,忽然问了个看似不相干的问题:"你说,一个普通 Activity 打开的时候,屏幕上一般会有几个 Window?"

林卓一愣。

这问题他以前要是被问,十有八九得卡壳。

但现在老杨话音刚落,他脑子里已经条件反射似的浮出答案。

"通常说的话,状态栏一个,导航栏一个,应用自己的 ActivityWindow 一个。应用这边实际是 PhoneWindow,布局挂在 DecorView 上。"他答完,抬头看老杨,"你又挖坑?"

老杨喝了口他那杯味道可疑的"抹茶美式",笑了笑说到:"访客弹窗那问题,多半和这个有关。你别只盯控件,多看看它是挂在哪层上的。"

说完他就走了。

林卓低头继续查,很快顺着线索扒到了老实现。

旧版"访客通行证"为了做一个所谓的"全局悬浮快捷入口",竟然直接通过 WindowManager 往界面上 addView() 了一层浮窗,拿的还是业务方传下来的 Context。有时候是 Activity,有时候干脆是包装过的 Application Context,生命周期乱得一塌糊涂。

难怪会出现弹层不消失、页面退出后还残留遮挡、偶发 WindowLeaked 的问题。

更麻烦的是,产品最初还真想把这个"快捷入口"做成跨应用悬浮。

林卓看着需求备注,眼皮都跳了一下。

要是走 TYPE_APPLICATION_OVERLAY,那就得碰 SYSTEM_ALERT_WINDOW 权限。这种权限敏感、用户感知强、审核风险高的能力,放在社区 App 里,怎么想都不划算。

他拿着问题和方案去找研发负责人。

"这个需求我建议收一下边界。"林卓说,"如果只是应用内快捷入口,不需要系统级悬浮窗。直接挂在当前 ActivityWindow 体系里更合适,用 PopupWindow 或者页面内浮层就能做,生命周期也好管。真做跨应用悬浮,权限成本和风险都太高,不值。"

负责人听完,没有立刻表态。他盯着文档想了几秒,点头:"我去跟产品说。应用内就够了,别为了一个入口把权限搞复杂。"

有了这句话,林卓心里一下踏实了不少。

技术上能做的事很多,但真正好的方案,往往是既能做成,又不把产品带进坑里。

整个 Bug 的思路定下来后,他开始拆问题。

第一刀先砍 WebView

他把公告详情页的加载逻辑重新梳理了一遍,接入了更规范的 WebViewClient 处理导航行为,只让公司自己控制的社区域名继续在应用内打开,其他外部链接统一交给系统浏览器,避免用户在不知情的情况下被带离当前流程。

为了兼容旧版本行为差异,他顺手把 AndroidX WebKit 的能力也补了进去,至少让一些细节在老机型上表现更一致。

然后是最危险的 JavaScript 桥接。

他没有粗暴地全删。

因为活动页里确实有一个"立即开门"的 H5 按钮,需要调原生访客二维码页面。

但他把桥接接口缩到了最小,只保留一个明确用途的方法,而且只在受信任的自家域名页面中注入;页面一旦跳到外部域名,就不再暴露接口。与此同时,文件访问相关能力也被他收紧,不再给那些不必要的 file 访问留口子。

"能不用的能力,就别给。"林卓一边敲代码,一边在脑子里默念老杨常说的那句"技术要对得起良心"。

第二刀落在访客弹层上。

旧实现里那个用 WindowManager 生造出来的"全局浮层",被他整个撤掉。

新的方案简单很多。

入口按钮仍然放在主页面,但点击后展示的是锚定当前视图的 PopupWindow。弹层本身不再绕开 ActivityWindow 体系,而是老老实实附着在当前界面之上。这样一来,界面销毁时它也能跟着生命周期回收,不会再莫名其妙挂在屏幕上当"幽灵层"。

为了让点击外部关闭生效,他特地补上了几个关键配置。

可聚焦,允许外部触摸,加透明背景。这些细节乍看不起眼,少一个,交互体验就可能完全变味。

他甚至专门在注释里写了一句英文说明,提醒后面维护的人:PopupWindow outside touch requires a background drawable to receive dismiss events.

第三刀,才是他最熟悉也最愿意下手的地方。

UI 一致性。

《智慧社区》这些年版本滚来滚去,早就把样式体系滚散了。

林卓没有一上来就做大规模重构,那样时间上根本来不及。

他先把"便民服务中心"这一版新增和强相关页面统一迁到 AppCompatActivity 体系里,再把主题切到基于 MaterialComponents 的 DayNight 方案。按钮、卡片、输入框这些核心控件,能换成 MDC 组件的就优先替换,至少先把颜色、圆角、文字层级和按压反馈统一起来。

这样做的好处很直接。

旧 Android 版本上,兼容层能兜底现代控件的行为。新版本上,深浅色模式切换也不会再像以前那样一块亮一块暗。

产品下午过来走查时,第一眼就看出了差别。

"这个按钮怎么突然顺眼了很多?"她指着"访客通行"和"在线报修"两个入口问。

林卓没抬头,只淡淡回了一句:"以前是各长各的,现在让它们像一个 App 里的亲兄弟了。"

旁边的小安没忍住,低头笑了一下。

第一轮开发到晚上八点才算告一段落。

林卓刚准备起身活动,小安就把测试报告发了过来。

她人没走,抱着杯热奶茶站在他桌边,神情却很认真。

"好消息是,大部分问题没了。"她说,"公告内跳转稳定了,外链会去浏览器,用户不至于一脸懵。访客弹层也能点外面关闭了,退出页面后不会残留遮挡。旧机型上样式统一很多,深色模式文字也清楚了。"

林卓刚想松口气,小安下一句话就跟着落了下来。

"但我又测出一个安全风险。"

林卓瞬间坐直:"哪儿?"

"我抓包改了个跳转地址。"小安把测试机递给他,"先从社区活动页进详情,再让页面跳一个非社区域名的中间页,结果那个页面居然还能调起原生方法。说明你改的还是不彻底。"

林卓低头看完复现路径,嘴里有点发干,赶紧喝了几口水。

这问题不一定会在线上立刻出事,但只要存在,就是隐患。

他没解释,也没找借口,立刻把逻辑重新收紧。

不是页面开始加载就注入桥接。而是等主文档 URL 校验通过,确认 Host 在白名单里,再挂载那一个最小化接口。页面跳转出去后,也及时移除桥接能力,防止"借壳调用"。

修完再跑一遍,小安复测通过。

她把测试机往桌上一放,轻轻舒了口气:"这回是真的稳了。"

林卓靠在椅背上,也长长吐出一口气。

他忽然意识到,自己现在面对问题时的第一反应,已经和最初完全不一样了。

以前遇到 H5 白屏,他可能只会盯着页面为什么没出来。

现在他会先问,这个页面挂在哪一层,导航链路怎么走,桥接暴露了什么能力,边界有没有收住,旧设备和新设备表现是否一致。

以前遇到弹窗挡按钮,他可能只会调 margin、调 z 轴、调布局。

现在他会先想,这到底是 View 层的问题,还是 Window 层的问题;该不该进 WindowManager,生命周期谁来兜底,交互关闭逻辑是不是完整。

技术知识并没有凭空变成能力。只是这些坑,他终于一个个踩实了。

周四下午,产品和技术负责人组织小范围走查,产品、测试、客户端三方围着两台手机轮流看。

活动页点进去,社区内链留在 App 内,外链正常跳浏览器。

访客弹层从按钮下方平稳弹出,点外部即关闭,返回页面也不会残留。

"便民服务中心"首页的按钮、卡片、标题、输入框风格终于像是同一套设计语言,老机型上也没再出现那种"系统按钮混搭自定义按钮"的割裂感。

第一版走查完毕,难得没有发现重大 Bug。

负责人把平板合上,对林卓说:"维护项目最难的不是写新功能,是接得住旧逻辑,还能在不炸线上的前提下把它慢慢扶正。你这次处理得不错。"

这话说得平,但分量不轻。

林卓"嗯"了一声,表面还算镇定,心里却隐隐发热。

散会后,老杨正好回来取资料。

听完整个过程,他点了点头:"行,说明你现在不只会写页面了。WindowWebView、主题兼容,这些东西单拎出来都不算新鲜,难的是放到一个老项目里,还能把它们捋顺。"

他说着,顿了顿,又补了一句。

"会做新城的人不少,能修旧城的人才更值钱。"

这话像根细针似的,轻轻扎进林卓心里。没有夸得很满,却比直接说"你厉害了"更让人记得住。

晚上下班时,小安拎着包在电梯口等他,人不多,走廊灯光把她的侧脸映得很柔和。

"今天不加班了吧?"她问。

"暂时不用。"林卓说。

"请你去吃个宵夜吧。"她眨了眨眼,"庆祝你把老项目从鬼门关拽回来一点点。"

林卓愣了下:"为什么是你请?"

小安故意板起脸:"因为安全问题那个 Bug 是我测出来的,算我立功后的善心奖励你,不行啊?"

林卓笑了。

"行,当然行。"

两人并肩往电梯里走。

电梯门合上的一瞬间,办公区的灯光被切成一条细缝,很快消失不见。

林卓望着不断变化的楼层数字,忽然觉得,这次从车机项目回到《智慧社区》,也许并不是从高处退回低处。

而是另一种更实在的进阶。

新业务能锻炼冲锋的本事,老业务却更能检验一个人有没有真正理解技术的边界、结构和分寸。很多时候研发无法选择自己的技术路线,但是能够在任何技术路线上走的更远的研发,一定更稀缺!

电梯到一楼时,小安忽然侧过头问他:"林卓。"

"嗯?"

"你现在是不是已经有点像老杨了?"

林卓脚步一顿:"哪儿像?"

"都会提前想到坑了。"她笑起来,杏眼弯弯的,"就是还差一点。"

"差哪一点?"

"差一点老杨那种,挖完坑还能装作不是自己挖的本事。"

林卓当场失笑。

夜风从园区门口吹过来,带着一点初春的凉意。他和小安并肩走出去,心里却有种很踏实的暖。

他知道,《智慧社区》这摊子绝不会只这一点问题,老代码里还藏着更多历史债。

旧主题、老组件、混乱的页面栈、散乱的交互逻辑,后面还有得收拾。

可他已经不像最初那样,会在问题面前先慌一下了,因为他开始明白,真正把一个开发者撑起来的,从来不只是会多少 API、背多少概念。

而是当窗口层叠、页面跳转、兼容适配、安全边界全都缠在一起时,依然能一层层拆开,再一处处缝回去。

此时的他,可能已经忘却了要转正的紧张与急迫,完全的沉浸在解决问题的海洋中,这是技术,也是成长。

相关推荐
谪星·阿凯2 小时前
从XXE遗留疑问到Upload-Labs全通关:文件上传漏洞的溯源与实战突破
android·计算机网络
星轨初途2 小时前
C++ 类和对象(下):初始化列表、static 成员与编译器优化深度剖析
android·开发语言·c++·经验分享·笔记
恋猫de小郭2 小时前
Flutter 的 build_runner 已经今非昔比,看看 build_runner 2.13 有什么特别?
android·前端·flutter
xiangpanf12 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx15 小时前
安卓线程相关
android
消失的旧时光-194316 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon17 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon17 小时前
VSYNC 信号完整流程2
android
dalancon17 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android