17、React Native性能优化与异常处理全解析

React Native性能优化与异常处理全解析

1. FlatList性能优化

1.1 基础优化

  • 预计算列表项高度 :使用 getItemLayout 来预计算列表项的高度,这样列表就无需在运行时进行计算,提高渲染效率。
  • 使用图像缓存 :例如 react-native-fast-image ,可以缓存图像,减少重复下载和加载,提升用户体验。

1.2 高级优化

参数 作用 优化策略
initialNumToRender 定义初始渲染的列表项数量,这些项在 FlatList 的生命周期内不会被卸载。 根据实际情况设置合适的初始渲染数量,避免过多或过少。
windowSize 定义需要渲染列表项的窗口区域。 调整窗口大小,平衡渲染性能和用户体验。
maxToRenderPerBatch updateCellsBatchingPeriod 控制渲染批次的大小和频率。小批次和较长的批处理周期可以提供更好的TTI(首次交互时间),而大批次和较短的批处理周期可以避免空白区域。 根据具体场景进行权衡,找到最佳的参数组合。

1.3 案例研究:应用基本启发式优化

1.3.1 添加键

为列表项添加唯一的键可以避免React发出警告。可以使用 FlatList keyExtractor 属性,将 feed ID 作为键。示例代码如下:

<FlatList
  ...
  keyExtractor={(item) => item.feed.id}
  ...
/>

这样可以消除警告信息:

ManyFaces[19949:1694125] [javascript] Warning: Each child in a list should have a unique "key" prop.
Check the render method of `VirtualizedList`. See https://fb.me/react-warning-keys for more information.
1.3.2 实现 shouldComponentUpdate

为所有 Feeds 组件实现 shouldComponentUpdate 方法,避免不必要的差异算法运行。由于 Feeds 组件被高阶组件(HOC)封装,无法使用纯组件。可以通过比较 item 属性来确定是否需要更新组件。示例代码如下:

export default function withMetaAndControls(Feed) {
  class ElemComponent extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
      if (nextProps.item === this.props.item) { 
        return false;
      }
      return true; 
    }
    render() {
      return (
        ...
      );
    }
    like = () => {
      this.props.like(this.props.feedIndex);
    }
    ...
  }
}

通过这种方式,只有当传入的 item 属性发生变化时,才会更新 Feeds 组件,提高性能。

2. 零崩溃:设计异常处理流程

2.1 异常处理的重要性

异常处理是系统设计中至关重要的一部分。“快速失败”原则要求我们尽早发现并处理潜在的异常,避免应用在用户面前崩溃。我们需要明确异常处理的目标:
- 所有异常都应被正确导向预期的位置,避免异常被抛到调用栈的任意上层或未被捕获而导致应用崩溃。
- 可恢复的异常应被静默处理,对用户透明。
- 不可恢复的异常应定义明确的行为和UI展示,向用户表达歉意。
- 记录所有意外的逻辑流程,包括可恢复和不可恢复的异常。
- 了解应用在实际环境中崩溃时的具体情况,以便高效修复并准确报告问题。

2.2 异常的分类及处理原则

分类方式 类别 处理原则
严重程度 可恢复异常 应用“静默日志”技术,记录异常信息,同时保持用户体验的连续性。
不可恢复异常 将异常一直抛到UI层,向用户提供反馈,如显示“出错了”页面、弹出提示框或重置状态并返回登录界面。
可控性 可控异常 记录所有必要的信息。
不可控异常 尽最大努力将其转换为可控异常,通过细致的日志记录来实现。
来源 外部异常 由于不完全受客户端逻辑控制,可引入“重试”逻辑来缓解问题。
内部异常 通常不进行重试。
范围 全局异常 只能由全局处理程序处理,如BAD_ACCESS和段错误。
局部异常 可以在局部的 catch 块或错误边界中捕获。

2.3 JavaScript的优势

与大多数其他编程语言(如C++和Objective-C)不同,JavaScript可以将所有异常局部化。这意味着所有异常,包括空指针异常,都可以使用 catch 块或错误边界以异常的形式捕获。为了使应用保持零崩溃的最低标准,可以采取以下措施:
- 始终应用顶级错误边界,捕获内部抛出的异常。
- 始终在方法调用的入口点应用顶级 catch 块,并将异常重定向到错误边界。

2.4 软件架构中的健壮性设计

2.4.1 入口点

在React Native中,有两个接收和处理外部数据的入口点:从服务器端接收数据和从原生层到JavaScript层的数据传递。在处理这些数据时,需要进行空值检查、优雅地回退并在必要时抛出异常。具体处理方式如下:
- 可选字段为空 :这是一种逻辑流程,而非异常流程,因为没有违反协议。可以为字段提供默认值作为回退。
- 必填字段为空 :协议被破坏,设置默认值并记录日志,秘密恢复以不打扰用户。
- 关键字段为空 :不仅协议被破坏,而且缺少的字段属于关键路径,用户体验无法继续。在这种情况下,应立即抛出异常,由UI层处理。

2.4.2 崩溃点

需要特别注意空指针异常和越界异常。这些异常可能发生在组件、全局函数和原生层中。可以使用错误边界来处理组件中的异常,将异步异常引导到错误边界,以及将原生异常引导到错误边界。对于全局函数,应避免将关键路径放在其中,所有异常应在内部被捕获和处理。

2.5 最后的手段:全局错误处理程序

即使有完善的防御代码,异常仍可能发生。这些异常应被视为失败,只能由全局错误处理程序处理。主要有以下三种全局错误处理方式:
- 包裹应用根组件的错误边界,显示“出错了”页面。
- 安装 RCTExceptionManager 并绑定委托。
- 使用 react-native-exception-handler

这些全局处理程序可以用于进行崩溃报告,提供来自实际环境的宝贵信息。但最好在开发和测试阶段尽早发现并解决这些问题。

2.6 总结

开发一个零崩溃的应用是一个重要的目标。优秀的开发者应具备深入研究生产阶段问题的意愿和能力,以及提前设计系统以提高其弹性和在灾难发生时提供尽可能多信息的能力。异常处理体验是用户体验的重要组成部分,需要与产品团队密切合作,制定出良好的计划。

3. 深入了解原生模块

3.1 原生模块的目的

原生模块的目的是导出原生功能。通过JavaScriptCore等引擎,可以将原生编写的函数注入到JavaScript运行时,供JavaScript代码直接使用。原生方法的导出基于这种能力,并进行了一些调整。

3.2 调用方式和性能优化

过去,调用原生模块时,参数在跨越桥接时需要进行序列化和反序列化,这会带来性能损失。基于JSI的优化消除了这种序列化和反序列化的需求,提高了性能。

3.3 初始化流程

原生模块的初始化流程大致分为三个阶段:预启动、启动和JavaScript层的初始化。以下是初始化流程的mermaid流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([Phase 0: Prior Bootstrap]):::startend --> B(RCT_EXTERN_MODULE):::process
    B --> C(RCT_EXTERN_REMAP_MODULE):::process
    C --> D(RCT_EXPORT_MODULE_NO_LOAD):::process
    D --> E(RCTRegisterModule):::process
    E --> F([Phase 1: Bootstrap]):::startend
    F --> G(@RCTBridge):::process
    G --> H(@RCTBridgeModule):::process
    H --> I(setUp):::process
    I --> J(@RCTBridge):::process
    J --> K(application(_:didFinishLaunchingWithOptions:)):::process
    K --> L(@AppDelegate):::process
    L --> M(initWithDelegate:bundleURL:moduleProvider:launchOptions:):::process
    M --> N(RCTBridge Delegate):::process
    N --> O(sourceURLForBridge):::process
    O --> P(start):::process
    P --> Q(@RCTCxxBridge):::process
    Q --> R(registerExtraModules):::process
    R --> S(extraModulesForBridge):::process
    S --> T(RCTGetModuleClasses):::process
    T --> U(JSCExecutorFactory()):::process
    U --> V(loadSource):::process
    V --> W(prepareBridge):::process
    W --> X([Phase 2: Initialization on JavaScript layer]):::startend

3.4 阶段0:预启动

原生模块的初始化从 RCT_EXTERN_MODULE 宏开始,该宏依次应用 RCT_EXTERN_REMAP_MODULE RCT_EXPORT_MODULE_NO_LOAD 宏。这些宏最终会在早期阶段将模块类填充到 RCTModuleClasses 中,用于后续实例化原生模块实例。具体代码如下:

#define RCT_EXTERN_MODULE(objc_name, objc_supername)  
RCT_EXTERN_REMAP_MODULE(, objc_name, objc_supername)

#define RCT_EXTERN_REMAP_MODULE( 
  js_name, objc_name, objc_supername)               
  objc_name:                                         
  objc_supername @                                   
  end @interface objc_name(RCTExternModule)<RCTBridgeModule> 
  @end                                              
  @implementation objc_name (RCTExternModule)        
  RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name) 

#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name)
  RCT_EXTERN void RCTRegisterModule(Class);          
  +(NSString *)moduleName                            
  {                                                 
      return @ #js_name;                             
  }                                                 
  __attribute__((constructor))
  static void RCT_CONCAT(initialize_,
                         objc_name)()                
  {                                                 
      RCTRegisterModule([objc_name class]);          
  }
  • 定义 @interface 使用传入的模块名称。
  • 紧接着定义 @implementation
  • 实现 moduleName 方法,这与Android原生模块实现中的 getName() 方法等效。
  • 使用 __attribute__((constructor)) 在其他操作之前进行模块注册。
  • 调用 RCTRegisterModule 注册模块。

RCT_EXTERN_MODULE 主要用于基于Swift的原生模块,因为Swift中不允许使用 load() 方法。对于普通的Objective-C,可以使用更简单的 RCT_EXPORT_MODULE 来实现相同的功能。

3.5 RCTRegisterModule 的实现

void RCTRegisterModule(Class moduleClass)
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    // 具体实现
  });
}

通过 dispatch_once 确保模块只注册一次。

综上所述,通过对FlatList的性能优化、异常处理流程的设计以及对原生模块的深入了解,可以提高React Native应用的性能和稳定性,为用户提供更好的体验。

4. 原生模块初始化流程详解

4.1 阶段0具体步骤分析

在阶段0(Prior Bootstrap)中,各个宏的使用和模块注册过程有着明确的逻辑。以下是对每个步骤的详细分析:
1. RCT_EXTERN_MODULE :作为起始宏,它调用了 RCT_EXTERN_REMAP_MODULE ,为后续的模块定义和注册做准备。
2. RCT_EXTERN_REMAP_MODULE :此宏定义了 @interface ,使用传入的模块名称,同时紧接着定义了 @implementation ,为模块的实现提供了基础。
3. RCT_EXPORT_MODULE_NO_LOAD :该宏实现了 moduleName 方法,这与Android原生模块实现中的 getName() 方法等效。并且使用 __attribute__((constructor)) 确保模块在其他操作之前进行注册,然后调用 RCTRegisterModule 完成模块的注册。

4.2 阶段1:启动阶段

在阶段1(Bootstrap)中,涉及到多个类和方法的调用,具体流程如下:
| 步骤 | 操作 | 说明 |
| ---- | ---- | ---- |
| 1 | @RCTBridge | 开始桥接相关操作 |
| 2 | @RCTBridgeModule | 模块相关设置 |
| 3 | setUp | 进行初始化设置 |
| 4 | application(_:didFinishLaunchingWithOptions:) | 应用启动相关处理 |
| 5 | @AppDelegate | 应用委托处理 |
| 6 | initWithDelegate:bundleURL:moduleProvider:launchOptions: | 初始化桥接委托等参数 |
| 7 | RCTBridge Delegate | 桥接委托相关操作 |
| 8 | sourceURLForBridge | 获取桥接的源URL |
| 9 | start | 启动桥接 |
| 10 | @RCTCxxBridge | C++桥接相关操作 |
| 11 | registerExtraModules | 注册额外模块 |
| 12 | extraModulesForBridge | 获取桥接的额外模块 |
| 13 | RCTGetModuleClasses | 获取模块类 |
| 14 | JSCExecutorFactory() | 创建JavaScriptCore执行器工厂 |
| 15 | loadSource | 加载源文件 |
| 16 | prepareBridge | 准备桥接 |

4.3 阶段2:JavaScript层初始化

阶段2(Initialization on JavaScript layer)是原生模块初始化流程的最后一个阶段,在这个阶段,JavaScript层开始进行初始化操作,与前面的阶段共同构成了完整的原生模块初始化过程。

5. 总结与实践建议

5.1 性能优化总结

通过对FlatList的性能优化,我们可以从基础优化和高级优化两个方面入手。基础优化包括使用 getItemLayout 预计算列表项高度和使用图像缓存;高级优化则通过调整 initialNumToRender windowSize maxToRenderPerBatch updateCellsBatchingPeriod 等参数来平衡渲染性能和用户体验。同时,为列表项添加键和实现 shouldComponentUpdate 方法也能有效提高性能。

5.2 异常处理总结

异常处理是保证应用稳定性的关键。我们需要明确异常处理的目标,对异常进行分类并遵循相应的处理原则。在软件架构中,要关注入口点和崩溃点的处理,同时使用全局错误处理程序作为最后的防线。JavaScript的优势使得我们可以将所有异常局部化,进一步提高应用的健壮性。

5.3 原生模块总结

原生模块的初始化流程分为预启动、启动和JavaScript层初始化三个阶段。通过宏的使用和模块注册,将原生功能导出供JavaScript代码使用。基于JSI的优化消除了参数序列化和反序列化的性能损失,提高了调用效率。

5.4 实践建议

为了更好地应用这些技术,以下是一些实践建议:
1. 性能优化实践 :在实际开发中,根据列表的实际情况调整高级优化参数,通过测试找到最佳的参数组合。同时,合理使用图像缓存,避免重复下载和加载。
2. 异常处理实践 :在入口点进行严格的空值检查和错误处理,对于不同类型的异常,按照处理原则进行处理。在开发和测试阶段,使用全局错误处理程序进行崩溃报告,及时发现并解决问题。
3. 原生模块实践 :理解原生模块的初始化流程,根据不同的开发语言选择合适的宏进行模块定义和注册。在调用原生模块时,利用JSI优化提高性能。

总之,通过对FlatList性能优化、异常处理和原生模块的深入了解和实践,可以提高React Native应用的性能和稳定性,为用户提供更加流畅和可靠的体验。

内容概要:本文介绍了ENVI Deep Learning V1.0的操作教程,重点讲解了如何利用ENVI软件进行深度学习模型的训练应用,以实现遥感图像中特定目标(如集装箱)的自动提取。教程涵盖了从数据准备、标签图像创建、模型初始化训练,到执行分类及结果优化的完整流程,并介绍了精度评价通过ENVI Modeler实现一键化建模的方法。系统基于TensorFlow框架,采用ENVINet5(U-Net变体)架构,支持通过点、线、面ROI或分类图生成标签数据,适用于多/高光谱影像的单一类别特征提取。; 适合人群:具备遥感图像处理基础,熟悉ENVI软件操作,从事地理信息、测绘、环境监测等相关领域的技术人员或研究人员,尤其是希望将深度学习技术应用于遥感目标识别的初学者实践者。; 使用场景及目标:①在遥感影像中自动识别和提取特定地物目标(如车辆、建筑、道路、集装箱等);②掌握ENVI环境下深度学习模型的训练流程关键参数设置(如Patch Size、Epochs、Class Weight等);③通过模型调优结果反馈提升分类精度,实现高效自动化信息提取。; 阅读建议:建议结合实际遥感项目边学边练,重点关注标签数据制作、模型参数配置结果后处理环节,充分利用ENVI Modeler进行自动化建模参数优化,同时注意软硬件环境(特别是NVIDIA GPU)的配置要求以保障训练效率。
内容概要:本文系统阐述了企业新闻发稿在生成式引擎优化(GEO)时代下的渠道策略效果评估体系,涵盖当前企业传播面临的预算、资源、内容效果评估四大挑战,并深入分析2025年新闻发稿行业五大趋势,包括AI驱动的智能化转型、精准化传播、首发内容价值提升、内容资产化及数据可视化。文章重点解析央媒、地方官媒、综合门户和自媒体四类媒体资源的特性、传播优势发稿策略,提出基于内容适配性、时间节奏、话题设计的策略制定方法,并构建涵盖品牌价值、销售转化GEO优化的多维评估框架。此外,结合“传声港”工具实操指南,提供AI智能投放、效果监测、自媒体管理舆情应对的流程解决方案,并针对科技、消费、B2B、区域品牌四大行业推出定制化发稿方案。; 适合人群:企业市场/公关负责人、品牌传播管理者、数字营销从业者及中小企业决策者,具备一定媒体传播经验并希望提升发稿效率ROI的专业人士。; 使用场景及目标:①制定科学的新闻发稿策略,实现从“流量思维”向“价值思维”转型;②构建央媒定调、门户扩散、自媒体互动的立体化传播矩阵;③利用AI工具实现精准投放GEO优化,提升品牌在AI搜索中的权威性可见性;④通过数据驱动评估体系量化品牌影响力销售转化效果。; 阅读建议:建议结合文中提供的实操清单、案例分析工具指南进行系统学习,重点关注媒体适配性策略GEO评估指标,在实际发稿中分阶段试点“AI+渠道”组合策略,并定期复盘优化,以实现品牌传播的长期复利效应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值