iOS设备间歇性NullPointerException:闲置或屏幕旋转后触发
问题描述
iOS设备上间歇性出现NullPointerException,仅在屏幕旋转后发生,多出现于应用长时间闲置后(曾在iPhone显示低电量提示时多次旋转设备复现)。
报错日志
[EDT] 11:19:53,486 - Orientation changed to Landscape - showHomeForm [EDT] 11:19:53,490 - line 572 [EDT] 11:19:53,495 - line 574 [EDT] 11:19:53,935 - line 576 [EDT] 11:19:53,940 - Orientation changed to Landscape Finished - showHomeForm [EDT] 11:19:53,954 - line 565 [EDT] 11:19:53,957 - Orientation changed to Portrait Finished - showHomeForm [EDT] 11:19:58,803 - Exception: java.lang.NullPointerException - null java.lang.NullPointerException at com_codename1_impl_ios_IOSImplementation.getDisplaySafeArea:256 at com_codename1_ui_Container.snapToSafeAreaInternal:2395 at com_codename1_ui_Container.calcPreferredSize:3176 at com_codename1_ui_Component.preferredSizeImpl:3545 at com_codename1_ui_Component.preferredSize:3584 at com_codename1_ui_Component.getPreferredSize:1449 at com_codename1_ui_Component.getPreferredH:1539 at com_codename1_ui_layouts_BorderLayout.positionTopBottom:477 at com_codename1_ui_layouts_BorderLayout.layoutContainer:317 at com_codename1_ui_Container.doLayout:2583 at com_codename1_ui_Container.layoutContainer:2219 at com_codename1_ui_Container.doLayout:2588 at com_codename1_ui_Container.layoutContainer:2219 at com_codename1_ui_Container.doLayout:2588 at com_codename1_ui_Form.sizeChangedInternal:881 at com_codename1_ui_Display.handleEvent:2514 at com_codename1_ui_Display.edtLoopImpl:1300 at com_codename1_ui_Display.mainEDTLoop:1218 at com_codename1_ui_RunnableWrapper.run:120 at com_codename1_impl_CodenameOneThread.run:176 at java_lang_Thread.runImpl:153 [EDT] 11:19:58,823 - Exception in My App version 2.03 [EDT] 11:19:58,827 - OS ios [EDT] 11:19:58,832 - Error java.lang.NullPointerException [EDT] 11:19:58,836 - Current Form VXSMS_Main [EDT] 11:19:58,840 - Exception: java.lang.NullPointerException - null java.lang.NullPointerException at com_codename1_impl_ios_IOSImplementation.getDisplaySafeArea:256 at com_codename1_ui_Container.snapToSafeAreaInternal:2395 at com_codename1_ui_Container.calcPreferredSize:3176 at com_codename1_ui_Component.preferredSizeImpl:3545 at com_codename1_ui_Component.preferredSize:3584 at com_codename1_ui_Component.getPreferredSize:1449 at com_codename1_ui_Component.getPreferredH:1539 at com_codename1_ui_layouts_BorderLayout.positionTopBottom:477 at com_codename1_ui_layouts_BorderLayout.layoutContainer:317 at com_codename1_ui_Container.doLayout:2583 at com_codename1_ui_Container.layoutContainer:2219 at com_codename1_ui_Container.doLayout:2588 at com_codename1_ui_Container.layoutContainer:2219 at com_codename1_ui_Container.doLayout:2588 at com_codename1_ui_Form.sizeChangedInternal:881 at com_codename1_ui_Display.handleEvent:2514 at com_codename1_ui_Display.edtLoopImpl:1300 at com_codename1_ui_Display.mainEDTLoop:1218 at com_codename1_ui_RunnableWrapper.run:120 at com_codename1_impl_CodenameOneThread.run:176 at java_lang_Thread.runImpl:153
空指针发生位置
空指针出现在com_codename1_impl_ios_IOSImplementation.getDisplaySafeArea方法第256行:
@Override public Rectangle getDisplaySafeArea(Rectangle rect) { if (rect == null) { rect = new Rectangle(); } int x = nativeInstance.getDisplaySafeInsetLeft(); // 第256行,空指针触发位置 int y = nativeInstance.getDisplaySafeInsetTop(); int w = getDisplayWidth() - nativeInstance.getDisplaySafeInsetRight() - x; int h = getDisplayHeight() - nativeInstance.getDisplaySafeInsetBottom() - y; rect.setBounds(x, y, w, h); return rect; }
nativeInstance已被声明为静态初始化:
public static IOSNative nativeInstance = new IOSNative();
屏幕旋转处理代码
VXSMS_Main.addOrientationListener((ActionListener) new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { if(Display.getInstance().isPortrait()){ Log.p("Orientation changed to Portrait - showHomeForm"); l_Home.setHidden(false); Log.p("line 571"); HomeButtoncont.setScrollableY(false); Log.p("line 573"); HomeButtoncont.getParent().animateLayoutAndWait(400); Log.p("line 575"); Log.p("Orientation changed to Portrait Finished - showHomeForm"); } else{ Log.p("Orientation changed to Landscape - showHomeForm"); l_Home.setHidden(true); Log.p("line 582"); HomeButtoncont.setScrollableY(true); Log.p("line 584"); HomeButtoncont.getParent().animateLayoutAndWait(400); Log.p("line 586"); Log.p("Orientation changed to Landscape Finished - showHomeForm"); } } });
补充信息:HomeButtoncont是包含4个MultiButton的容器,仅在应用长时间闲置后(iPhone显示低电量提示)多次旋转设备时复现过一次该问题。
核心疑问:屏幕旋转处理代码是否存在不当之处?如何对此问题进行防护?
解答
旋转处理代码的潜在问题
- 阻塞EDT线程:*同步的
animateLayoutAndWait*会占用事件调度线程(EDT)直到动画完成,在屏幕旋转回调中使用该方法,可能导致UI响应延迟;在系统资源紧张(如低电量)时,EDT被阻塞无法及时处理系统级UI布局事件,间接加剧Native层的异常触发概率。 - 缺失空值校验:代码直接调用
HomeButtoncont.getParent(),若HomeButtoncont未被添加到父容器或在闲置后被意外回收,会触发空指针(虽非本次报错原因,但属于潜在风险)。
防护与修复措施
1. 修复Native层空指针问题
iOS在应用进入后台或资源回收时,可能导致Native实例被销毁,需在getDisplaySafeArea方法中添加空值校验并重建实例:
@Override public Rectangle getDisplaySafeArea(Rectangle rect) { if (rect == null) { rect = new Rectangle(); } // 添加nativeInstance空值校验,为空则重建 if (nativeInstance == null) { nativeInstance = new IOSNative(); } int x = nativeInstance.getDisplaySafeInsetLeft(); int y = nativeInstance.getDisplaySafeInsetTop(); int w = getDisplayWidth() - nativeInstance.getDisplaySafeInsetRight() - x; int h = getDisplayHeight() - nativeInstance.getDisplaySafeInsetBottom() - y; rect.setBounds(x, y, w, h); return rect; }
2. 优化旋转回调中的动画处理
将*同步的animateLayoutAndWait*替换为异步的animateLayout,避免阻塞EDT:
VXSMS_Main.addOrientationListener((ActionListener) new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { if(Display.getInstance().isPortrait()){ Log.p("Orientation changed to Portrait - showHomeForm"); l_Home.setHidden(false); HomeButtoncont.setScrollableY(false); // 使用异步动画,释放EDT资源 HomeButtoncont.getParent().animateLayout(400); Log.p("Orientation changed to Portrait Finished - showHomeForm"); } else{ Log.p("Orientation changed to Landscape - showHomeForm"); l_Home.setHidden(true); HomeButtoncont.setScrollableY(true); HomeButtoncont.getParent().animateLayout(400); Log.p("Orientation changed to Landscape Finished - showHomeForm"); } } });
若需等待动画完成后执行后续逻辑,可使用带回调的重载方法:
HomeButtoncont.getParent().animateLayout(400, () -> { // 动画完成后的逻辑 Log.p("Orientation changed to Portrait Finished - showHomeForm"); });
3. 添加关键组件的空值校验
在旋转回调中对l_Home、HomeButtoncont等核心组件添加空值校验,避免意外空指针:
if (l_Home != null && HomeButtoncont != null && HomeButtoncont.getParent() != null) { // 执行布局修改和动画逻辑 }
4. 处理应用前后台状态
在应用进入后台时移除OrientationListener,恢复前台时重新注册,避免后台状态下的无效UI操作:
// 定义全局监听实例 ActionListener orientationListener = (ActionListener) evt -> { // 原旋转处理逻辑 }; // 注册前后台状态监听 Display.getInstance().addStatusListener(status -> { if (status == StatusListener.STATUS_FOREGROUND) { VXSMS_Main.addOrientationListener(orientationListener); } else if (status == StatusListener.STATUS_BACKGROUND) { VXSMS_Main.removeOrientationListener(orientationListener); } });
内容的提问来源于stack exchange,提问作者James N
相关产品推荐
相关产品推荐

