尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Android 10快捷开关长按白屏:SystemUI到Settings跳转链路排查与修复

Android 10快捷开关长按白屏:SystemUI到Settings跳转链路排查与修复 先说结论这种白屏问题十有八九不是SystemUI自己崩了而是长按之后要跳转的那个设置页Activity/Fragment在定制ROM里被改坏或者被裁掉了SystemUI这边又没做异常兜底于是直接启动了一个空壳页面。做系统定制的兄弟应该都懂下拉状态栏里的快捷开关AOSP里叫QSTile单击负责开关功能长按负责跳转到对应的详细设置页。点按没问题长按就白屏十有八九问题出在跳转链路而不是开关本身。这个现象还特别容易出现在Android 10.0的定制ROM上尤其是从原生AOSP代码改过来的项目。因为Android 10正好把Wi-Fi和移动数据整合成了一个新的“互联网”Internet入口长按Wi-Fi图标不再像以前那样直接跳进WifiSettingsActivity而是会拉起一个网络设置面板。如果这个面板在你的ROM里没有正确注册或者对应的Fragment被裁剪了那白屏几乎是必然的。蓝牙那边也是类似道理长按蓝牙图标跳的是蓝牙设置页一旦Settings模块出问题同样白屏。这篇文章我会把整个排查和修复流程完整捋一遍包括怎么抓日志定位是SystemUI的锅还是Settings的锅、怎么用adb命令单独拉起页面来验证、代码层面怎么改才稳妥以及一些我在实际操作中踩过的坑。适合正在做Android 10.0系统定制、SystemUI二次开发或者维护第三方ROM的工程师参考。原生的代码逻辑我也会点到但重点还是讲清楚排查思路毕竟每家的改动不一样思路通了比抄代码管用得多。1. 先说现象快捷开关长按白屏到底是个什么鬼1.1 白屏现象和影响范围先描述一下这个bug的典型表现。系统正常开机下拉状态栏快捷开关区域能看到一排图标Wi-Fi、蓝牙、移动数据、飞行模式这些。单击Wi-Fi或者蓝牙图标开关功能正常图标颜色会变Wi-Fi也能正常连上路由器蓝牙也能正常配对。但是长按Wi-Fi图标或者长按蓝牙图标本来应该跳转到对应设置页的结果屏幕突然白屏像是一个空白的Activity被拉起来了既没有标题栏也没有内容。屏幕响应倒是没死因为按Home键还能退出但再点进去也是同样的白屏。这个问题的影响范围看起来不大但实际很烦。用户可能不常用长按功能可一旦用就会觉得系统“坏了”尤其是普通用户不会区分“设置进不去”和“系统崩溃”反馈到售后那边的描述基本就是“手机出问题了”。测过几个定制项目后发现这个问题通常不是偶发性的而是100%复现所以只要复现一次定位起来其实并不难。1.2 长按和单击走的不是同一条代码路径很多不熟悉SystemUI内部机制的开发可能第一反应是在SystemUI的开关View里找问题觉得是长按事件没处理好。这里先纠正一个方向在AOSP里快捷开关的单击和长按本来就是两条完全独立的代码路径。单击走的是handleClick逻辑很直接就是切换状态、调用对应服务。长按走的是handleSecondaryClick次级点击的语义就是“我要进入更多的设置”所以里面几乎不会去干开关状态的事而是直接构造一个Intent跳转Settings里的某个Activity或者Fragment。这条路径上任何一个环节出了问题比如Intent构造错了、目标Activity不存在、Settings端加载页面时异常表现就是白屏。这一点理解之后排查范围就能迅速缩小要么是SystemUI这边跳转的Intent有问题要么是Settings那边被跳转的页面有问题。别再傻乎乎去查蓝牙开关的底层逻辑了方向错了只会浪费时间。2. 白屏根因分析到底是SystemUI的锅还是Settings的锅2.1 Android 10.0里长按Wi-Fi和蓝牙到底会跳到哪里AOSP原生代码里Android 10.0的SystemUI对Wi-Fi图标的长按逻辑已经不是直接跳到WifiSettingsActivity了。因为Android 10把Wi-Fi和移动数据整合到一个叫“互联网”Internet的大设置入口里长按Wi-Fi图标会尝试拉起一个网络相关的设置面板这个面板可能是Settings.Panel.ACTION_INTERNET也可能是直接跳转到“网络与互联网”这个设置页面。具体要看InternetTile这个类的实现它在源码里的路径大概是frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java关键代码大致长这样Override public void handleSecondaryClick(Nullable View view) { Intent intent new Intent(Settings.Panel.ACTION_INTERNET); mHost.startActivityDismissingKeyguard(intent); }蓝牙的长按逻辑在BluetoothTile或者类似的Tile实现里一般会跳转到Settings.ACTION_BLUETOOTH_SETTINGS。AOSP源码路径大概是frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java原生代码里用的是startActivityDismissingKeyguard这个方法会先解锁屏幕再启动Activity。如果目标Activity在系统里找不到理论上应该抛ActivityNotFoundException但很多定制ROM在改代码的时候给这层跳转加了try-catch或者干脆把它包在了一个异步任务里异常吞掉之后Activity没起来白屏就出现了。2.2 Settings端的白屏才是最常见的大头从我实际处理过的几个项目来看SystemUI端的Intent问题反而好修真正让人头疼的是Settings端。长按之后确实拉起了一个Activity但那个Activity里加载的Fragment在启动过程中抛了异常于是呈现给用户的就是一个白屏。具体原因可能有很多常见的有这么几类第一定制ROM把Settings里的某个Fragment裁剪了但Activity还在。比如有些厂商精简系统时把蓝牙设置页或者网络设置页相关的Fragment代码删掉了或者是通过混淆配置把这个类给干掉了Activity启动后尝试加载一个不存在的Fragment结果就是onCreate都走完了但页面内容区域完全是空的。第二Fragment依赖了某个系统服务或者Controller但那个服务在当前版本的ROM里被改没了或者返回了null。比如蓝牙设置Fragment通常会依赖BluetoothAdapter如果蓝牙服务起不来或者适配器没初始化好Fragment就会在创建列表的时候抛NPE白屏可以说是必然的。第三主题资源问题。白屏并不全是崩溃也有可能是Activity起来了但布局加载时用到的自定义主题没有正确适配导致背景是白色的、内容也是白色的看起来像白屏实际只是看不见内容。这种问题在替换了第三方主题或者改了overlay资源之后比较容易出现。2.3 还在白屏先看是“纯白”还是“带崩溃”排查的时候我习惯先把白屏分成两种一种是纯粹的白色背景好像是空Activity另一种是启动时闪了一下然后白屏之后可能还会弹一个“设置屡次停止运行”的对话框。如果是后者那基本就是Settings端Fragment或Activity抛异常了直接去抓崩溃日志最有效。如果一直是白色、没有任何崩溃提示那更像是在启动阶段就被拦住了或者Activity的contentView根本没加载出来。这两种情况的侧重点不同前者要去解崩溃后者要去查Activity启动流程和布局加载。区分方法很简单白屏出现之后不要急着按Home直接连adb抓logcat日志搜索关键字AndroidRuntime或者FATAL EXCEPTION有崩溃就是Settings端的问题什么都没有再搜ActivityTaskManager看有没有START相关的日志没有的话就说明Activity压根没启动起来。3. 修复实操从抓日志到改代码的完整流程3.1 第一步先复现并抓全日志看清报错再动手修这类白屏问题我从来不会一上来就翻代码而是先把日志抓全。步骤很简单手机连上adb在终端里执行adb logcat -c清掉旧的日志然后让测试机复现一次白屏最快的方法是下拉状态栏长按Wi-Fi图标等白屏出现。再切回终端执行adb logcat -d -b crash这条命令会打印出崩溃缓冲区的日志。如果白屏是由Settings崩溃导致的在这里基本就能看到关键信息。比如我之前遇到过一个案例日志里明确写着FATAL EXCEPTION: main Process: com.android.settings, PID: 3127 java.lang.NullPointerException: Attempt to invoke virtual method boolean android.bluetooth.BluetoothAdapter.isEnabled() on a null object reference这就是典型的蓝牙Adapter为null导致的白屏。原因也简单定制ROM在启动阶段延迟初始化了蓝牙服务Settings的Fragment加载的时候BluetoothAdapter还没准备好。如果crash缓冲区里没有东西再用常规日志过滤关键进程adb logcat -d | grep -E ActivityTaskManager|SystemUI|Settings重点看有没有和START、Displayed相关的记录确认Activity到底启动了没有。这一步做完问题方向基本就能定了。3.2 第二步用adb直接拉起目标Activity隔离问题边界抓完日志后我习惯再用adb单独启动一次目标Activity来进一步确认是SystemUI跳转的问题还是Settings本身就起不来。以蓝牙设置页为例执行adb shell am start -a android.settings.BLUETOOTH_SETTINGS直接拉起系统的蓝牙设置页。如果这样拉起后页面能正常显示那就说明Settings本身没毛病问题在SystemUI那边的Intent构造或者跳转方式上。如果这样拉起后依然是白屏那基本可以确定就是Settings端的问题了可以放心把重点放在Settings代码上。对于Wi-Fi/互联网入口对应的Action有很多种可以先试试adb shell am start -a android.settings.WIFI_SETTINGS或者adb shell am start -a android.settings.INTERNET_SETTINGS拉到哪个能用就用哪个。这一步能非常快地把问题边界画出来避免在SystemUI代码里瞎翻。我见过有同行花了一整天才查明白结果adb一拉发现Settings自己也起不来问题本来就出在Settings端。3.3 第三步代码级修复三种情况的针对性处理确认了问题边界之后再动手改代码心里就有底了。这里分三种情况说每种我都给出比较稳妥的改法。情况一Settings端的Fragment或者Service依赖崩溃比如前面提到的BluetoothAdapter为null这种情况。稳妥的做法是在Fragment里做空判断至少保证页面主体能渲染出来不让整个Activity变成白屏。常见的处理是在onViewCreated或者列表Adapter初始化之前加一道保护BluetoothAdapter adapter BluetoothAdapter.getDefaultAdapter(); if (adapter null) { // 页面主体改为显示蓝牙不可用之类的占位布局 showBluetoothUnavailablePlaceholder(); return; }当然这是应急修复长期来讲还是得把蓝牙服务的初始化时序问题解决了但至少用户不会看到白屏。占位页面可以用一个简单的LinearLayout加上提示文字代码量不大能显著提升用户体验。情况二SystemUI跳转的Intent目标不存在或者被裁剪这种情况在定制ROM里频繁出现因为精简设置项的时候很容易把目标Activity的注册拿掉但SystemUI里没人同步改。最稳妥的改法是在SystemUI的Tile里加异常兜底。以InternetTile为例try { Intent intent new Intent(Settings.Panel.ACTION_INTERNET); mHost.startActivityDismissingKeyguard(intent); } catch (ActivityNotFoundException e) { Log.w(TAG, Internet settings not found, fallback to WIFI_SETTINGS, e); try { Intent fallbackIntent new Intent(Settings.ACTION_WIFI_SETTINGS); mHost.startActivityDismissingKeyguard(fallbackIntent); } catch (ActivityNotFoundException ex) { // 到这里说明连Wi-Fi设置都没了那就只能提示一下了 } }这样做的好处是即使主入口被裁了也能退而求其次跳到另一个相关设置页而不是留一个白屏给用户。情况三目标Activity的exported属性或权限问题有次遇到一个很有意思的问题SettingsActivity明明存在但SystemUI长按跳不过去原因是定制ROM给Settings的某个Activity加了android:exportedfalse导致跨进程调用被拒绝。SystemUI是独立进程它要启动Settings的Activity对方必须是exported的否则会抛SecurityException或者直接失败。如果确认是这个原因在Settings的AndroidManifest.xml里给对应Activity补上android:exportedtrue同时检查有没有权限限制如果有permission要求要么补上权限声明要么确认调用方已经有权限。这里要强调一下不是所有Activity都适合设成exported设置页里的Activity一般问题不大但涉及支付、账号等敏感页面的就不要乱动。3.4 第四步编译验证别只测一次就算完改完代码当然要编译。如果只改了SystemUI在AOSP根目录执行make SystemUI -j16如果改了Settings执行make Settings模块编译完替换进系统镜像重新烧录或者push进去。SystemUI通常可以这样快速替换adb root adb remount adb push out/target/product/xxx/system/system/systemui/SystemUI.apk /system/system/systemui/SystemUI.apk adb rebootSettings也一样替换对应APK然后重点回归这几个场景一是长按Wi-Fi图标确认能进入设置页而不是白屏二是长按蓝牙图标确认能进入蓝牙设置页三是单击Wi-Fi和蓝牙图标确认开关功能没受影响四是飞行模式下长按这两个图标确认不会因为网络状态变化引出新的异常。这几轮回归没问题修复才算真正落地。4. 常见问题与排查技巧实录4.1 快速定位“白屏”是SystemUI的问题还是Settings的问题这里整理一个速查思路遇到白屏问题可以按这个顺序排查现象优先怀疑方向排查手段白屏且时有崩溃弹窗Settings端Fragment崩溃adb logcat -d -b crash白屏但无崩溃日志Activity未启动或布局空洞adb logcat -d | grep ActivityTaskManager单击正常长按白屏SystemUI跳转逻辑或目标Activity注册adb shell am start单独拉起目标页长按后快速闪一下再白屏Resource加载异常或主题问题检查overlay资源和主题继承关系只在特定定制版本出现裁剪导致的依赖缺失adb shell pm list packages对比目标项这个表格基本能覆盖90%的白屏场景。核心原则就是先定位边界再动手改。4.2 长按热点、NFC等开关白屏的通用处理思路Wi-Fi和蓝牙只是最常见的两个长按其他快捷开关也可能遇到类似白屏问题比如热点、NFC、无线充电等。处理思路完全一样不用为每个开关单独挠头。先把开关对应的Tile类找到路径一般都在frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/下面文件名和开关名基本对应。然后看它的handleSecondaryClick方法确认它长按之后跳的是什么Intent再去Settings里确认对应的Activity有没有注册、能不能正常启动。如果不想每个Tile都去改一遍兜底逻辑也可以考虑在QSTileHost这一层做统一的Intent启动包装在startActivityDismissingKeyguard里统一加try-catch日志也统一打点后续排查起来能省不少事。4.3 逻辑对但Selector没生效优先检查这几个地方另外有个很容误判的场景代码逻辑完全对Settings也能正常起但长按跳转过去之后页面糊了看起来像白屏。这种情况优先检查AndroidManifest里Activity的启动模式以及SystemUI传过去的Intent有没有带特殊flag。比如有次遇到的问题是SystemUI启动Wi-Fi设置时带了FLAG_ACTIVITY_CLEAR_TASK直接把之前的设置页任务栈清空了导致页面起来后栈内状态异常内容显示不出来。去掉多余flag之后问题就消失了。还有一次是Intent里带了一个不存在的extraSettings端解析extra时抛了异常同样表现为白屏。这类问题没有统一的解法逻辑上就是启动参数越简单越好非必要不传extra。4.4 不同Android版本之间的差异要特别注意标题虽然写的是Android 10.0但实际做适配的兄弟可能在Android 11、12、13上也会遇到类似问题。不同版本的SystemUI代码结构差异很大比如Android 12开始SystemUI的很多类都迁移到了新的包名和架构下Tile的获取方式也变了。我见过有人拿着Android 10的修改方案硬套Android 13结果编译都过不了。所以这里特别提醒换了一个大版本先看对应版本的源码结构再决定怎么改。不要直接拿老补丁硬怼。比如Android 13里InternetTile的跳转逻辑、Settings页面的Fragment路径和Android 10都有不小的差异白屏的根因也可能完全不同。4.5 独家避坑技巧改完别忘回归开关本身最后分享一个我踩过很多次才记牢的坑修白屏时很容易只顾着长按跳转的路径结果一不小心把单击开关的功能也影响了。比如在Tile类里做异常兜底时改的handler被单击和长按共用稍微一不注意单击也会出问题。所以每次修完白屏除了验证长按跳转一定把单击开关、开关状态同步、不同系统语言下的表现都过一遍。回归时间多了十来分钟但能避免提交一个修了A害了B的补丁。5. 写在最后的实操心得这类长按白屏的问题说穿了就是典型的系统定制残留问题。原生AOSP在出厂状态下通常不会有这种毛病一旦出现在定制ROM里八成是因为某个模块被改过、被精简过或者被替换过导致原本能正常走的调用链断了。SystemUI只是个受害者真的元凶往往在Settings那边被裁掉了一个Fragment、一个Activity或者一个不起眼的服务依赖。我个人的习惯是遇到这类问题不要急着看代码先花十分钟把日志和Activity启动情况摸清楚。用adb单独拉起目标Activity这一步真的特别好用能一瞬间把问题边界画清楚至少帮我省掉过一整天的无用功。修复的时候SystemUI侧的异常兜底是必备品不管问题源头在哪儿先保证长按不白屏、能回退到一个可用的页面这是底线。Settings侧的崩溃得治本空指针判断和占位页面只能算应急真正让依赖服务稳定起来才是长久之计。如果大家在自己项目里还碰到过其他诡异的白屏场景不管是哪个快捷开关、哪个Android版本欢迎在评论区一起交流踩坑经验共享出来后面的人就能少走点弯路。
返回列表