You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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显示低电量提示)多次旋转设备时复现过一次该问题。

核心疑问:屏幕旋转处理代码是否存在不当之处?如何对此问题进行防护?


解答

旋转处理代码的潜在问题

  1. 阻塞EDT线程:*同步的animateLayoutAndWait*会占用事件调度线程(EDT)直到动画完成,在屏幕旋转回调中使用该方法,可能导致UI响应延迟;在系统资源紧张(如低电量)时,EDT被阻塞无法及时处理系统级UI布局事件,间接加剧Native层的异常触发概率。
  2. 缺失空值校验:代码直接调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 09:15:55