• (一)前言 今天我们一起来看一下WebView组件讲解以及使用实例 刚创建的React Native技术交流群(282693535),欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩...

    转载请标明出处:

    http://blog.csdn.net/developer_jiangqq/article/details/50676379

    本文出自:【江清清的博客】

    ()前言

             【好消息】个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org       

            今天我们一起来看一下WebView组件讲解以及使用实例

              刚创建的React Native技术交流3群(496508742),React Native技术交流4群(458982758),请不要重复加群!,欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!

              WebView组件进行创建渲染一个原生的WebView,进行加载一个网页。

    ()属性方法

    1. 承可以使View组件所有属性和Style(具体查看:http://facebook.github.io/react-native/docs/view.html#content http://facebook.github.io/react-native/docs/view.html#style)
    2. automaticallyAdjustContentInsets bool   设置是否自动调整内容
    3. contentInset  {top:number,left:number,bottom:number,right:number}  设置内容所占的尺寸大小
    4. html  string  WebView加载的HTML文本字符串
    5. injectJavaScript  string 当网页加载之前进行注入一段js代码
    6. onError function  方法 当网页加载失败的时候调用
    7. onLoad  function 方法  当网页加载结束的时候调用
    8. onLoadEnd fucntion 当网页加载结束调用,不管是成功还是失败
    9. onLoadStart  function  当网页开始加载的时候调用
    10. onNavigationStateChange function方法  当导航状态发生变化的时候调用
    11. renderError  function  该方法用于渲染一个View视图用来显示错误信息
    12. renderLoagin function  该方法用于渲染一个View视图用来显示一个加载进度指示器
    13. startInLoadingState  bool  
    14. url  string  设置加载的网页地址
    15. allowsInlineMediaPlayback  bool   该适合iOS平台,设置决定当使用HTML5播放视频的时候在当前页面位置还是使用原生的全屏播放器播放,默认值false。【注意】.为了让视频在原网页位置进行播放,不光要设置该属性为true,还必须要设置HTML页面中video节点的包含webkit-playsinline属性
    16. bounces bool  该适合iOS平台 设置是否有界面反弹特性
    17. domStorageEnabled bool  该适合Android平台 该只适合于Android平台,用于控制是否开启DOM Storage(存储)
    18. javaScriptEnabled  bool  该适合于Android平台,是否开启javascript,在iOS中的WebView是默认开启的
    19. onShouldStartLoadWithRequest  function  该适合iOS平台,该允许拦截WebView加载的URL地址,进行自定义处理。该方法通过返回true或者falase来决定是否继续加载该拦截到请求
    20. scalesPageToFit  bool  该适合iOS平台  用于设置网页是否缩放自适应到整个屏幕视图以及用户是否可以改变缩放页面
    21. scrollEnabled  bool    该适合iOS平台 用于设置是否开启页面滚动

    ()实战实例

           上面我已经对于WebView组件的基本介绍以及相关属性方法做了讲解,下面我们用几个实例来演示一下WebView组件的使用。

           3.1.先演示一个WebView组件最基本的使用方法,直接加载一个网页,具体代码如下:

    'use strict';
    import React, {
      AppRegistry,
      Component,
      StyleSheet,
      Text,
      View,
      WebView,
    } from'react-native';
    var DEFAULT_URL = 'http://www.lcode.org';
     
    var WebViewDemo =React.createClass({
      render: function() {
        return (
          <View style={{flex:1}}>
            <Textstyle={{height:40}}>简单的网页显示</Text>
            <WebViewstyle={styles.webview_style}
              url={DEFAULT_URL}
              startInLoadingState={true}
              domStorageEnabled={true}
              javaScriptEnabled={true}
              >
            </WebView>
          </View>
        );
      },
    });
    var styles =StyleSheet.create({
        webview_style:{ 
           backgroundColor:'#00ff00',  
        }
    });
     
    AppRegistry.registerComponent('WebViewDemo',() => WebViewDemo);

    运行效果截图如下:


       3.2.WebView加载本地的HTML静态字符串,具体代码如下:

    'use strict';
    import React, {
      AppRegistry,
      Component,
      StyleSheet,
      Text,
      View,
      WebView,
    } from'react-native';
    var DEFAULT_URL = 'http://www.lcode.org';
    const HTML = `
    <!DOCTYPEhtml>\n
    <html>
      <head>
        <title>HTML字符串</title>
        <metahttp-equiv="content-type" content="text/html;charset=utf-8">
        <meta name="viewport"content="width=320, user-scalable=no">
        <style type="text/css">
          body {
            margin: 0;
            padding: 0;
            font: 62.5% arial, sans-serif;
            background: #ccc;
          }
          h1 {
            padding: 45px;
            margin: 0;
            text-align: center;
            color: #33f;
          }
        </style>
      </head>
      <body>
        <h1>加载静态的HTML文本信息</h1>
      </body>
    </html>
    `;
    var WebViewDemo =React.createClass({
      render: function() {
        return (
          <View style={{flex:1}}>
            <WebViewstyle={styles.webview_style}
              html={HTML}
              startInLoadingState={true}
              domStorageEnabled={true}
              javaScriptEnabled={true}
              >
            </WebView>
          </View>
        );
      },
    });
    var styles =StyleSheet.create({
        webview_style:{ 
           backgroundColor:'#00ff00',  
        }
    });
     
    AppRegistry.registerComponent('WebViewDemo',() => WebViewDemo);

     运行效果截图如下:


    ()最后总结

              今天我们主要学习一下WebView组件的基本介绍和实例演示使用,具体还有更加详细的使用方法会在后面进阶中继续更新的。大家有问题可以加一下群React Native技术交流群(282693535)或者底下进行回复一下。

           尊重原创,转载请注明:From Sky丶清(http://blog.csdn.net/developer_jiangqq) 侵权必究!

           关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

         关注我的微博,可以获得更多精彩内容

          

    展开全文
  • 源码传送门最近学习ReactNative感觉到挺有意思的,在学习的过程中,发现网上一些人写的文章内容过时了,这主要是ReactNative的版本升级太快,如果你现在看一篇16甚至15年写的文章,把知识点和官方文档对比下,会让你...

    源码传送门

    最近学习ReactNative感觉到挺有意思的,在学习的过程中,发现网上一些人写的文章内容过时了,这主要是ReactNative的版本升级太快,如果你现在看一篇16甚至15年写的文章,把知识点和官方文档对比下,会让你大跌眼镜。所以奉劝各位想学习ReactNative的同学,选择学习资料一定要以官方文档,和官方demo为准,其他资料为辅。

    Image组件

    在ReactNative中Image是用于显示图片的组件,和开发Android的时候ImageView控件相同的效果。它可以用来显示网络图片、静态资源、临时的本地图片、以及本地磁盘上的图片(如相册)等。恰当的使用Image组件能更形象更直观的向用户传达信息。

    Image组件加载项目中的静态资源

    在这里的静态资源指的是加载的js部分的图片,非android,ios原生应用下的资源文件,对于加载这种图片资源,我们通过require(‘图片文件相对本文件目录的的路径’)引入图片文件,并将其设置到Image组件的source属性即可。如下

                <Image
                    style={styles.image}
                    //   ./表示当前文件目录 ../ 父目录
                     source={require('./reactlogo.png')}
                />

    需要注意的一点是,上面require中不能用字符串拼接路径,否则会加载报错。

    加载原生图片资源

    在此所说的原生资源指的我们开发android的时候再res目录下的drawable,或者mipmap目录。以及ios下对应的资源目录。对于加载这种图片资源和加载项目中的资源有点不一样,此处以android为例,如下加载drawable下的文件

                <Image
                        source={{uri: 'launcher_icon'}}
                        style={{width: 38, height: 38}}
                    />);

    除了通过上面方式加载也可以通过下面方式

                <Image
                    source={nativeImageSource({
                        android: 'launcher_icon',
                        width: 96,
                        height: 96
                    })}
                />

    nativeImageSource中可以指定图片宽高,如果同时在image组件的样式属性style设置宽高的话,最终宽高是以style中宽高为准。在上面默认加载的是drawable下的图片资源,如果想加载mipmap中的资源,可以如下

                <Image
                    source={nativeImageSource({
                        android: 'mipmap/launcher_icon',
                        width: 96,
                        height: 96
                    })}
                />

    通过上面方式,我们就可以加载图片了,如果是新加到drawable下的图片需要重新编译运行,否则是不生效的。

    加载网络图片

                <Image
                    source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
                    style={{width: 38, height: 38}}
                />);

    对于加载网络图片需要注意的一点就是,需要指定样式的宽和高,否则图片将不显示(不设置默认宽和高为0了)。

    Image组件常用的属性

    • style:
      • width :设置图片的宽
      • height:设置图片的高
      • borderWidth:设置边框宽度
      • borderColor :设置边框颜色
      • backgroundColor:设置背景色(有些图片是透明背景时,一般会用到这个属性)
      • opacity:不透明度,值在0到1之间,1表示不透明,0表示透明。
      • tintColor :给图片着色,这个属性用处较多,如,一个黑白图片,常常会点击时变成其他颜色图片,此时可用此属性
    • blurRadius 设置图片的模糊半径,可模糊图片
    • defaultSource 给图片设置默认图片,用于加载网络成功之前显示的图片。(ios支持)
    • source
      在上面我们介绍了source属性加载不同的图片资源,但是还有一个没讲到,它可以接收一个数组作为参数,这样可根据组件的宽和高自动加载与之匹配的宽和高的图片。使用方式如下
            <Image
                    style={{flex: 1}}
                    source={[
                             {uri: 'https://facebook.github.io/react/img/logo_small.png', width: 38, height: 38},
                             {uri: 'https://facebook.github.io/react/img/logo_small_2x.png', width: 76, height: 76},          
                             {uri: 'https://facebook.github.io/react/img/logo_og.png', width: 400, height: 400}
                            ]}
                        />
    • resizeMode
      该属性用来设置图片的缩放模式,对应值如下

      • cover
        保持图片宽高比,直到宽度和高度都大于等于容器视图的尺寸(参考下图效果)
      • contain
        在保持图片宽高比的前提下缩放图片,直到宽度和高度都小于等于容器视图的尺寸
      • stretch
        拉伸图片且不维持宽高比,直到宽高都刚好填满容器
      • center 居中不拉伸
      • repeat
        重复平铺图片直到填满容器。图片会维持原始尺寸。(iOS)

    image.png

    在Android上支持GIF和WebP格式图片

    默认情况下Android是不支持GIF和WebP格式的。你需要在build.gradle文件中根据需要添加对应的依赖。

    dependencies {
      // If your app supports Android versions before Ice Cream Sandwich (API level 14)
      compile 'com.facebook.fresco:animated-base-support:1.0.1'
    
      // For animated GIF support
      compile 'com.facebook.fresco:animated-gif:1.0.1'
    
      // For WebP support, including animated WebP
      compile 'com.facebook.fresco:animated-webp:1.0.1'
      compile 'com.facebook.fresco:webpsupport:1.0.1'
    
      // For WebP support, without animations
      compile 'com.facebook.fresco:webpsupport:1.0.1'
    }

    如果你在使用GIF的同时还使用了ProGuard,那么需要在proguard-rules.pro中添加如下规则

    -keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl {
      public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory, com.facebook.imagepipeline.core.ExecutorSupplier);
    }

    ImageBackground

    该组件是Image组件的扩展,它支持嵌套组件。如在图片上显示一个文本,则可以通过如下实现

                <ImageBackground
                    style={{width: 100, height: 100, backgroundColor: 'transparent'}}
                    source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
                >
                    <Text style={styles.nestedText}>
                        React
                    </Text>
                </ImageBackground>

    实现效果图如下,一般的我们可以嵌套ActivityIndicator来提示用户图片正在加载,当加载完成隐藏此控件。
    image.png

    网络图片加载监听

    对于网络图片的加载,ReactNative提供了一些属性用于图片不同加载时期的监听。

    • onLoadStart
      图片开始加载时调用
    • onLoad
      图片加载完成时调用,此时图片加载成功
    • onLoadEnd
      加载结束后调用,与onLoad不同的是不论成功还是失败,此回调函数都会被执行。
      使用方法如下
                <Image
                    source={{uri:'https://facebook.github.io/react/img/logo_og.png'}}
                    style={[styles.base, {overflow: 'visible'}]}
                    onLoadStart={() => console.log('onLoadStart')}
                    onLoad={(event) => console.log('onLoad') }
                    onLoadEnd={() =>  console.log('onLoadEnd')}
                />

    对于iOS,还提供了加载进度的回调函数onProgress

    <Image
       style={styles.image}
       onProgress={(event) => {
          console.log('onProgress')
          this.setState({
            progress: Math.round(100 * event.nativeEvent.loaded / event.nativeEvent.total)
        })}}/>

    可以通过参数event.nativeEvent.loaded获取已经加载的大小,通过event.nativeEvent.total获取图片的总大小。
    不仅如此,ReactNative还提供了预加载图片函数prefetch(url: string),它可以将图片下载到磁盘缓存

    var prefetchTask = Image.prefetch('https://facebook.github.io/react/img/logo_og.png');
    prefetchTask.then(() => {
       //此处可设置状态,显示Image组件。此时组件会使用预加载的图片信息。而不用再次加载
        console.log('加载图片成功')
    }, error => {
        console.log('加载图片失败')
    })

    好了,今天就介绍到这里,文中若有错误的地方欢迎指正,再次感谢。文中一些示例源码,可前往GitHub在线预览,也可以下载项目学习其他组件。

    展开全文
  • 2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了。文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能优化的文章。 本文谈到的 React ...

    2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了。文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能优化的文章。

    本文谈到的 React Native 性能优化,还没到修改 React Native 源码那种地步,所以通用性很强,对大部分 RN 开发者来说都用得着。

    本文的内容,一部分是 React/RN/Android/iOS 官方推荐的优化建议,一部分是啃源码发现的优化点,还有一部分是可以解决一些性能瓶颈的优秀的开源框架。本文总结的内容你很少在网络上看到,所以看完后一定会有所收获。如果觉得写的不错,请不要吝啬你的赞,把这篇 1w 多字的文章分享出去,让更多的人看到。

    看文章前要明确一点,一些优化建议并不是对所有团队都适用的。有的团队把 React Native 当增强版网页使用,有的团队用 React Native 实现非核心功能,有的团队把 React Native 当核心架构,不同的定位需要不同的选型。对于这些场景,我在文中也会提一下,具体使用还需要各位开发者定夺。

     

    目录:

    • 一、减少 re-render
    • 二、减轻渲染压力
    • 三、图片优化那些事
    • 四、对象创建调用分离
    • 五、动画性能优化
    • 六、长列表性能优化
    • 七、React Native 性能优化用到的工具
    • 八、推荐阅读

     

    一、减少 re-render

    因为 React Native 也是 React 生态系统的一份子,所以很多 React 的优化技巧可以用到这里,所以文章刚开始先从大家最熟悉的地方开始。

    对于 React 来说,减少 re-render 可以说是收益最高的事情了。

    1️⃣ shouldComponentUpdate

    📄 文档
    https://react.docschina.org/docs/optimizing-performance.html#shouldcomponentupdate-in-actionsx

    简单式例:

    class Button extends React.Component {
      shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
          return true;
        }
        return false;
      }
    
      render() {
        return <button color={this.props.color} />;
      }
    }

    无论哪篇文章,谈到 React 性能优化,shouldComponentUpdate 一定是座上宾。

    我们通过这个 API,可以拿到前后状态的 state/props,然后手动检查状态是否发生了变更,再根据变更情况来决定组件是否需要重新渲染。

    🔗 官方文档对 shouldComponentUpdate 的作用原理和使用场景已经说的非常清晰了,我就没有必要搬运文章了。在实际项目中,阅文集团的 🔗 React Native 应用「元气阅读」也做了很好的示范,🔗 Twitter 的性能优化分享也做的图文并茂,可有很高的参考价值,对此感兴趣的同学可以点击跳转查看。

    在此我想提醒的是,shouldComponentUpdate 是强业务逻辑相关的,如果使用这个 API,你必须考虑和此组件相关的所有 props 和 state,如果有遗漏,就有可能出现数据和视图不统一的情况。所以使用的时候一定非常小心。

    在此我想提醒的是,shouldComponentUpdate 是强业务逻辑相关的,如果使用这个 API,你必须考虑和此组件相关的所有 props 和 state,如果有遗漏,就有可能出现数据和视图不统一的情况。所以使用的时候一定非常小心。

    2️⃣ React.memo

    📄 文档:https://react.docschina.org/docs/react-api.html#reactmemo

    React.memo 是 React v16.6 中引入的新功能,是一个专门针对 React 函数组件的高阶组件。

    默认情况下,它和 PureComponent 一样,都是进行浅比较,因为就是个高阶组件,在原有的组件上套一层就可以了:

    const MemoButton = React.memo(function Button(props) {
      return <button color={this.props.color} />;
    });

    如果想和 shouldComponentUpdate 一样,自定义比较过程,React.memo 还支持传入自定义比较函数:

    function Button(props) {
      return <button color={this.props.color} />;
    }
    function areEqual(prevProps, nextProps) {
      if (prevProps.color !== nextProps.color) {
          return false;
        }
      return true;
    }
    export default React.memo(MyComponent, areEqual);

    值得注意的是areEqual() 这个函数的返回值和 shouldComponentUpdate 正好相反,如果 props 相等,areEqual()返回的是 trueshouldComponentUpdate 却返回的是 false

    3️⃣ React.PureComponent

    📄 文档:https://react.docschina.org/docs/react-api.html#reactpurecomponent

    简单式例:

    class PureComponentButton extends React.PureComponent {
      render() {
        return <button color={this.props.color} />;
      }
    }

    和 shouldComponentUpdate 相对应,React 还有一个类似的组件 React.PureComponent,在组件更新前对 props 和 state 做一次浅比较。所以涉及数据嵌套层级过多时,比如说你 props 传入了一个两层嵌套的 Object,这时候 shouldComponentUpdate 就很为难了:我到底是更新呢还是不更新呢?

    考虑到上面的情况,我在项目中一般很少用 PureComponent虽然很简单易用,但是面对复杂逻辑时,反而不如利用 shouldComponentUpdate 手动管理简单粗暴。当然这个只是个人的开发习惯,社区上也有其他的解决方案:

    • 把组件细分为很小的子组件,然后统一用 PureComponent 进行渲染时机的管理
    • 使用 immutable 对象,再配合 PureComponent 进行数据比较(🔗 参考链接:有赞 React 优化
    • ......

    在这个问题上仁者见仁智者见智,在不影响功能的前提下,主要是看团队选型,只要提前约定好,其实在日常开发中工作量都是差不多的(毕竟不是每个页面都有必要进行性能优化)。

     

    二、减轻渲染压力

    React Native 的布局系统底层依赖的是 🔗 Yoga 这个跨平台布局库,将虚拟 DOM 映射到原生布局节点的。在 Web 开发中,99% 的情况下都是一个 Virtual DOM 对应一个真实 DOM 的,那么在 React Native 中也是一一对应的关系吗?我们写个简单的例子来探索一下。

    我们先用 JSX 写两个橙色底的卡片,除了卡片文字,第一个卡片还嵌套一个黄色 View,第二个卡片嵌套一个空 View:

    // 以下示例 code 只保留了核心结构和样式,领会精神即可
    render() {
      return (
        <View>
          <View style={{backgroundColor: 'orange'}}>
            <View style={{backgroundColor: 'yellow'}}>
              <Text>Card2</Text>
            </View>
          </View>
          <View style={{backgroundColor: 'orange'}}>
            <View>
              <Text>Card2</Text>
            </View>
          </View>
        </View>
      );
    };

    用 react-devtools 查看 React 嵌套层级时如下所示:

    从上图中可以看出,React 组件和代码写的结构还是一一对应的。

    我们再看看 React Native 渲染到原生视图后的嵌套层级(iOS 用 Debug View Hierarchay,Android 用 Layout Inspector):

    从上图可以看出,iOS 是一个 React 节点对应一个原生 View 节点的;Android 第二个卡片的空白 View 却不见了!

    如果我们翻一翻 React Native 的源码,就会发现 React Native Android UI 布局前,会对只有布局属性的 View(LAYOUT_ONLY_PROPS 源码)进行过滤,这样可以减少 View 节点和嵌套,对碎片化的 Android 更加友好。

    通过这个小小的例子我们可以看出,React 组件映射到原生 View 时,并不是一一对应的,我们了解了这些知识后,可以如何优化布局呢?

    1️⃣ 使用 React.Fragment 避免多层嵌套

    📄 React Fragments 文档:https://zh-hans.reactjs.org/docs/fragments.html

    我们先从最熟悉的地方讲起——React.Fragment。这个 API 可以让一个 React 组件返回多个节点,使用起来很简单:

    render() {
      return (
        <React.Fragment>
          <ChildA />
          <ChildB />
          <ChildC />
        </React.Fragment>
      );
    }
    
    // 或者使用 Fragment 短语法
    render() {
      return (
        <>
          <ChildA />
          <ChildB />
          <ChildC />
        </>
      );
    }

    Fragments 作用还是蛮明显的:避免你多写一层 View。用处还是很广的,比如说自己业务上封装的 React 组件,React Native 官方封装的组件(比如说 ScrollView or Touchable* 组件 ),活用这个属性,可以减少你的 View 嵌套层级。

    2️⃣ 减少 GPU 过度绘制

    我们在业务开发时,经常会遇到这种场景:整个界面的背景色是白色的,上面又加了一个白色背景的卡片组件,卡片内部又包含了一个白色背景的小组件......

    // 以下示例 code 只保留了核心结构和样式,领会精神即可
    render() {
      return (
        <View>
          <View style={{backgroundColor: 'white'}}>
            <View style={{backgroundColor: 'white'}}>
              <Text style={{backgroundColor: 'white'}}>Card1</Text>
            </View>
          </View>
          <View>
            <View>
              <Text>Card2</Text>
            </View>
          </View>
        </View>
      );
    };

    首先我们要明确一点,屏幕上的每个像素点的颜色,是由多个图层的颜色决定的,GPU 会渲染这些图层混合后的最终颜色,但是,iOS 和 Android 的 GPU 渲染机制是不一致的。

    虽然上面的代码最后的的渲染结果在显示上都是白色的,但是 GPU 的优化是不一样的。我们用 iOS 的 Color Blended Layers 和 Android 的🔗 GPU 过度绘制调试工具查看最后的渲染结果:

    对于 iOS 来说,出现红色区域,就说明出现了颜色混合:

    • Card1 的几个 View 都设置了非透明背景色,GPU 获取到顶层的颜色后,就不再计算下层的颜色了
    • Card2 的 Text View 背景色是透明的,所以 GPU 还要获取下一层的颜色进行混合

    对于 Android 来说,GPU 会多此一举地渲染对用户不可见的像素。有一个颜色指示条:白 -> 蓝 -> 绿 -> 粉 -> 红,颜色越往后表示过度绘制越严重。

    • Card1 的几个 View 都设置了非透明背景色,红色表示起码发生了 4 次过度绘制
    • Card2 只有文字发生了过度绘制

    在过渡绘制这个测试上,iOS 和 Android 的实验结果几乎是完全相反的,所以解决方案肯定不是两全其美的,我个人认为,React Native 开发做视图优化时,应该优先优化 Android,所以我们可以从以下几点优化:

    • 减少背景色的重复设置:每个 View 都设置背景色的话,在 Android 上会造成非常严重的过度绘制;并且只有布局属性时,React Native 还会减少 Android 的布局嵌套
    • 避免设置半透明颜色:半透明色区域 iOS Android 都会引起过度绘制
    • 避免设置圆角:圆角部位 iOS Android 都会引起过度绘制
    • 避免设置阴影:阴影区域 iOS Android 都会引起过度绘制
    • ......

    避免 GPU 过度绘制的细节太多了,一般页面不需要这种精细化管理,长列表优化时可以考虑一下这个方向。

     

    三、图片优化那些事

    性能优化的另一个大头就是图片。这里的图片优化不仅仅指减少图片大小,减少 HTTP 带宽占用,我会更多的讨论一些 Image 组件上的优化,比如说缓存控制,图片采样等技术。

    1️⃣ Image 组件的优化项

    React Native 的 Image 图片组件,如果只是作为普通的图片展示组件,那它该有的都有了,比如说:

    • 加载本地/网络图片
    • 自动匹配 @2x/@3x 图片
    • 图片加载事件:onLoadStart/onLoad/onLoadEnd/onError
    • loading 默认图 or loading 指示器
    • ......

    但是,如果你要把它当一个图片下载管理库用时,就会非常的难受,因为 Image 的这几个属性在 iOS/Android 上有不同的表现,有的实现了有的没有实现,用起来非常不顺手。

    在讲解图片优化前,我们先想一下,一个基本的图片下载管理库要实现什么:

    1. 图片类型:首先你的主要职责是加载图片,你起码能加载多种图片类型
    2. 下载管理:在加载多张图片的场景,能管理好多个请求,可以控制图片加载的优先级
    3. 缓存管理:做好三级缓存,不能每个图片都要请求网络,均衡好内存缓存和磁盘缓存的策略
    4. 多图加载:大量图片同时渲染时,如何让图片迅速加载,减少卡顿

    针对上面的 4 条原则,我们来一一刨析 Image 组件。

    1.图片类型

    基础的 png/jpg/base64/gif 格式,支持良好。不过要注意的是,想要 Android 加载的 gif 图片动起来,要在 build.gradle 里面加一些依赖,具体内容可以看这个 🔗 链接

    如果要加载 webp 格式的图片,就有些问题了。作为 Google 推出的一种图片格式,Android 自然是支持的,但是 iOS 就不支持了,需要我们安装一些第三方插件。

    2.下载管理

    先说结论,Image 组件对图片的下载管理能力基本为 0。

    Image基本上只能监听单张图片的加载流程:onLoadStart/onLoad/onLoadEnd/onError,如果要控制多张图片的下载优先级,对不起,没有。

    3.缓存管理

    缓存这里要从两方面说,一是通过 HTTP 头信息管理缓存,二是直接通过一些组件属性管理缓存。

    Image 组件请求网络图片时,其实是可以加 HTTP header 头信息的,这样就可以利用 HTTP 缓存来管理图片,写法如下面代码所示:

    <Image
      source={{
        uri: 'https://facebook.github.io/react/logo-og.png',
        method: 'POST',
        headers: {
          Pragma: 'no-cache',
        },
        body: 'Your Body goes here',
      }}
      style={{width: 400, height: 400}}
    />

    具体的控制参数可以参考 🔗 MDN HTTP 缓存,这里就不细说了。

    直接通过属性控制图片缓存,iOS 有。Android?对不起,没有。

    iOS 可以通过 source 参数里的 cache 字段控制缓存,属性也是非常常见的那几种:默认/不使用缓存/强缓存/只使用缓存。具体的使用可以看 🔗 iOS Image 缓存文档

    4.多图加载

    都快到 5G 时代了,短视频/VLog 大家都天天刷了,更不用说多图场景了,基本上已经是互联网应用的标配了。

    讲图片加载前先明确一个概念:图片文件大小 != 图片加载到内存后的大小

    我们常说的 jpg png webp,都是原图压缩后的文件,利于磁盘存储和网络传播,但是在屏幕上展示出来时,就要恢复为原始尺寸了。

    React Native 性能优化——图片内存优化

    比如说一张 100x100 的 jpg 图片,可能磁盘空间就几 kb,不考虑分辨率等问题,加载到内存里,就要占用 3.66 Mb。

    // 不同的分辨率/文件夹/编码格式,都会带来数值差异
    // 下面的计算只是最一般的场景,领会精神即可
    
    (100 * 100 * 3) / (8 * 1024) = 3.66 Mb
    (长 * 宽 * 每个像素占用字节数) / (8 * 1024) = 3.66 Mb

    上面只是 100x100 的图片,如果图片尺寸增加一倍,图片在内存里的大小是按平方倍数增长的,数量一多后,内存占用还是很恐怖的。

    在多图加载的场景里,经过实践,iOS 不管怎么折腾,表现都比较好,但是 Android 就容易出幺蛾子。下面我们就详细说说 Android 端如何优化图片。

    在一些场景里,Android 会内存爆涨,帧率直接降为个位数。这种场景往往是小尺寸 Image 容器加载了特别大的图片,比如说 100x100 的容器加载 1000x1000 的图片,内存爆炸的原因就是上面说的原因。

    那么这种问题怎么解决呢?Image 有个 resizeMethod 属性,就是解决 Android 图片内存暴涨的问题。当图片实际尺寸和容器样式尺寸不一致时,决定以怎样的策略来调整图片的尺寸。

    • resize小容器加载大图的场景就应该用这个属性。原理是在图片解码之前,会用算法对其在内存中的数据进行修改,一般图片大小大概会缩减为原图的 1/8。
    • scale:不改变图片字节大小,通过缩放来修改图片宽高。因为有硬件加速,所以加载速度会更快一些。
    • auto:文档上说是通过启发式算法自动切换 resize 和 scale 属性。这个启发式算法非常误导人,第一眼看上去还以为是会对比容器尺寸和图片尺寸采用不同策略。但我看了一下源码,它只是单纯的判断图片路径,如果是本地图片,就会用 resize,其他都是 scale 属性,所以 http 图片都是 scale 的,我们还得根据具体场景手动控制。

    顺便提一下,Android 图片加载的时候,还会有一个 easy-in 的 300ms 加载动画效果,看上去会觉得图片加载变慢了,我们可以通过设置 fadeDuration 属性为 0,来关闭这个加载动画。

    2️⃣ 优先使用 32 位色彩深度的图片

    📄 色彩深度 wiki:https://github.com/DylanVann/react-native-fast-image/blob/master/README.md

    色彩深度这个概念其实前面也提了一下,比如说我们常用的带透明度 PNG 图片,就是 32 位的:

    • R:红色,占据 8 bit
    • G:绿色,占据 8 bit
    • B:蓝色,占据 8 bit
    • A:透明通道,占据 8 bit

    为啥推荐使用 32 bit 图片呢?直接原因有 2 个:

    1. Android 推荐使用 🔗 ARGB_8888 格式的图片,因为这种图片显示效果更好
    2. iOS GPU 只支持加载 32 bit 的图片。如果是其他格式的(比如说 24 bit 的 jpg),会先在 CPU 里转为 32 bit,再传给 GPU

     

    虽然推荐 32 bit 图片,但是说实话,这个对前端开发是不可控的,因为图片来源一般就 2 个:

    1. 设计师的切图,由设计师控制
    2. 网络上的图片,由上传者控制

    所以想针对这一点进行优化的话,沟通成本挺高,收益反而不高(一般只在长列表有些问题),但也是图片优化的一个思路,故放在这一节里。

    3️⃣ Image 和 ImageView 长宽保持一致

    前面举了一个 100x100 的 ImageView 加载 1000x1000 Image 导致 Android 内存 OOM 的问题,我们提出了设置 resizeMethod={'resize'} 的方法来缩减图片在内存中的体积。其实这是一种无奈之举,如果可以控制加载图片的大小,我们应该保持 Image 和 ImageView 长宽一致。

    首先我们看看长宽不一致会引起的问题:

    • Image 小于 ImageView:图片不清晰,表情包电子包浆质感
    • Image 大于 ImageView:浪费内存,有可能会引起 OOM
    • 尺寸不一致会带来抗锯齿计算,增加了图形处理负担

    React Native 开发时,布局使用的单位是 pt,和 px 存在一个倍数关系。在加载网络图片时,我们可以使用 React Native 的 🔗 PixelRatio.getPixelSizeForLayoutSize 方法,根据不同的分辨率加载不同尺寸的图片,保证 Image 和 ImageView 长宽一致。

    4️⃣ 使用 react-native-fast-image

    📄 react-native-fast-image 文档:https://github.com/DylanVann/react-native-fast-image/blob/master/README.md

    经过上面的几个 Image 属性分析,综合来看,Image 组件对图片的管理能力还是比较弱的,社区上有个 Image 组件的替代品:react-native-fast-image

    它的底层用的是 🔗 iOS 的 SDWebImage 和 🔗 Android 的 Glide。这两个明星图片下载管理库,原生开发同学肯定很熟悉,在缓存管理,加载优先级和内存优化上都有不错的表现。而且这些属性都是双平台可用,这个库都封装好了,但是官网上只有基础功能的安装和配置,如果想引入一些功能(比如说支持 WebP),还是需要查看 SDWebImage 和 Glide 的文档的。

    引入前我还是想提醒一下,React Native 的 Android Image 组件底层封装了 FaceBook 的 Fresco,引入这个库相当于又引入了 Glide,包体积不可避免的会变大,所以引入之前可能还要均衡一下。

    5️⃣ 图片服务器辅助

    前面说的都是从 React Native 侧优化图片,但是一个产品从来不是单打独斗,借助服务端的力量其实可以省很多事。

    1.使用 WebP

    WebP 的优势不用我多说,同样的视觉效果,图片体积会明显减少。而且可以显著减小 CodePush 热更新包的体积(热更新包里,图片占用 90% 以上的体积)。

    虽然 WebP 在前端解压耗时可能会多一点点,但是考虑到传输体积缩小会缩短网络下载时间,整体的收益还是不错的。

    2.图床定制图片

    一般比较大的企业都有内建图床和 CDN 服务,会提供一些自定制图片的功能,比如说指定图片宽高,控制图片质量。当然一些比较优秀的第三方对象存储也提供这些功能,比如说🔗 七牛云 图片处理

    借用云端图片定制功能,前端可以轻松通过控制 URL 参数控制图片属性

    比如说 Android 通过 resizeMethod 的 resize 更改图片字节大小,虽然也可以解决问题,但是这个算法还是在前端运行的,还是会占用用户内存资源。我们把链接改成:

    https://www.imagescloud.com/image.jpg/0/w/100/h/100/q/80
    // w: 长为 100 px
    // h: 宽最多为 100 px
    // q: 压缩质量为 80

    这样子就可以把计算转移到服务端,减少前端的 CPU 占用,优化前端整体的性能。

     

    四、对象创建调用分离

    对象创建和调用分离,其实更多的是一种编码习惯。

    我们知道在 JavaScript 里,啥都是对象,而在 JS 引擎里,创建一个对象的时间差不多是调用一个已存在对象的 10 多倍。在绝大部分情况下,这点儿性能消耗和时间消耗根本不值一提。但在这里还是要总结一下,因为这个思维习惯还是很重要的。

    1️⃣ public class fields 语法绑定回调函数

    📄 文档:https://zh-hans.reactjs.org/docs/handling-events.html

    作为一个前端应用,除了渲染界面,另一个重要的事情就是处理用户交互,监听各种事件。所以在组件上绑定各种处理事件也是一个优化点。

    在 React 上如何处理事件已经是个非常经典的话题了,我搜索了一下,从 React 刚出来时就有这种文章了,动不动就是四五种处理方案,再加上新出的 Hooks,又能玩出更多花样了。

    最常见的绑定方式应该是直接通过箭头函数处理事件:

    class Button extends React.Component {
      handleClick() {
        console.log('this is:', this);
      }
    
      render() {
        return <button onClick={(e) => this.handleClick(e)}>Click me</button>;
      }
    }

    但这种语法的问题是每次 Button 组件重新渲染时,都会创建一个 handleClick() 函数,当 re-render 的次数比较多时,会对 JS 引擎造成一定的垃圾回收压力,会引起一定的性能问题。

    🔗 官方文档里比较推荐开发者使用 🔗 public class fields 语法 来处理回调函数,这样的话一个函数只会创建一次,组件 re-render 时不会再次创建:

    class Button extends React.Component {
      // 此语法确保 handleClick 内的 this 已被绑定。
      handleClick = () => {
        console.log('this is:', this);
      }
    
      render() {
        return <button onClick={this.handleClick}>Click me</button>;
      }
    }

    在实际开发中,经过一些数据对比,因绑定事件方式的不同引起的性能消耗基本上是可以忽略不计的,re-render 次数过多才是性能杀手。但我认为这个意识还是有的,毕竟从逻辑上来讲,re-render 一次就要创建一个新的函数是真的没必要。

    2️⃣ public class fields 语法绑定渲染函数

    这个其实和第一个差不多,只不过把事件回调函数改成渲染函数,在 React Native 的 Flatlist 中很常见。

    很多新人使用 Flatlist 时,会直接向 renderItem 传入匿名函数,这样每次调用 render 函数时都会创建新的匿名函数:

    render(){
      <FlatList
        data={items}
        renderItem={({ item }) => <Text>{item.title}</Text>}
      />
    }

    改成 public class fields 式的函数时,就可以避免这个现象了:

    renderItem = ({ item }) => <Text>{item.title}</Text>;
    
    render(){
      <FlatList
        data={items}
        renderItem={renderItem}
      />
    }

    同样的道理,ListHeaderComponent 和 ListFooterComponent 也应该用这样写法,预先传入已经渲染好的 Element,避免 re-render 时重新生成渲染函数,造成组件内部图片重新加载出现的闪烁现象。

    3️⃣ StyleSheet.create 替代 StyleSheet.flatten

    📄 文档:https://reactnative.cn/docs/stylesheet/

    StyleSheet.create 这个函数,会把传入的 Object 转为优化后的 StyleID,在内存占用和 Bridge 通信上会有些优化。

    const styles = StyleSheet.create({
      item: {
        color: 'white',
      },
    });
    
    console.log(styles.item) // 打印出的是一个整数 ID

    在业务开发时,我们经常会抽出一些公用 UI 组件,然后传入不同的参数,让 UI 组件展示不一样的样式。

    为了 UI 样式的灵活性,我们一般会使用 StyleSheet.flatten,把通过 props 传入自定义样式和默认样式合并为一个样式对象:

    const styles = StyleSheet.create({
      item: {
        color: 'white',
      },
    });
    
    StyleSheet.flatten([styles.item, props.style]) // <= 合并默认样式和自定义样式

    这样做的好处就是可以灵活的控制样式,问题就是使用这个方法时,会🔗 递归遍历已经转换为 StyleID 的样式对象,然后生成一个新的样式对象。这样就会破坏 StyleSheet.create 之前的优化,可能会引起一定的性能负担。

    当然本节不是说不能用 StyleSheet.flatten通用性和高性能不能同时兼得,根据不同的业务场景采取不同的方案才是正解。

    4️⃣ 避免在 render 函数里创建新数组/对象

    我们写代码时,为了避免传入 [] 的地方因数据没拿到传入 undefined,经常会默认传入一个空数组:

    render() {
      return <ListComponent listData={this.props.list || []}/>
    }

    其实更好的做法是下面这样的:

    const EMPTY_ARRAY = [];
    
    render() {
        return <ListComponent listData={this.props.list || EMPTY_ARRAY}/>
    }
    

    这个其实算不上啥性能优化,还是前面再三强调的思路:对象创建和调用分离。毕竟每次渲染的时候重新创建一个空的数组/对象,能带来多大的性能问题?

    把 [] 改为统一的 EMPTY_ARRAY 常量,其实和日常编码中避免出现 Magic Number 一样,算一种编程习惯,但我觉得这种优化可以归到这个类别里,所以专门提一下。

     

    五、动画性能优化

    动画流畅很简单,在大部分的设备上,只要保证 60fps 的帧率就可以了。但要达到这个目标,在 React Native 上还是有些问题的,我画了一张图,描述了目前 React Native 的基础架构(0.61 版本)。

    • UI Thread:在 iOS/Android 上专门绘制 UI 的线程
    • JS Thread:我们写的业务代码基本都在这个线程上,React 重绘,处理 HTTP 请求的结果,磁盘数据 IO 等等
    • other Thread:泛指其他线程,比如说数据请求线程,磁盘 IO 线程等等

    上图我们可以很容易的看出,JS 线程太忙了,要做的事情太多了。而且 UI Thread 和 JS Thread 之前通信是异步的(Async Bridge),只要其它任务一多,就很难保证每一帧都是及时渲染的。

    分析清楚了,React Native 动画优化的方向自然而然就出来了:

    • 减少 JS Thread 和 UI Thread 之间的异步通信
    • 尽量减少 JS Thread 侧的计算

    1️⃣ 开启 useNativeDrive: true

    📄 文档:https://facebook.github.io/react-native/docs/animations#using-the-native-driver

    JS Thread 和 UI Thread 之间是通过 JSON 字符串传递消息的。对于一些可预测的动画,比如说点击一个点赞按钮,就跳出一个点赞动画,这种行为完全可以预测的动画,我们可以使用 useNativeDrive: true 开启原生动画驱动。

    通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。

    使用也很简单,只要在动画开始前在动画配置中加入 useNativeDrive: true 就可以了:

    Animated.timing(this.state.animatedValue, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true // <-- 加上这一行
    }).start();

    开启后所有的动画都会在 Native 线程运行,动画就会变的非常丝滑顺畅。

    经过各种暴力测试,使用原生驱动动画时,基本没有掉帧现象,但是用 JS 驱动动画,一旦操作速度加快,就会有掉帧现象。

    值得注意的是,useNativeDriver 这个属性也有着局限性,只能使用到只有非布局相关的动画属性上,例如 transform 和 opacity。布局相关的属性,比如说 height 和 position 相关的属性,开启后会报错。而且前面也说了,useNativeDriver 只能用在可预测的动画上,比如说跟随手势这种动画,useNativeDriver 就用不了的。

    2️⃣ 使用 setNativeProps

    📄 文档:https://facebook.github.io/react-native/docs/direct-manipulation

    setNativeProps 这个属性,相当于直接操作浏览器的 DOM。React 官方一般是不推荐直接操作 DOM 的,但业务场景千变万化,总会遇到一些场景不得不操作 DOM,在React Native 里也是同样的道理。

    比如说下面的动图,在屏幕中上下滚动时,y 轴上的偏移可以通过 ScrollView#onScroll 属性开启 useNativeDrive: true 来优化滚动体验。但是我们可以看到,随着上下滑动,圆圈里的数字也是随之变化的。

    如果把数字存在 this.state 里, 每次滑动不可避免的要进行大量的 setState,React 端会进行大量的重绘操作,可能会引起掉帧。我们这里就可以用 setNativeProps,避免 React 端重绘,相当于直接修改 DOM 上的数字,这样可以让动画更加流畅。

    3️⃣ 使用 InteractionManager

    📄 文档:https://facebook.github.io/react-native/docs/interactionmanager

    原生应用感觉如此流畅的一个重要原因就是在互动和动画的过程中避免繁重的操作。

    在 React Native 里,JS 线程太忙了,啥都要干,我们可以把一些繁重的任务放在 InteractionManager.runAfterInteractions() 里,确保在执行前所有的交互和动画都已经处理完毕。

    InteractionManager.runAfterInteractions(() => {
      // ...需要长时间同步执行的任务...
    });

    在 React Native 官方提供的组件里,PanResponder、Animated,VirtualizedList 都用了 InteractionManager,为的就是平衡复杂任务和交互动画之间的执行时机。

    4️⃣ 使用 react-native-reanimated 和 react-native-gesture-handler

    📺 视频教程:https://www.youtube.com/channel/UC806fwFWpiLQV5y-qifzHnA

    📄 react-native-gesture-handler 文档:https://github.com/software-mansion/react-native-gesture-handler

    📄 react-native-reanimated 文档:https://github.com/software-mansion/react-native-reanimated

    这两个库是被 Youtube 一个自由软件开发者博主 🔗 William Candillon 安利的,后面查了一下,也是 Expo 默认内置动画库和手势库。

    这两个库目的就是替代 React Native 官方提供的🔗 手势库🔗 动画库,除了 API 更加友好,我认为最大的优势是:手势动画是在 UI Thread 运行的

    我们在前面也说了,useNativeDrive: true 这个属性,只能用在可预测的动画上。跟随手势的动画,是无法使用这个属性的,所以手势捕捉和动画,都是在 JS 侧动态计算的。

    我们举一个简单的例子:小球跟随手势移动

    我们先看看 React Native 官方提供的手势动画,可以看到 JS Thread 有大量的计算,计算结果再异步传输到 UI Thread,稍微有些风吹草动,就会引起掉帧。

    如果使用 react-native-gesture-handler,手势捕捉和动画都是 UI Thread 进行的,脱离 JS Thread 计算和异步线程通信,流畅度自然大大提升:

    所以说,如果要用 React Native 构建复杂的手势动画,使用 react-native-gesture-handler 和 react-native-reanimated,是一个不错的选择,可以大幅度提高动画的流畅度。

    5️⃣ 使用 BindingX

    📄 BindingX 文档:https://alibaba.github.io/bindingx/guide/cn_introduce

    BindingX 是阿里开源的一个框架,用来解决 weex和 React Native上富交互问题,核心思路是将"交互行为"以表达式的方式描述,并提前预置到 Native,避免在行为触发时 JS 与 Native 的频繁通信。

    当然,引入上面几个第三方库会肯定会带来一定的学习成本。对于复杂交互的页面,有的团队可能会采用原生组件来代替,比如说🔗 美团外卖就会用原生组件去实现精细动画和强交互模块,所以具体使用还要看团队的技术储备和 APP 场景。

     

    六、长列表性能优化

    在 React Native 开发中,最容易遇到的对性能有一定要求场景就是长列表了。在日常业务实践中,优化做好后,千条数据渲染还是没啥问题的。

    虚拟列表前端一直是个经典的话题,核心思想也很简单:只渲染当前展示和即将展示的 View,距离远的 View 用空白 View 展示,从而减少长列表的内存占用。

    在 React Native 官网上,🔗 列表配置优化其实说的很好了,我们基本上只要了解清楚几个配置项,然后灵活配置就好。但是问题就出在「了解清楚」这四个字上,本节我会结合图文,给大家讲述清楚这几个配置。

    1️⃣ 各种列表间的关系

    React Native 有好几个列表组件,先简单介绍一下:

    • ScrollView:会把视图里的所有 View 渲染,直接对接 Native 的滚动列表
    • VirtualizedList:虚拟列表核心文件,使用 ScrollView,长列表优化配置项主要是控制它
    • FlatList:使用 VirtualizedList,实现了一行多列的功能,大部分功能都是 VirtualizedList 提供的
    • SectionList:使用 VirtualizedList,底层使用 VirtualizedSectionList,把二维数据转为一维数据

    还有一些其他依赖文件,有个🔗 博文的图总结的挺好的,我这里借用它的图一下:

    我们可以看出 VirtualizedList 才是主演,下面我们结合一些示例代码,分析它的配置项。

    2️⃣ 列表配置项

    讲之前先写个小 demo。demo 非常简单,一个基于 FlatList 的奇偶行颜色不同的列表。

    export default class App extends React.Component {
      renderItem = item => {
        return (
          <Text
            style={{
              backgroundColor: item.index % 2 === 0 ? 'green' : 'blue',
            }}>
            {'第 ' + (item.index + 1) + ' 个'}
          </Text>
        );
      }
    
      render() {
        let data = [];
        for (let i = 0; i < 1000; i++) {
          data.push({key: i});
        }
    
        return (
          <View style={{flex: 1}}>
            <FlatList
          data={data}
              renderItem={this.renderItem}
              initialNumToRender={3} // 首批渲染的元素数量
              windowSize={3} // 渲染区域高度
              removeClippedSubviews={Platform.OS === 'android'} // 是否裁剪子视图
          maxToRenderPerBatch={10} // 增量渲染最大数量
              updateCellsBatchingPeriod={50} // 增量渲染时间间隔
              debug // 开启 debug 模式
            />
          </View>
        );
      }
    }

    VirtualizedList 有个 debug 的配置项,开启后会在视图右侧显示虚拟列表的显示情况。

    这个属性文档中没有说,是翻🔗 源码发现的,我发现开启它后用来演示讲解还是很方便的,可以很直观的学习 initialNumToRender、windowSize、Viewport,Blank areas 等概念。

    下面是开启 debug 后的 demo 截屏:

    上面的图还是很清晰的,右侧 debug 指示条的黄色部分表示内存中 Item,各个属性我们再用文字描述一下:

    1.initialNumToRender

    首批应该渲染的元素数量,刚刚盖住首屏最好。而且从 debug 指示条可以看出,这批元素会一直存在于内存中。

    2.Viewport

    视口高度,就是用户能看到内容,一般就是设备高度。

    3.windowSize

    渲染区域高度,一般为 Viewport 的整数倍。这里我设置为 3,从 debug 指示条可以看出,它的高度是 Viewport 的 3 倍,上面扩展 1 个屏幕高度,下面扩展 1 个屏幕高度。在这个区域里的内容都会保存在内存里。

    将 windowSize 设置为一个较小值,能有减小内存消耗并提高性能,但是快速滚动列表时,遇到未渲染的内容的几率会增大,会看到占位的白色 View。大家可以把 windowSize 设为 1 测试一下,100% 会看到占位 View。

    4.Blank areas

    空白 View,VirtualizedList 会把渲染区域外的 Item 替换为一个空白 View,用来减少长列表的内存占用。顶部和底部都可以有。

    上图是渲染图,我们可以利用 react-devtools 再看看 React 的 Virtual DOM(为了截屏方便,我把 initialNumToRender 和 windowSize 设为 1),可以看出和上面的示意图是一致的。

    5.removeClippedSubviews

    这个翻译过来叫「裁剪子视图」的属性,文档描述不是很清晰,大意是设为 true 可以提高渲染速度,但是 iOS 上可能会出现 bug。这个属性 VirtualizedList 没有做任何优化,是直接透传给 ScrollView 的。

    在 0.59 版本的一次 🔗 commit 里,FlatList 默认 Android 开启此功能,如果你的版本低于 0.59,可以用以下方式开启:

    removeClippedSubviews={Platform.OS === 'android'}

    6.maxToRenderPerBatch 和 updateCellsBatchingPeriod

    VirtualizedList 的数据不是一下子全部渲染的,而是分批次渲染的。这两个属性就是控制增量渲染的。

    这两个属性一般是配合着用的,maxToRenderPerBatch 表示每次增量渲染的最大数量,updateCellsBatchingPeriod 表示每次增量渲染的时间间隔

    我们可以调节这两个参数来平衡渲染速度和响应速度。但是,调参作为一门玄学,很难得出一个统一的「最佳实践」,所以我们在业务中也没有动过这两个属性,直接用的系统默认值。

    3️⃣ ListLtems 优化

    📄 ListLtems 优化 文档:https://reactnative.cn/docs/optimizing-flatlist-configuration/#list-items

    文档中说了好几点优化,其实在前文我都介绍过了,这里再简单提一下:

    1.使用 getItemLayout

    如果 FlatList(VirtualizedList)的 ListLtem 高度是固定的,那么使用 getItemLayout 就非常的合算。

    在源码中(#L1287#L2046),如果不使用 getItemLayout,那么所有的 Cell 的高度,都要调用 View 的 onLayout 动态计算高度,这个运算是需要消耗时间的;如果我们使用了 getItemLayout,VirtualizedList 就直接知道了 Cell 的高度和偏移量,省去了计算,节省了这部分的开销。

    在这里我还想提一下几个注意点,希望大家使用 getItemLayout 要多注意一下:

    • 如果 ListItem 高度不固定,使用 getItemLayout 返回固定高度时,因为最终渲染高度和预测高度不一致,会出现页面跳动的问题【🔗 问题链接
    • 如果使用了 ItemSeparatorComponent,分隔线的尺寸也要考虑到 offset 的计算中【🔗 文档链接
    • 如果 FlatList 使用的时候使用了 ListHeaderComponent,也要把 Header 的尺寸考虑到 offset 的计算中【🔗 官方示例代码链接

    2.Use simple components & Use light components

    使用简单组件,核心就是减少逻辑判断和嵌套,优化方式可以参考「二、减轻渲染压力」的内容。

    3.Use shouldComponentUpdate

    参考「一、re-render」的内容。

    4.Use cached optimized images

    参考「三、图片优化那些事」的内容。

    5.Use keyExtractor or key

    常规优化点了,可以看 React 的文档 🔗 列表 & Key

    6.Avoid anonymous function on renderItem

    renderItem 避免使用匿名函数,参考「四、对象创建调用分离」的内容。

     

    七、React Native 性能优化用到的工具

    性能优化工具,本质上还是调试工具的一个子集。React Native 因为它的特殊性,做一些性能分析和调试时,需要用到 RN/iOS/Android 三端的工具,下面我就列举一下我平常用到的工具,具体的使用方法不是本文的重点,如有需要可根据关键词自行搜索。

    1.React Native 官方调试工具

    这个官网说的很清楚了,具体内容可见🔗 直达链接

    2.react-devtools

    React Native 是跑在原生 APP 上的,布局查看不能用浏览器插件,所以要用这个基于 Electron 的 react-devtools。写本文时 React Native 最新版本还是 0.61,不支持最新 V4 版本的 react-devtools,还得安装旧版本。具体安装方法可见这个🔗 链接

    3.XCode

    iOS 开发 IDE,查看分析性能问题时可以用 instruments 和 Profiler 进行调试。

    4.Android Studio

    Android 开发 IDE,查看性能的话可以使用 Android Profiler🔗 官方网站写的非常详细。

    5.iOS Simulator

    iOS 模拟器,它的 Debug 可以看一些分析内容。

    6.Android 真机 -> 开发者选项

    Android 开发者选项有不少东西可看,比如说 GPU 渲染分析和动画调试。真机调试时可以开启配合使用。

     

    八、推荐阅读

    【React Native 性能优化指南】到此就算写完了,文中内容可能有不严谨 or 错误的地方,请各位前端/iOS/Android 大佬多多指教。

    全文参考近 50 个链接,全放文末太占篇幅了,所以我都分散在文章各处了,我以 emoji 表情🔗标记的方式进行提示,大家有疑惑的地方可以去原文查看。

    在此我还要推荐一下我以前写的关于 Webpack 的文章,两篇都是全网独创

    展开全文
  • 文章参考:https://github.com/sucese/react-native/blob/master/doc/ReactNative%E6%BA%90%E7%A0%81%E7%AF%87/3ReactNative%E6%BA%90%E7%A0%81%E7%AF%87%EF%BC%9A%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.md ...

    文章参考:https://github.com/sucese/react-native/blob/master/doc/ReactNative%E6%BA%90%E7%A0%81%E7%AF%87/3ReactNative%E6%BA%90%E7%A0%81%E7%AF%87%EF%BC%9A%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.md

    文章参考:https://www.jianshu.com/p/baff68f85d41

    创建ReactNativeHost

    ReactNative的启动流程,我们首先看Application中的代码,实例化ReactNativeHost。ReactNativeHost主要的工作就是创建了ReactInstanceManager,它将一些信息传递给了ReactInstanceManager。同时,我们看一下ReactNativeHost的提供的方法。

    public abstract class ReactNativeHost {
       
          protected ReactInstanceManager createReactInstanceManager() {
            ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
              //应用上下文
              .setApplication(mApplication)
              //JSMainModuleP相当于应用首页的js Bundle,可以传递url从服务器拉取js Bundle
              //当然这个只在dev模式下可以使用
              .setJSMainModulePath(getJSMainModuleName())
              //是否开启dev模式
              .setUseDeveloperSupport(getUseDeveloperSupport())
              //红盒的回调
              .setRedBoxHandler(getRedBoxHandler())
              //自定义UI实现机制,这个我们一般用不到
              .setUIImplementationProvider(getUIImplementationProvider())
              .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
        
            //添加ReactPackage
            for (ReactPackage reactPackage : getPackages()) {
              builder.addPackage(reactPackage);
            }
        
            //获取js Bundle的加载路径
            String jsBundleFile = getJSBundleFile();
            if (jsBundleFile != null) {
              builder.setJSBundleFile(jsBundleFile);
            } else {
              builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
            }
            return builder.build();
          }
    }
    

    下面是ReactNativeHost提供的一些方法:

    protected @Nullable RedBoxHandler getRedBoxHandler();
    protected @Nullable JavaScriptExecutorFactory getJavaScriptExecutorFactory();
    protected final Application getApplication();
    protected UIImplementationProvider getUIImplementationProvider();
    
    

    实例化ReactActivityDelegate:

    启动ReactActivity的子类实例,在这个Activty的启动流程中,正式开始我们ReactNative启动和加载流程。
    我们先来分析一下ReactActivityDelegate。我么可以看到ReactActivityDelegate的一些提供的方法。就知道这个类的一些职责。他主要就是Activity的宿主的一些生命周期的委托调用。也就是所有ReactNative和宿主Activity的关联都是通过这个类类简历联系的。

    当然我们也看到。其实所有的生命周期的方法。这个类要么委托给ReactDelegate。要么就是直接ReactInstanceManager进行处理。所以这个类的职责也是非常简单的。

    可以说ReactNative和她的宿主Activity达到了很好的解耦。

    public abstract class ReactActivity extends AppCompatActivity
        implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
    
      private final ReactActivityDelegate mDelegate;
    
      protected ReactActivity() {
        mDelegate = createReactActivityDelegate();
      }
    
      /**
       * Returns the name of the main component registered from JavaScript. This is used to schedule
       * rendering of the component. e.g. "MoviesApp"
       */
      protected @Nullable String getMainComponentName() {
        return null;
      }
    
      /** Called at construction time, override if you have a custom delegate implementation. */
      protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName());
      }
      .......
    }
    

    这个类很简单,就是实例化的一个Activity的委托对象。然后所有的生命周期调用全部使用ReactActivityDelegate的代理对象完成。

    那么下面,我们看一下这个类的实现。

    public ReactActivityDelegate(ReactActivity activity, @Nullable String mainComponentName) {
        Log.d(TAG, "第二步:FMsg: 实例化ReactActivityDelegate called with: activity = [" + activity + "], mainComponentName = [" + mainComponentName + "]");
        mActivity = activity;
        mMainComponentName = mainComponentName;
      }
    

    我们看一下。Activity的onCreate的方法的代理实现。

    实例化ReactDelegate

    protected void onCreate(Bundle savedInstanceState) {
        String mainComponentName = getMainComponentName();
        mReactDelegate =
            new ReactDelegate(
                getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
              @Override
              protected ReactRootView createRootView() {
                return ReactActivityDelegate.this.createRootView();
              }
            };
        //mMainComponentName就是上面ReactActivity.getMainComponentName()返回的组件名    
        if (mMainComponentName != null) {
          //载入app页面
          loadApp(mainComponentName);
        }
      }
    

    这个方法一个,实例化ReactDelegate对象。二是调用loadApp的方法。

    我们来看一下ReactDelegate的这个对象。他的职责到底是什么呢??

    protected void loadApp(String appKey) {
        mReactDelegate.loadApp(appKey);
        getPlainActivity().setContentView(mReactDelegate.getReactRootView());
      }
    

    这个方法很简单。还是调用了ReactDelegate的loadApp方法。至此位置ReactActivityDelegate的启动流程中的职责也就告一段落。下面我们看一下ReactDelegate里面的loadApp

      public void loadApp(String appKey) {
        if (mReactRootView != null) {
          throw new IllegalStateException("Cannot loadApp while app is already running.");
        }
        //创建ReactRootView作为根视图,它本质上是一个FrameLayout
        mReactRootView = createRootView();、
        //启动RN应用.这个地方我们要注意下。ReactInstanceManager就是从这个地方开始实例化的。
        mReactRootView.startReactApplication(
            getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
      }
    

    最终这个方法是调用到ReactRootView。这个View我们知道,其实就是我们通过setContentView设置给Activity宿主的FameLayout

    实例化ReactInstanceManager

    我们可以从代码中看到,在startReactApplication的时候,我们通过ReactNativeHost的实例化对象来获取ReactInstanceManager

     /** Get the current {@link ReactInstanceManager} instance, or create one. */
      public ReactInstanceManager getReactInstanceManager() {
        if (mReactInstanceManager == null) {
          ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_START);
          mReactInstanceManager = createReactInstanceManager();
          ReactMarker.logMarker(ReactMarkerConstants.GET_REACT_INSTANCE_MANAGER_END);
        }
        return mReactInstanceManager;
      }
      
      
      protected ReactInstanceManager createReactInstanceManager() {
        ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
        Log.d(TAG, "FMsg:第五步:createReactInstanceManager() called");
        ReactInstanceManagerBuilder builder =
            ReactInstanceManager.builder()
                .setApplication(mApplication)
                 // 设置JSMain的根路径地址。也就是index.js  //"index.android"
                .setJSMainModulePath(getJSMainModuleName())
                .setUseDeveloperSupport(getUseDeveloperSupport())
                //红盒的回调
                .setRedBoxHandler(getRedBoxHandler())
                // 获取JS执行引擎的工作累的方法。默认是JSC。当然我们也可以自定义设置成V8的引擎
                // 如果需要自定义设置,那么Application中重写这个方法
                .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
                 //自定义UI实现机制,这个我们一般用不到
                .setUIImplementationProvider(getUIImplementationProvider())
                .setJSIModulesPackage(getJSIModulePackage())
                .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
        // 类实例化的作用就是传递给ReactInstanceManager所有的Packages
        Log.i(TAG, "FMsg:第五步:createReactInstanceManager: addPackage  size = " + getPackages().size());
        // 这个使我们Application实例化的时候,我们提供的ReactPackage
        for (ReactPackage reactPackage : getPackages()) {
          builder.addPackage(reactPackage);
        }
    
        String jsBundleFile = getJSBundleFile();
        Log.i(TAG, "FMsg:第五步:createReactInstanceManager: jsBundleFile = "+jsBundleFile);
        if (jsBundleFile != null) {
          builder.setJSBundleFile(jsBundleFile);
        } else {
          builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
        }
        // 这个建造者模式,可以好好分析一下。里面有很多的默认参数
        ReactInstanceManager reactInstanceManager = builder.build();
        ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_END);
        return reactInstanceManager;
      }
    
     
    

    其实从这个地方。ReactNativeHost的历史使命就完成了。所有我们复写的他的提供方法都是在这个地方传递给ReactInstance的构造化实例对象。

    下面,我们来看一下ReactInstanceManager的构造函数

     /* package */ ReactInstanceManager(
          Context applicationContext,
          @Nullable Activity currentActivity,
          @Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler,
          JavaScriptExecutorFactory javaScriptExecutorFactory,
          @Nullable JSBundleLoader bundleLoader,
          @Nullable String jsMainModulePath,
          List<ReactPackage> packages,
          boolean useDeveloperSupport,
          @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
          LifecycleState initialLifecycleState,
          @Nullable UIImplementationProvider mUIImplementationProvider,
          NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
          @Nullable RedBoxHandler redBoxHandler,
          boolean lazyViewManagersEnabled,
          @Nullable DevBundleDownloadListener devBundleDownloadListener,
          int minNumShakes,
          // 这个参数也比较重要,这个参数就是ReactNative的刷新帧的频率。默认-1.表示直接使用Android的刷新帧频率
          int minTimeLeftInFrameForNonBatchedOperationMs,
          @Nullable JSIModulePackage jsiModulePackage,
          @Nullable Map<String, RequestHandler> customPackagerCommandHandlers) {
        Log.d(ReactConstants.TAG, "ReactInstanceManager.ctor()");
        Log.d(TAG, "FMsg:第五步:ReactInstanceManager() called with: currentActivity = [" + currentActivity + "], defaultHardwareBackBtnHandler = [" + defaultHardwareBackBtnHandler + "], javaScriptExecutorFactory = [" + javaScriptExecutorFactory + "], bundleLoader = [" + bundleLoader + "], jsMainModulePath = [" + jsMainModulePath + "], packages = [" + packages + "], useDeveloperSupport = [" + useDeveloperSupport + "], bridgeIdleDebugListener = [" + bridgeIdleDebugListener + "], initialLifecycleState = [" + initialLifecycleState + "], mUIImplementationProvider = [" + mUIImplementationProvider + "], nativeModuleCallExceptionHandler = [" + nativeModuleCallExceptionHandler + "], redBoxHandler = [" + redBoxHandler + "], lazyViewManagersEnabled = [" + lazyViewManagersEnabled + "], devBundleDownloadListener = [" + devBundleDownloadListener + "], minNumShakes = [" + minNumShakes + "], minTimeLeftInFrameForNonBatchedOperationMs = [" + minTimeLeftInFrameForNonBatchedOperationMs + "], jsiModulePackage = [" + jsiModulePackage + "], customPackagerCommandHandlers = [" + customPackagerCommandHandlers + "]");
        initializeSoLoaderIfNecessary(applicationContext);
    
        DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
    
        mApplicationContext = applicationContext;
        mCurrentActivity = currentActivity;
        mDefaultBackButtonImpl = defaultHardwareBackBtnHandler;
        // 其实默认传入的null  如果我们想使用其他JS引擎,比如V8.我们可以在这里面进行传入
        mJavaScriptExecutorFactory = javaScriptExecutorFactory;
        mBundleLoader = bundleLoader;
        mJSMainModulePath = jsMainModulePath;
        mPackages = new ArrayList<>();
        mUseDeveloperSupport = useDeveloperSupport;
        Systrace.beginSection(
            Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstanceManager.initDevSupportManager");
        mDevSupportManager =
            DevSupportManagerFactory.create(
                applicationContext,
                createDevHelperInterface(),
                mJSMainModulePath,
                useDeveloperSupport,
                redBoxHandler,
                devBundleDownloadListener,
                minNumShakes,
                customPackagerCommandHandlers);
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        mBridgeIdleDebugListener = bridgeIdleDebugListener;
        mLifecycleState = initialLifecycleState;
        mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
        mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
    
        /// 这个地方就是添加ReactPackage
        synchronized (mPackages) {
          PrinterHolder.getPrinter()
              .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: Use Split Packages");
          // 这个方法很重要,这个方法我们会将系统内置的那些CoreModulesPackage添加进去
          // AndroidInfoModule.class,
          // DeviceEventManagerModule.class,
          // DeviceInfoModule.class,
          // DevSettingsModule.class,
          // ExceptionsManagerModule.class,
          // HeadlessJsTaskSupportModule.class,
          // SourceCodeModule.class,
          // Timing.class,
          // UIManagerModule.class
          mPackages.add(
              new CoreModulesPackage(
                  this,
                  new DefaultHardwareBackBtnHandler() {
                    @Override
                    public void invokeDefaultOnBackPressed() {
                      ReactInstanceManager.this.invokeDefaultOnBackPressed();
                    }
                  },
                  mUIImplementationProvider,
                  lazyViewManagersEnabled,
                  minTimeLeftInFrameForNonBatchedOperationMs));
          if (mUseDeveloperSupport) {
            mPackages.add(new DebugCorePackage());
          }
          mPackages.addAll(packages);
        }
        mJSIModulePackage = jsiModulePackage;
    
        // Instantiate ReactChoreographer in UI thread.
        // 这个就是实例化Android的序列帧的监听对象
        ReactChoreographer.initialize();
        if (mUseDeveloperSupport) {
          mDevSupportManager.startInspector();
        }
      }
    

    从ReactInstanceManager的构造函数,我们可以看到。所有ReactNative的启动的初始化资源基本都已经准备完毕。所以startReactApplication的方法。我们必须要传入ReactInstanceManager实例化对象。

    其实通过查看ReactInstance的类的内容,我们可以看到这个类的方法其实也是比较简单的。他的关于启动流程最大的一个作用就是创建ReactContext。那么这个创建ReactContext是什么开始的呢?

    我们来继续看ReactRootView的startReactApplication方法。

    startReactApplication

      @ThreadConfined(UI)
      public void startReactApplication(
          ReactInstanceManager reactInstanceManager,
          String moduleName,
          @Nullable Bundle initialProperties,
          @Nullable String initialUITemplate) {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
        try {
          // 线程检查
          UiThreadUtil.assertOnUiThread();
    
          // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
          // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
          // it in the case of re-creating the catalyst instance
          Assertions.assertCondition(
              mReactInstanceManager == null,
              "This root view has already been attached to a catalyst instance manager");
    
          mReactInstanceManager = reactInstanceManager;
          mJSModuleName = moduleName;
          mAppProperties = initialProperties;
          mInitialUITemplate = initialUITemplate;
    
          if (mUseSurface) {
            // TODO initialize surface here
          }
          //创建RN上下文上下文对象
          mReactInstanceManager.createReactContextInBackground();
          //attachToReactInstanceManager 调用的是mReactInstanceManager.attachRootView(this)
          attachToReactInstanceManager();
    
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
      }
    

    这个方法比较深,我们可以一步步往下看。进入到这个方法,就进入到ReactInstance的核心功能。也是ReactNative启动的核心流程。也是最重要的流程中。createReactContext的创建流程。

    @ThreadConfined(UI)
      public void createReactContextInBackground() {
        Log.d(ReactConstants.TAG, "ReactInstanceManager.createReactContextInBackground()");
        Log.d(TAG, "FMsg:第六步:ReactRootView开始创建ReactContext createReactContextInBackground() called");
        UiThreadUtil
            .assertOnUiThread(); // Assert before setting mHasStartedCreatingInitialContext = true
        if (!mHasStartedCreatingInitialContext) {
          mHasStartedCreatingInitialContext = true;
          recreateReactContextInBackgroundInner();
        }
      }
      
      
       @ThreadConfined(UI)
      private void recreateReactContextInBackgroundInner() {
        Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
        Log.d(TAG, "FMsg:第六步:调用内部方法recreateReactContextInBackgroundInner() called");
        PrinterHolder.getPrinter()
            .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
        UiThreadUtil.assertOnUiThread();
        // 调试相关的逻辑,咱们暂时忽略
        if (mUseDeveloperSupport && mJSMainModulePath != null) {
          final DeveloperSettings devSettings = mDevSupportManager.getDevSettings();
    
          if (!Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
            if (mBundleLoader == null) {
              mDevSupportManager.handleReloadJS();
            } else {
              mDevSupportManager.isPackagerRunning(
                  new PackagerStatusCallback() {
                    @Override
                    public void onPackagerStatusFetched(final boolean packagerIsRunning) {
                      UiThreadUtil.runOnUiThread(
                          new Runnable() {
                            @Override
                            public void run() {
                              if (packagerIsRunning) {
                                mDevSupportManager.handleReloadJS();
                              } else if (mDevSupportManager.hasUpToDateJSBundleInCache()
                                  && !devSettings.isRemoteJSDebugEnabled()) {
                                // If there is a up-to-date bundle downloaded from server,
                                // with remote JS debugging disabled, always use that.
                                onJSBundleLoadedFromServer(null);
                              } else {
                                // If dev server is down, disable the remote JS debugging.
                                devSettings.setRemoteJSDebugEnabled(false);
                                recreateReactContextInBackgroundFromBundleLoader();
                              }
                            }
                          });
                    }
                  });
            }
            return;
          }
        }
    
        recreateReactContextInBackgroundFromBundleLoader();
      }
      
      
       @ThreadConfined(UI)
      private void recreateReactContextInBackgroundFromBundleLoader() {
        Log.d(
            ReactConstants.TAG,
            "ReactInstanceManager.recreateReactContextInBackgroundFromBundleLoader()");
        Log.d(TAG, "FMsg:第六步:线上环境来将会使用后台任务创建ReactContext recreateReactContextInBackgroundFromBundleLoader() called");
        PrinterHolder.getPrinter()
            .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: load from BundleLoader");
        recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader);
      }
      
      
        @ThreadConfined(UI)
      private void runCreateReactContextOnNewThread(final ReactContextInitParams initParams) {
        Log.d(ReactConstants.TAG, "ReactInstanceManager.runCreateReactContextOnNewThread()");
        Log.d(TAG, "FMsg:第六步:runCreateReactContextOnNewThread() called with: initParams = [" + initParams + "]");
        UiThreadUtil.assertOnUiThread();
        synchronized (mAttachedReactRoots) {
          synchronized (mReactContextLock) {
            // 生命周期的回收,主要是针对可能会造成的多次初始化RN环境的
            if (mCurrentReactContext != null) {
              tearDownReactContext(mCurrentReactContext);
              mCurrentReactContext = null;
            }
          }
        }
    
        mCreateReactContextThread =
            new Thread(
                null,
                new Runnable() {
                  @Override
                  public void run() {
                    ReactMarker.logMarker(REACT_CONTEXT_THREAD_END);
                    synchronized (ReactInstanceManager.this.mHasStartedDestroying) {
                      while (ReactInstanceManager.this.mHasStartedDestroying) {
                        try {
                          ReactInstanceManager.this.mHasStartedDestroying.wait();
                        } catch (InterruptedException e) {
                          continue;
                        }
                      }
                    }
                    // As destroy() may have run and set this to false, ensure that it is true before we
                    // create
                    mHasStartedCreatingInitialContext = true;
    
                    try {
                      Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
                      ReactMarker.logMarker(VM_INIT);
                      Log.i(TAG, "FMsg:第六步:runCreateReactContextOnNewThread run: " + Thread.currentThread().getName());
                      final ReactApplicationContext reactApplicationContext =
                          createReactContext(
                              initParams.getJsExecutorFactory().create(),
                              initParams.getJsBundleLoader());
    
                      mCreateReactContextThread = null;
                      ReactMarker.logMarker(PRE_SETUP_REACT_CONTEXT_START);
    
                      // 这个地方也是一个保护机制,我们暂时不需要关注
                      final Runnable maybeRecreateReactContextRunnable =
                          new Runnable() {
                            @Override
                            public void run() {
                              if (mPendingReactContextInitParams != null) {
                                runCreateReactContextOnNewThread(mPendingReactContextInitParams);
                                mPendingReactContextInitParams = null;
                              }
                            }
                          };
                      Runnable setupReactContextRunnable =
                          new Runnable() {
                            @Override
                            public void run() {
                              try {
                                setupReactContext(reactApplicationContext);
                              } catch (Exception e) {
                                mDevSupportManager.handleException(e);
                              }
                            }
                          };
    
                      reactApplicationContext.runOnNativeModulesQueueThread(setupReactContextRunnable);
                      UiThreadUtil.runOnUiThread(maybeRecreateReactContextRunnable);
                    } catch (Exception e) {
                      mDevSupportManager.handleException(e);
                    }
                  }
                },
                "create_react_context");
        ReactMarker.logMarker(REACT_CONTEXT_THREAD_START);
        mCreateReactContextThread.start();
      }
    

    这个地方就是我们比较重要的创建ReactContext对象的逻辑。

       /** @return instance of {@link ReactContext} configured a {@link CatalystInstance} set */
      private ReactApplicationContext createReactContext(
          JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
        Log.d(TAG, "FMsg:第六步:ReactInstanceManager.createReactContext " + Thread.currentThread().getName());
        Log.d(TAG, "FMsg:第六步:createReactContext() called with: jsExecutor = [" + jsExecutor + "], jsBundleLoader = [" + jsBundleLoader + "]");
        ReactMarker.logMarker(CREATE_REACT_CONTEXT_START, jsExecutor.getName());
        // 这个地方就进行了实例化reactContext
        // ReactApplicationContext extends ReactContext extends ContextWrapper
        // 构造函数传入的是Context。 eactApplicationContext是ReactContext的包装类。
        final ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    
        // 这个就是NativeModuleCallExceptionHandler.异常捕获器
        NativeModuleCallExceptionHandler exceptionHandler =
            mNativeModuleCallExceptionHandler != null
                ? mNativeModuleCallExceptionHandler
                : mDevSupportManager;
        reactContext.setNativeModuleCallExceptionHandler(exceptionHandler);
    
        // 创建JavaModule注册表Builder,用来创建JavaModule注册表,JavaModule注册表将所有的JavaModule注册到CatalystInstance中。
        NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
        // 最最重要的一个对象,催化器实例对象
        // jsExecutor、nativeModuleRegistry、nativeModuleRegistry等各种参数处理好之后,开始构建CatalystInstanceImpl实例。
        CatalystInstanceImpl.Builder catalystInstanceBuilder =
            new CatalystInstanceImpl.Builder()
                    // 设置ReactNative的消息队列的配置数据
                .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
                    // 通过JS执行器工厂实例化的JS线程执行器
                .setJSExecutor(jsExecutor)
                .setRegistry(nativeModuleRegistry)
                .setJSBundleLoader(jsBundleLoader)
                .setNativeModuleCallExceptionHandler(exceptionHandler);
    
        ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
        // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
        final CatalystInstance catalystInstance;
        try {
          // 没什么好说的。空值判断。但是催化器的构造函数我们需要研究一下。
          // 里面针对消息队列的线程进行初始化
          catalystInstance = catalystInstanceBuilder.build();
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
          ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
        }
    
        // 关联ReacContext与CatalystInstance实例化
        // 解析上面实例化的是三个线程封装对象
        reactContext.initializeWithInstance(catalystInstance);
    
        if (mJSIModulePackage != null) {
          // 这个默认是为null的。这个数据最初是在ReactNativeHost里面传入的
          catalystInstance.addJSIModules(
              mJSIModulePackage.getJSIModules(
                  reactContext, catalystInstance.getJavaScriptContextHolder()));
          // 这个地方没太看懂
          if (ReactFeatureFlags.useTurboModules) {
            catalystInstance.setTurboModuleManager(
                catalystInstance.getJSIModule(JSIModuleType.TurboModuleManager));
          }
        }
        if (mBridgeIdleDebugListener != null) {
          catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
        }
        if (Systrace.isTracing(TRACE_TAG_REACT_APPS | TRACE_TAG_REACT_JS_VM_CALLS)) {
          catalystInstance.setGlobalVariable("__RCTProfileIsProfiling", "true");
        }
        ReactMarker.logMarker(ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START);
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
        // 加载JSBundle的数据
        catalystInstance.runJSBundle();
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    
        return reactContext;
      }
    

    这个是非常重要的方法,我们需要一点点来分析一下。

    1、首先他将Application的对象用ReactApplicationContext进行包装,生成ReactApplication对象。这个没什么好说的。简单的包装而已。

    2、创建JavaModule的注册表

    3、进行CatalystInstanceImpl实例化

    4、关联ReacContext与CatalystInstance实例化 解析上面实例化的是三个线程封装对象。这个地方。

    我们主要作用是让reactContext来持有catalystInstance对象 两个作用:1、获取这个实力上面传入ReactQueueConfigurationSpec实例,或者三个消息队列线程封装的对象 2、主要是通过催化器实例获取JSModule、NativeModule

    其次,ReacContext还有就是一些生命周期的管理

    实例化CatalystInstanceImpl

    下面就是最重要的催化剂实例的构造方法

    private CatalystInstanceImpl(
          final ReactQueueConfigurationSpec reactQueueConfigurationSpec,
          final JavaScriptExecutor jsExecutor,
          final NativeModuleRegistry nativeModuleRegistry,
          final JSBundleLoader jsBundleLoader,
          NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
        Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
        Log.d(TAG, "FMsg:第七步:CatalystInstanceImpl() called with: reactQueueConfigurationSpec = [" + reactQueueConfigurationSpec + "], jsExecutor = [" + jsExecutor + "], nativeModuleRegistry = [" + nativeModuleRegistry + "], jsBundleLoader = [" + jsBundleLoader + "], nativeModuleCallExceptionHandler = [" + nativeModuleCallExceptionHandler + "]");
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl");
    
        // 重点关注一下这一方法。这是一个Native方法。这样我们就调用到Native层
        mHybridData = initHybrid();
        // ReactNative的消息队列相关的实例化
        // TODO 这个地方要重点看一下
        // 这个主要是分别在对应的线程中创建消息任务队列的Handler(搬运工)
        // 当然还创建了两个线程(主线程不用创建)
        mReactQueueConfiguration =
            ReactQueueConfigurationImpl.create(
                reactQueueConfigurationSpec, new NativeExceptionHandler());
        mBridgeIdleListeners = new CopyOnWriteArrayList<>();
        // NativeModules注入器
        mNativeModuleRegistry = nativeModuleRegistry;
        mJSModuleRegistry = new JavaScriptModuleRegistry();
        // JSBundlerLoader
        mJSBundleLoader = jsBundleLoader;
        mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
        mNativeModulesQueueThread = mReactQueueConfiguration.getNativeModulesQueueThread();
        mTraceListener = new JSProfilerTraceListener(this);
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    
        Log.d(TAG, "FMsg: 第七步:CatalystInstanceImpl() called Initializing React Xplat Bridge before initializeBridge");
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "initializeCxxBridge");
        Log.d(TAG, "FMsg:第七步:CatalystInstanceImpl() called with: initializeCxxBridge");
    
        // TODO 这个地方要重点看一下. 又一个非常重要的方法。
        // 初始化Java和Native之间的Bridge桥。这个是一个Native方法
        initializeBridge(
            //
            new BridgeCallback(this),
            // JSCore的JS引擎
            jsExecutor,
            // JS消息队列线程封装对象
            mReactQueueConfiguration.getJSQueueThread(),
                // Native消息队列线程封装对象
            mNativeModulesQueueThread,
            // 非C++的Modules就是JavaModule.也就是我们在Application里面封装的Modules
            // 这些NativeModules都会被封装成JavaModuleWrapper对象送给Native层
            mNativeModuleRegistry.getJavaModules(this),
            // 获取我们所有的Module中的C++的modules。这个是通过注解来标记那个NativeModule是C++
            // 但是我们翻了一下源码,没看到哪个NativeModule是C++
            mNativeModuleRegistry.getCxxModules());
        Log.d(ReactConstants.TAG, "第七步:实例化Initializing React Xplat Bridge after initializeBridge");
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
    
        mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
      }
    

    下面,我们看一下Native层的调用

    jni::local_ref<CatalystInstanceImpl::jhybriddata> CatalystInstanceImpl::initHybrid(
        jni::alias_ref<jclass>) {
       cout<<"===========FMsg:CatalystInstanceImpl initHybrid  called==============="<<endl;
      return makeCxxInstance();
    }
    
    void CatalystInstanceImpl::initializeBridge(
        jni::alias_ref<ReactCallback::javaobject> callback,
        // This executor is actually a factory holder.
        JavaScriptExecutorHolder* jseh,
        jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
        jni::alias_ref<JavaMessageQueueThread::javaobject> nativeModulesQueue,
        jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
        jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {
        LOG(INFO) <<"===========FMsg:CatalystInstanceImpl initializeBridge==============="<<endl;
        // .............省略部分代码
    
      moduleRegistry_ = std::make_shared<ModuleRegistry>(
        buildNativeModuleList(
           std::weak_ptr<Instance>(instance_),
           javaModules,
           cxxModules,
           moduleMessageQueue_));
    
      instance_->initializeBridge(
        std::make_unique<JInstanceCallback>(
        callback,
        moduleMessageQueue_),
        jseh->getExecutorFactory(),
        folly::make_unique<JMessageQueueThread>(jsQueue),
        moduleRegistry_);
    }
    

    至此,Java层的代码执行完毕。我们彻底就进入Native层的代码逻辑了。上面Jni代码中,调用的instance_->initializeBridge()。 这个逻辑层,我们要到Native层去查找逻辑实现了。

    void Instance::initializeBridge(
        std::unique_ptr<InstanceCallback> callback,
        std::shared_ptr<JSExecutorFactory> jsef,
        std::shared_ptr<MessageQueueThread> jsQueue,
        std::shared_ptr<ModuleRegistry> moduleRegistry) {
      LOG(INFO) <<"===========FMsg:Instance initializeBridge==============="<<endl;
      callback_ = std::move(callback);
      moduleRegistry_ = std::move(moduleRegistry);
      jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
        nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
            jsef.get(), moduleRegistry_, jsQueue, callback_);
    
        std::lock_guard<std::mutex> lock(m_syncMutex);
        m_syncReady = true;
        m_syncCV.notify_all();
      });
    
      CHECK(nativeToJsBridge_);
    }
    

    上线的代码中。催化器对象在启动的流程中的主要作用就完成了。主要就是将催化器初始化的一些Java、Native、JS层的一些通信对象准备好。

    我们在回过头去,看catalystInstance.runJSBundle()方法的调用。

      @Override
      public void runJSBundle() {
        Log.d(ReactConstants.TAG, "CatalystInstanceImpl.runJSBundle()");
        Log.d(TAG, "FMsg:第九步:runJSBundle() called");
        Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
        // incrementPendingJSCalls();
        mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
    
        synchronized (mJSCallsPendingInitLock) {
    
          // Loading the bundle is queued on the JS thread, but may not have
          // run yet.  It's safe to set this here, though, since any work it
          // gates will be queued on the JS thread behind the load.
          mAcceptCalls = true;
    
          for (PendingJSCall function : mJSCallsPendingInit) {
            function.call(this);
          }
          mJSCallsPendingInit.clear();
          mJSBundleHasLoaded = true;
        }
    
        // This is registered after JS starts since it makes a JS call
        Systrace.registerListener(mTraceListener);
      }
    
      public static JSBundleLoader createFileLoader(
          final String fileName, final String assetUrl, final boolean loadSynchronously) {
        Log.d(TAG, "FMsg:第九步:createFileLoader() called with: fileName = [" + fileName + "], assetUrl = [" + assetUrl + "], loadSynchronously = [" + loadSynchronously + "]");
        return new JSBundleLoader() {
          @Override
          public String loadScript(JSBundleLoaderDelegate delegate) {
            delegate.loadScriptFromFile(fileName, assetUrl, loadSynchronously);
            return fileName;
          }
        };
      }
    
      @Override
      public void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {
        Log.d(TAG, "FMsg:第九步:loadScriptFromFile() called with: fileName = [" + fileName + "], sourceURL = [" + sourceURL + "], loadSynchronously = [" + loadSynchronously + "]");
        mSourceURL = sourceURL;
        jniLoadScriptFromFile(fileName, sourceURL, loadSynchronously);
      }
      
    

    这个jniLoadScriptFromFile也是一个Native的方法,我们可以看到他会调用到CatalystInstanceImpl.cpp的

    void CatalystInstanceImpl::jniLoadScriptFromFile(const std::string& fileName,
                                                     const std::string& sourceURL,
                                                     bool loadSynchronously) {
      LOG(INFO) <<"===========FMsg:CatalystInstanceImpl jniLoadScriptFromFile==============="<<endl;
      if (Instance::isIndexedRAMBundle(fileName.c_str())) {
        instance_->loadRAMBundleFromFile(fileName, sourceURL, loadSynchronously);
      } else {
        std::unique_ptr<const JSBigFileString> script;
        RecoverableError::runRethrowingAsRecoverable<std::system_error>(
          [&fileName, &script]() {
            script = JSBigFileString::fromPath(fileName);
          });
        instance_->loadScriptFromString(std::move(script), sourceURL, loadSynchronously);
      }
    }
    

    调用到Instancee.cpp的

    void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                        std::string sourceURL,
                                        bool loadSynchronously) {
      LOG(INFO) <<"===========FMsg:Instance loadScriptFromString==============="<<endl;
      SystraceSection s("Instance::loadScriptFromString", "sourceURL",
                        sourceURL);
      if (loadSynchronously) {
        loadApplicationSync(nullptr, std::move(string), std::move(sourceURL));
      } else {
        loadApplication(nullptr, std::move(string), std::move(sourceURL));
      }
    }
    
    
    void Instance::loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,
                                   std::unique_ptr<const JSBigString> string,
                                   std::string sourceURL) {
      LOG(INFO) <<"===========FMsg:Instance loadApplication=========string======"<<endl;
      callback_->incrementPendingJSCalls();
      SystraceSection s("Instance::loadApplication", "sourceURL",
                        sourceURL);
      nativeToJsBridge_->loadApplication(std::move(bundleRegistry), std::move(string),
                                         std::move(sourceURL));
    }
    

    下面有调用到了

    void NativeToJsBridge::loadApplication(
        std::unique_ptr<RAMBundleRegistry> bundleRegistry,
        std::unique_ptr<const JSBigString> startupScript,
        std::string startupScriptSourceURL) {
       LOG(INFO) <<"===========FMsg:NativeToJsBridge loadApplication==============="<<endl;
      runOnExecutorQueue(
          [this,
           bundleRegistryWrap=folly::makeMoveWrapper(std::move(bundleRegistry)),
           startupScript=folly::makeMoveWrapper(std::move(startupScript)),
           startupScriptSourceURL=std::move(startupScriptSourceURL)]
            (JSExecutor* executor) mutable {
        auto bundleRegistry = bundleRegistryWrap.move();
        if (bundleRegistry) {
          executor->setBundleRegistry(std::move(bundleRegistry));
        }
         //executor从runOnExecutorQueue()返回的map中取得,与OnLoad中的JSCJavaScriptExecutorHolder对应,也与
         //Java中的JSCJavaScriptExecutor对应。它的实例在JSExecutor.cpp中实现。
        try {
          LOG(INFO) <<"===========FMsg:NativeToJsBridge executor  loadApplicationScript======" << startupScriptSourceURL << "。"<<endl;
          executor->loadApplicationScript(std::move(*startupScript),
                                          std::move(startupScriptSourceURL));
        } catch (...) {
          m_applicationScriptHasFailure = true;
          throw;
        }
      });
    }
    
    
    void NativeToJsBridge::runOnExecutorQueue(std::function<void(JSExecutor*)> task) {
      if (*m_destroyed) {
        return;
      }
    
      std::shared_ptr<bool> isDestroyed = m_destroyed;
      m_executorMessageQueueThread->runOnQueue([this, isDestroyed, task=std::move(task)] {
        if (*isDestroyed) {
          return;
        }
    
        // The executor is guaranteed to be valid for the duration of the task because:
        // 1. the executor is only destroyed after it is unregistered
        // 2. the executor is unregistered on this queue
        // 3. we just confirmed that the executor hasn't been unregistered above
        task(m_executor.get());
      });
    }
    

    下面,我们看到他调用了JSIExecutor的loadApplicationScript。下面我们来看一下这个方法的实现。

    void JSIExecutor::loadApplicationScript(
        std::unique_ptr<const JSBigString> script,
        std::string sourceURL) {
      SystraceSection s("JSIExecutor::loadApplicationScript");
      LOG(INFO) <<"===========FMsg:JSIExecutor loadApplicationScript===============sourceURL = " << sourceURL << "。" <<endl;
      // TODO: check for and use precompiled HBC
    
      runtime_->global().setProperty(
          *runtime_,
          "nativeModuleProxy",
          Object::createFromHostObject(
              *runtime_, std::make_shared<NativeModuleProxy>(*this)));
    
      runtime_->global().setProperty(
          *runtime_,
          "nativeFlushQueueImmediate",
          Function::createFromHostFunction(
              *runtime_,
              PropNameID::forAscii(*runtime_, "nativeFlushQueueImmediate"),
              1,
              [this](
                  jsi::Runtime &,
                  const jsi::Value &,
                  const jsi::Value *args,
                  size_t count) {
                if (count != 1) {
                  throw std::invalid_argument(
                      "nativeFlushQueueImmediate arg count must be 1");
                }
                // 应该是JSIExecutor⾥里里⾯面callNativeModules中的delegate_->callNativeModules
                LOG(INFO) <<"===========FMsg:JSIExecutor loadApplicationScript======callNativeModules======="<<endl;
                callNativeModules(args[0], false);
                return Value::undefined();
              }));
    
      runtime_->global().setProperty(
          *runtime_,
          "nativeCallSyncHook",
          Function::createFromHostFunction(
              *runtime_,
              PropNameID::forAscii(*runtime_, "nativeCallSyncHook"),
              1,
              [this](
                  jsi::Runtime &,
                  const jsi::Value &,
                  const jsi::Value *args,
                  size_t count) { return nativeCallSyncHook(args, count); }));
    
    #if DEBUG
      runtime_->global().setProperty(
          *runtime_,
          "globalEvalWithSourceUrl",
          Function::createFromHostFunction(
              *runtime_,
              PropNameID::forAscii(*runtime_, "globalEvalWithSourceUrl"),
              1,
              [this](
                  jsi::Runtime &,
                  const jsi::Value &,
                  const jsi::Value *args,
                  size_t count) { return globalEvalWithSourceUrl(args, count); }));
    #endif
    
      if (runtimeInstaller_) {
        runtimeInstaller_(*runtime_);
      }
    
      bool hasLogger(ReactMarker::logTaggedMarker);
      std::string scriptName = simpleBasename(sourceURL);
      if (hasLogger) {
        ReactMarker::logTaggedMarker(
            ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
      }
      //使用Webkit JSC去解释执行JS
      runtime_->evaluateJavaScript(
          std::make_unique<BigStringBuffer>(std::move(script)), sourceURL);
      flush();
      if (hasLogger) {
        ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
        ReactMarker::logTaggedMarker(
            ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
      }
    }
    

    最后代码调用到evaluateJavaScript方法中。这个方法的实现JSCRuntime.cpp中。我们简简单看一下:

    jsi::Value JSCRuntime::evaluateJavaScript(
        const std::shared_ptr<const jsi::Buffer> &buffer,
        const std::string& sourceURL) {
      // LOG(INFO) <<"===========FMsg:JSCRuntime evaluateJavaScript===============sourceURL = " << sourceURL << "。" <<endl;
      std::string tmp(
          reinterpret_cast<const char*>(buffer->data()), buffer->size());
      JSStringRef sourceRef = JSStringCreateWithUTF8CString(tmp.c_str());
      JSStringRef sourceURLRef = nullptr;
      if (!sourceURL.empty()) {
        sourceURLRef = JSStringCreateWithUTF8CString(sourceURL.c_str());
      }
      JSValueRef exc = nullptr;
      JSValueRef res =
          JSEvaluateScript(ctx_, sourceRef, nullptr, sourceURLRef, 0, &exc);
      JSStringRelease(sourceRef);
      if (sourceURLRef) {
        JSStringRelease(sourceURLRef);
      }
      checkException(res, exc);
      return createValue(res);
    }
    

    从上面的代码中,我们我们开始加载JSBundle的逻辑已经执行完毕。但是这个时候其实ReactNative并没有启动起来。

    我们下面来看一下启动流程的最后一步:

    我们来回到ReactRootView的startReactApplication这个方法中的最后一步:

      private void attachToReactInstanceManager() {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachToReactInstanceManager");
        Log.d(TAG, "FMsg:attachToReactInstanceManager() called");
        try {
          if (mIsAttachedToInstance) {
            return;
          }
    
          mIsAttachedToInstance = true;
            // ReactInstanceManager绑定到当前的RootView
          Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
          getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
      }
    

    方法很简单,我们来看attachRootView的实现

      public void attachRootView(ReactRoot reactRoot) {
        Log.d(TAG, "FMsg:attachRootView() called with: reactRoot = [" + reactRoot + "]");
        UiThreadUtil.assertOnUiThread();
        // ReactInstanceManager 是支持多 RootView 的.其实这个地方大家可以猜想,其实我们的ReactNative的页面,可以作为一个小的页面View
        // 附着到宿主Activity上。
        mAttachedReactRoots.add(reactRoot);
    
        // Reset reactRoot content as it's going to be populated by the application content from JS.
        clearReactRoot(reactRoot);
    
        // If react context is being created in the background, JS application will be started
        // automatically when creation completes, as reactRoot reactRoot is part of the attached
        // reactRoot reactRoot list.
        ReactContext currentContext = getCurrentReactContext();
        if (mCreateReactContextThread == null && currentContext != null) {
          // 最后,取出 ReactContext,让 ReactRootView 与 CatalystInstance 相关联:
          attachRootViewToInstance(reactRoot);
        }
      }
    
      private void attachRootViewToInstance(final ReactRoot reactRoot) {
        Log.d(ReactConstants.TAG, "ReactInstanceManager.attachRootViewToInstance()");
        Log.d(TAG, "FMsg:第十步:attachRootViewToInstance() called with: reactRoot = [" + reactRoot + "]");
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachRootViewToInstance");
    
        // 首先通过 UIManagerHelper 拿到 UIManager:
        // UIManager 是什么呢?它有两个实现类:FabricUIManager、UIManagerModule
        // 我们来看后者,它的注释中说:这是一个原生模块,允许JS创建和更新原生视图。其实这个就是我们后来看RN的渲染流程中非常重要的类
        UIManager uiManager =
            UIManagerHelper.getUIManager(mCurrentReactContext, reactRoot.getUIManagerType());
    
        @Nullable Bundle initialProperties = reactRoot.getAppProperties();
    
        final int rootTag =
            uiManager.addRootView(
                reactRoot.getRootViewGroup(),
                initialProperties == null
                    ? new WritableNativeMap()
                    : Arguments.fromBundle(initialProperties),
                reactRoot.getInitialUITemplate());
        reactRoot.setRootViewTag(rootTag);
        if (reactRoot.getUIManagerType() == FABRIC) {
          // Fabric requires to call updateRootLayoutSpecs before starting JS Application,
          // this ensures the root will hace the correct pointScaleFactor.
          uiManager.updateRootLayoutSpecs(
              rootTag, reactRoot.getWidthMeasureSpec(), reactRoot.getHeightMeasureSpec());
          reactRoot.setShouldLogContentAppeared(true);
        } else {
          Log.d(TAG, "FMsg:第十步:attachRootViewToInstance() called with: reactRoot = [" + reactRoot + "]");
          // 这个方法就是我们真的去调用JS的相关代码,我们可以看看这个代码
          reactRoot.runApplication();
        }
        Systrace.beginAsyncSection(
            TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
        // 这个地方其实,我们有疑问:
        // 为什么使用UiThreadUtil.runOnUiThread。而不是
        //  mCurrentReactContext.runOnUiQueueThread();???
        UiThreadUtil.runOnUiThread(
            new Runnable() {
              @Override
              public void run() {
                Systrace.endAsyncSection(
                    TRACE_TAG_REACT_JAVA_BRIDGE, "pre_rootView.onAttachedToReactInstance", rootTag);
                reactRoot.onStage(ReactStage.ON_ATTACH_TO_INSTANCE);
              }
            });
        Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    

    这个方法的最后一步,我们就可以看到reactRoot.runApplication() 这个方法很明显,就是运行起来JS的相关代码逻辑。

      @Override
      public void runApplication() {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.runApplication");
        Log.d(TAG, "FMsg:第十一步:ReactRootView runApplication() called");
        try {
          if (mReactInstanceManager == null || !mIsAttachedToInstance) {
            return;
          }
    
          ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
          if (reactContext == null) {
            return;
          }
    
          CatalystInstance catalystInstance = reactContext.getCatalystInstance();
          String jsAppModuleName = getJSModuleName();
    
          if (mUseSurface) {
            // TODO call surface's runApplication
          } else {
            if (mWasMeasured) {
              updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec);
            }
    
            WritableNativeMap appParams = new WritableNativeMap();
            appParams.putDouble("rootTag", getRootViewTag());
            @Nullable Bundle appProperties = getAppProperties();
            if (appProperties != null) {
              appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
            }
    
            mShouldLogContentAppeared = true;
            Log.i(TAG, "FMsg: 第十步:==============runApplication: ==========================" + jsAppModuleName);
            Log.i(TAG, "FMsg: 第十步:==============runApplication: ==========================" + appParams);
            catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
          }
        } finally {
          Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
      }
    

    这个方法也是比较简单的。通过ReactContext的实例,拿到催化器实例。然后通过催化器实例获取JSModule的runApplication

      @Override
      public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
        // Log.d(TAG, "FMsg:getJSModule() called with: jsInterface = [" + jsInterface + "]");
        // mJSModuleRegistry注入器获取当前的注入器。至于怎么去获取的。我们可以再分析
        return mJSModuleRegistry.getJavaScriptModule(this, jsInterface);
      }
    

    可以看到,最终调用的是 catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams) , AppRegistry.class 是JS层暴露给Java层的接口方法。它的真正实现在 AppRegistry.js 里, AppRegistry.js 是运行所有 RN 应用的 JS 层入口,我们来看看它的实现:在 Libraries/ReactNative 中的 AppRegistry.js

      /**
       * Loads the JavaScript bundle and runs the app.
       *
       * See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
       */
      runApplication(appKey: string, appParameters: any): void {
        const msg =
          'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
        infoLog(msg);
        BugReporting.addSource(
          'AppRegistry.runApplication' + runCount++,
          () => msg,
        );
        invariant(
          runnables[appKey] && runnables[appKey].run,
          `"${appKey}" has not been registered. This can happen if:\n` +
            '* Metro (the local dev server) is run from the wrong folder. ' +
            'Check if Metro is running, stop it and restart it in the current project.\n' +
            "* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.",
        );
    
        SceneTracker.setActiveScene({name: appKey});
        runnables[appKey].run(appParameters);
      },
    

    到这里就会去调用JS进行渲染,在通过 UIManagerModule 将JS组件转换成Android组件,最终显示在 ReactRootView 上。

    最后总结一下,就是先在应用终端启动并创建上下文对象,启动 JS Runtime ,进行布局,将JS端的代码通过C++层, UIManagerMoodule 转化成 Android 组件,再进行渲染,最后将渲染的View添加到 ReactRootView 上,最终呈现在用户面前。

    展开全文
  • (一)前言 今天我们一起来看一下Image组件的... 刚创建的React Native技术交流群(282693535),欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送! Imag

    转载请标明出处:

    http://blog.csdn.net/developer_jiangqq/article/details/50557632

    本文出自:【江清清的博客】


    ()前言

            【好消息】个人网站已经上线运行,后面博客以及技术干货等精彩文章会同步更新,请大家关注收藏:http://www.lcode.org 

          今天我们一起来看一下Image组件的相关使用讲解以及模仿实现一下美团首页顶部分类的效果。具体环境搭建以及相关配置的请查看之前的相关文章。

            刚创建的React Native技术交流3群(496508742),React Native技术交流4群(458982758),欢迎各位大牛,React Native技术爱好者加入交流!同时博客左侧欢迎微信扫描关注订阅号,移动技术干货,精彩文章技术推送!

             Image是一个现实多种不同类型图片的React组件,可以进行加载网络图片,本地资源图片,打包的APP中的图片资源,以及磁盘例如:相册中的图片。

    ()Image基本用法

             2.1.加载项目资源图片

    0.14版本开始react native支持加载我们项目目录中的图片资源,我现在在测试项目中创建一个img目录,在里边加入my_icon.png图片.,那么可以通过以下方式进行访问:

     <View style={{marginLeft:10,marginTop:10}}>
           <Text style={{fontSize:16}}>'测试本地图片'</Text>
           <Image source={require('./img/my_icon.png')} />
          </View>

    运行结果如下:


             该图片资源文件的查找和JS模块相似,该会根据填写的图片路径相对于当前的js文件路径进行搜索。RN更加好的是Packager会根据平台选择相应的文件,例如:my_icon.ios.pngmy_icon.android.png两个文件(命名方式android或者ios)。该会根据android或者ios平台选择相应的文件。

            对于iOS开发来讲,大家肯定会知道我们经常可以设置@2X@2X等格式的图片来进行适配手机屏幕,例如:my_icon@2x.png或者my_icon@3x.png。这样的话Packager进行打包的时候会根据屏幕的不同密度进行选择显示对应的图片。如果没有恰好的满足当前屏幕的分辨率,那么会选择最接近的那个图片资源。

             [注意].这边使用Image组件,require中的图片名称必须为一个静态的字符串信息。不能在require中进行拼接。例如:

     <Image source={require('./img/my_icon'+'.png')} />

    这样之后运行就报错了:


       

          2.2.加载使用APP中的图片

      现阶段做原生APP的需求还是比较多的,不过现在使用了React  Native之后,我们可以进行混合开发APP(一部分采用ReactNative,另一部分采用原生平台代码).甚至可以使用已经打包在APP中的图片资源(例如:xcode asset文件夹以及Android drawable文件夹)

     例如如下代码我们获取android项目中的app_icon图片,并且设置图片的尺寸带40x40

    <Image source={{uri:'ic_launcher'}} style={{width: 40, height: 40}} />

               不过如果要显示效果:希望大家做如下修改,因为现在android项目采用gradle,现在不会默认生成drawable文件夹中了,所以大家如果要演示效果,需要在res下面新建一个drawable文件夹,然后放一个图片进入,接着在重新打包运行即可(这边测试的是把ic_launcher.png图片放入到res/drawable文件夹中)。不过经测试drawable-hdpi这类的不同分辨率格式文件夹也可以运行。

    该适用于调试版本,如果采用发布版本就需要例如'image!xx.png'格式的访问方式了

    运行效果如下:


          2.2.加载使用APP中的图片

     客户端的很多图片资源基本上都是实时通过网络进行获取的,该写法和上面的加载本地资源图片的方式不太一样,这边一定需要指定图片的尺寸大小,具体代码示例代码如下:

    <Image source={{uri:'http://img2.xxh.cc:8080/images/ZTT_1404756641470_image.jpg'}}  style={{width:100,height:100}}/>

    加载网络图片效果如下:


         2.3.Image实现某些控件的背景图效果

     React Native中支持嵌套的方式,例如我们现在有一个Text组件,假如要实现背景图的效果,那么可以使用Image嵌套的Text的方式,然后Image加载图片方式实现,例如代码如下:

    <Image source={require('./img/my_icon.png')} >
               <Text style={{color:'red'}}>下面是背景图</Text>
    </Image>

    具体掩饰效果如下:我们发现Text组件文本底部是一个图片的背景


    ()Image属性方法

           1.onLayout   (function) Image布局发生改变的,会进行调用该方法,调用的代码为:

    {nativeEvent:{layout: {x, y, width, height}}}.

           2.onLoad (function):当图片加载成功之后,回调该方法

           3.onLoadEnd (function):当图片加载失败回调该方法,该不会管图片加载成功还是失败

           4.onLoadStart (fcuntion):当图片开始加载的时候调用该方法

           5.resizeMode  缩放比例,可选参数('cover', 'contain', 'stretch') 该当图片的尺寸超过布局的尺寸的时候,会根据设置Mode进行缩放或者裁剪图片

           6.source {uri:string} 进行标记图片的引用,该参数可以为一个网络url地址或者一个本地的路径    

    ()Image样式风格

           1.FlexBox  支持弹性盒子风格

           2.Transforms  支持属性动画                3.resizeMode  设置缩放模式

           4.backgroundColor 背景颜色

           5.borderColor     边框颜色              6.borderWidth 边框宽度

           7.borderRadius  边框圆角   

           8.overflow 设置图片尺寸超过容器可以设置显示或者隐藏('visible','hidden')

           9.tintColor  颜色设置         10.opacity 设置不透明度0.0(透明)-1.0(完全不透明)

    ()Image实例-仿照美团首页顶部分类

           下面我们模仿一下美团首页的顶部分类的效果,也算是总结了前面所学的View,Text和今天的Image组件,具体代码如下:

    /**
     * 模仿美团首页顶部分类效果
     * Sample React Native App
     * https://github.com/facebook/react-native
     */
    'use strict';
    import React, {
      AppRegistry,
      Component,
      StyleSheet,
      Text,
      View,
      Image,
    } from'react-native';
    class TestImage extends Component {
      render() {
        return (
          <View style={{marginLeft:5,marginTop:10,marginRight:5}}>
         
             <View style={{flexDirection:'row'}}>
                 <View style={{width:70}}>
                     <Image source={require('./img/one.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,textAlign:'center',fontSize:11,color:'#555555'}}>美食</Text>
                 </View>
                  <View style={{width:70}}>
                     <Image source={require('./img/two.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>电影</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/three.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>酒店</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/four.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>KTV</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/five.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>外卖</Text>
                 </View>
              </View>
              <View style={{flexDirection:'row',marginTop:10}}>
                 <View style={{width:70}}>
                     <Image source={require('./img/six.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,textAlign:'center',fontSize:11,color:'#555555'}}>优惠买单</Text>
                 </View>
                  <View style={{width:70}}>
                     <Image source={require('./img/seven.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>周边游</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/eight.png')}style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>休闲娱乐</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/nine.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>今日新单</Text>
                 </View>
                 <View style={{width:70}}>
                     <Image source={require('./img/ten.png')} style={{alignSelf:'center',width:45,height:45}} />
                     <Text style={{marginTop:5,alignSelf:'center',fontSize:11,color:'#555555',textAlign:'center'}}>丽人</Text>
                 </View>
              </View>
          </View>
        );
      }
    }
    AppRegistry.registerComponent('TestImage',() => TestImage);

    注以上的代码的样式没有重构 单独用StyleSheet写,具体运行效果如下:


    ()最后总结

              今天我们主要给大家介绍Image组件,以及通过一个具体实例把之前的View和Text组件的基本使用串联了一下。大家有问题可以加一下群React Native技术交流群(282693535)或者底下进行回复一下。

              尊重原创,转载请注明:From Sky丶清(http://blog.csdn.net/developer_jiangqq) 侵权必究!

           关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

         关注我的微博,可以获得更多精彩内容

          

     

    展开全文
  • 一:Image组件的基本用法1.1从当前项目(即本地)中加载图片 加载本地图片 ('./img/1.jpg')} style={styles3.img1} /> 这时候的图片可以放在根目录,也就是和inde
  • React Native之原理浅析

    2019-10-12 10:48:45
    React Native的核心驱动力就来自于JS Engine. 你写的所有JS和JSX代码都会被JS Engine来执行, 没有JS Engine的参与,你是无法享受ReactJS给原生应用开发带来的便利的。在iOS上,默认的就是JavaScriptCore, iOS 7之.....
  • 在开发Android的时候,一般我们会有一些加载网页的需求,或者执行一些JavaScript,我们都知道在Android中实现这个功能的控件是WebView,在ReactNative中也有实现此类需求额的组件,它的名字也是WebView。那么今天的这...
  • React Native之七牛

    2017-12-05 11:21:26
    但是对于React Native来说,不知道多不多。但是这是我公司这边使用的是七牛的。https://github.com/qiniu/react-native-sdk 然后这个连接是七牛的官方SDK。但是下来之后使用,你会发现很多蛋疼的地方,是官方文档不...
  • 今天讲一下Image,其实在React Native里面Image的使用方式多种多样啊。现在贴一下代码来讲解一下 其中的source里面有俩种使用方式一种是这个网络图片的,这个就没什么好说吧。uri加上连接。 其中讲另一个问题就是...
  • 理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码的一些感触,主要总结了 React Native 启动流程、JS 调用 Java 流程、Java 调用 JS 流程。
  • react-native-video是github上一个专用于React Native做视频播放的组件。这个组件是React Native上功能最全最好用的视频播放组件,还在持续开发之中,虽然还有些bug,但基本不影响使用,强力推荐。 本篇文章主要介绍...
  • 由于新版本(简称2.0版本)的APP使用新技术ReactNative新技术,并且时间紧张,只有公共部分,如登录、联系人等公共部分使用ReactNative重新做的,但是之前的一些旧模块(发文、收文、出差、签报、信息发布等)就没有...
  • QQ空间终端开发团队 Facebook 在2015.9.15发布了 React ... Native 让开发者使用 JavaScript 和 React 编写应用,利用相同的核心代码就可以创建 基于Web,iOS 和 Android 平台的原生应用。本文将浅析Andr
  • React Native常用组件之Image
  • 开源项目地址:...该组件进行封装成React Native平台播放器Video组件,大家可以使用该组件进行播放视频啦~不过支持React Native的版本最低0.19版本。 刚创建的Reac
  • 前言学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所偏差,在学习中如果有错会及时修改...
  • 做过RN的童鞋都知道,RN上官方的视频组件是react-native-video。然而,官方的文档的demo并不是那么详尽,踩了一身的坑,仍然和理想中的视频播放器相去甚远。本文会完成一个基本的视频播放器,包含: 全屏切换 ...
1 2 3 4 5 ... 20
收藏数 1,149
精华内容 459