点击按钮修改值时TimePicker抛出java.lang.NullPointerException异常
问题详情
- 仅在**Pixel 8 Pro升级到Android 14 Build #AP1A.240305.019.A1(2024年3月5日安全更新)**时触发,旧版本无此异常
- 无需编写任何业务代码,仅在布局中添加
TimePicker控件,点击其±调整值按钮就会崩溃 - 崩溃核心原因:
TimePickerSpinnerDelegate.updateInputState()方法中尝试对null的EditText调用hasFocus()
崩溃栈:
FATAL EXCEPTION: main
Process: com.police.nycmoneymanagement, PID: 32232
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.EditText.hasFocus()' on a null object reference
at android.widget.TimePickerSpinnerDelegate.updateInputState(TimePickerSpinnerDelegate.java:480)
at android.widget.TimePickerSpinnerDelegate.-$$Nest$mupdateInputState(Unknown Source:0)
at android.widget.TimePickerSpinnerDelegate$2.onValueChange(TimePickerSpinnerDelegate.java:119)
at android.widget.NumberPicker.notifyChange(NumberPicker.java:2080)
at android.widget.NumberPicker.setValueInternal(NumberPicker.java:1850)
at android.widget.NumberPicker.changeValueByOne(NumberPicker.java:1879)
at android.widget.NumberPicker.-$$Nest$mchangeValueByOne(Unknown Source:0)
at android.widget.NumberPicker$1.onClick(NumberPicker.java:723)
at android.view.View.performClick(View.java:7729)
at android.view.View.performClickInternal(View.java:7706)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:30484)
at android.os.Handler.handleCallback(Handler.java:959)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loopOnce(Looper.java:232)
at android.os.Looper.loop(Looper.java:317)
at android.app.ActivityThread.main(ActivityThread.java:8501)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
解决方案
1. 切换TimePicker为时钟模式(推荐)
这是最简单的规避方案,直接修改布局或代码切换TimePicker的显示模式,绕过Spinner模式的系统bug:
布局中设置:
<TimePicker android:id="@+id/timePicker" android:layout_width="wrap_content" android:layout_height="wrap_content" android:timePickerMode="clock" />
或代码中动态设置:
TimePicker timePicker = findViewById(R.id.timePicker); timePicker.setTimePickerMode(TimePicker.MODE_CLOCK);
2. 反射修复Spinner模式(若必须使用该模式)
如果业务要求必须用Spinner模式,可通过反射包装系统的监听逻辑,避免空指针:
try { // 获取TimePicker的Delegate实例 Field delegateField = TimePicker.class.getDeclaredField("mDelegate"); delegateField.setAccessible(true); Object delegate = delegateField.get(timePicker); if (delegate instanceof TimePickerSpinnerDelegate) { // 获取原有的值变化监听 Field listenerField = TimePickerSpinnerDelegate.class.getDeclaredField("mOnValueChangeListener"); listenerField.setAccessible(true); NumberPicker.OnValueChangeListener originalListener = (NumberPicker.OnValueChangeListener) listenerField.get(delegate); // 包装监听,先检查EditText是否为空 NumberPicker.OnValueChangeListener wrappedListener = (picker, oldVal, newVal) -> { try { Field pickerInputField = NumberPicker.class.getDeclaredField("mInputText"); pickerInputField.setAccessible(true); EditText pickerEditText = (EditText) pickerInputField.get(picker); if (pickerEditText != null) { originalListener.onValueChange(picker, oldVal, newVal); } } catch (Exception e) { e.printStackTrace(); } }; // 替换原监听 listenerField.set(delegate, wrappedListener); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); }
注意:反射依赖系统内部类结构,后续系统更新可能失效,需做好兼容处理。
3. 等待系统补丁
此问题属于Android系统层面的bug,Google后续大概率会通过OTA更新修复,可告知受影响用户等待系统推送更新。
内容的提问来源于stack exchange,提问作者Paul

