尝试 React Native Android 的辛酸历程

叁叁肆2018-10-18 14:44

此文已由作者王宇飞授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。



React Native 出来已经有相当长一段时间了,相比于 H5 页面令人揪心的性能, React Native 在性能上接近原生代码的表现确实让人惊艳。大白话来说,React Native 用 JavaScript 能完成传统原生代码的功能,实现了跨平台开发,更重要的是对代码的动态更新的意义。之前听说阿里在双11活动中应用到了 React Native 的相关技术,并且提出了无线电商动态化解决方案 Weex,暂且不论外界对于 Weex 是如何评价的,现在倒是时候尝试一下 React Native 了。

React Native 对于 Android 的友好度远不及 iOS,对于 Windows 平台的友好度也远不及 Linux 和 Mac OS。在使用过程中也是坑坑洼洼无数,考虑到我厂不少 Android 开发童鞋使用的平台是 Windows,所以后面的内容两个平台都会提到。

大部分内容都是基于官方的文档,但是官方文档目前在很多地方并不完善,有坑的地方不少,我会把遇到的坑都特别说明出来。

准备工作

  • JDK(基本废话...)

  • Android SDK(同上,但最好将 ANDROID_HOME 写入 path 中,不写也行,不过以后会略微麻烦)

  • Node.js (官方要求 4.0 以上版本) Windows 下还要注意配置环境变量

后文中不少操作执行的时间都比较长,需要耐心等...

简单的官方例子

使用起来很简单,其实就是官方文档中列出的三步:

npm install -g react-native-cli  

react-native init AwesomeProject  

react-native run-android  

简单来说就是:安装 react-native 命令行工具 -> 初始化项目 -> 运行
Windows 下注意下面提到的 “坑2”

坑1:【网络环境】:  由于“众所周知”的原因,各个步骤都建议自备梯子

坑2:Windows 下 Packager 的启动  根据官方的说法,Packager 是一个模块管理者,当前阶段直观来看,它使得 App 在调试阶段从远端(也就是你的电脑)获取需要加载的 js。如果在 Mac 下,执行 react-native run-android 后 Packager 会自动运行,而在 Win 下则不会。所以,切记!!!【 Win 里在执行 run-android 之前务必先在项目目录下执行 react-native start】
对应到例子中就是:
cd AwesomeProject
react-native start
如下图即为启动成功:

要保持 Packager 一直运行,所以,不能关闭当前 CMD 窗口。

坑3:调试服务器配置  通常首次运行会失败出现红色屏幕,提示 Can't find variable: __fbBatchedBridge 或 unable to download js bundle。

出错的主要原因在于 App 无法与 Packager 通信,比如,公司电脑接入公司有线网络,调试用的真机接入 Netease Wi-Fi,默认的配置下是无法调试的,需要在 Packager 已经成功启动的前提下,在红色屏幕出现时点击菜单按键或者摇动手机,出现菜单后选择 Dev Settings,进入 Debug server host & port for device,输入 <电脑IP>:8081,然后返回,然后再次进入菜单选 Reload JS。有的时候即使这样也不会成功,请检查电脑的 8081 端口是否被其他进程占用。

坑4: 注册设置 Activity 摇手机出现菜单后点击 Dev Settings 还有可能 Crash,这是因为还需要在 Manifest 设置界面对应的 Activity :
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

集成到自己的项目里

首先,集成 React Native 需要 API 16+

这才是实际使用中最常用到方式,毕竟依靠纯 React Native 实现一个 App 的情形并不多。
首先,在 app.gradle 中引入依赖:

compile 'com.facebook.react:react-native:0.20.0' (写这篇文章时的最新版本)
在 Manifest 中声明权限:(调试用,如果实际不需联网可在发布时去除这个权限)
<uses-permission android:name="android.permission.INTERNET" />
然后在需要使用 React Native 的地方做如下处理:

01  public class MainActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
02  
03     private ReactInstanceManager mReactInstanceManager;
04 
05     @Override
06     protected void onCreate(Bundle savedInstanceState) {
07         super.onCreate(savedInstanceState);
08         setContentView(R.layout.activity_main);
09         if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
10             Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
11            startActivity(serviceIntent);
12             finish();
13         } else {
14             ReactRootView reactRootView = (ReactRootView) findViewById(R.id.react_view);
15             mReactInstanceManager = ReactInstanceManager.builder()
16                     .setApplication(getApplication())
17                     .setBundleAssetName("index.android.bundle")
18                     .setJSMainModuleName("index.android")
19                     .addPackage(new MainReactPackage())
20                     .setUseDeveloperSupport(BuildConfig.DEBUG) // set DEBUG to true!!!
21                     .setInitialLifecycleState(LifecycleState.RESUMED)
22                     .build();
23             reactRootView.startReactApplication(mReactInstanceManager, "DemoApp", null);
24         }
25     }
26 
27     @Override
28     public void invokeDefaultOnBackPressed() {
29         super.onBackPressed();
30     }

    ...

DefaultHardwareBackBtnHandler 接口以及其  invokeDefaultOnBackPressed 用于处理返回按键按下对 React Native 层面的影响,如果 React Native 界面不需要对返回事件作特殊处理,直接执行系统返回事件即可。

类似于 WebView, React Native 用于显示界面的控件为 ReactRootView,其本质上为 FrameLayout。

【第 9 - 13 行代码】由于 Android 6.0+ 增强了权限管理,所以在 API 23+ 时需要额外请求 OVERLAY_PERMISSION,在弹出的界面中将当前应用设为“允许”,然后再次启动。否则就没办法显示红色的错误提示并且 crash。这个问题在 API 22- 中不存在。这个处理也仅仅针对调试环境,正式环境可以去掉。

ReactInstanceManager 用于管理 CatalystInstance 对象, CatalystInstance 对象简单来说就是提供了 JavaScript 调用以及通过 JavaScript 调用本地 Java 方法的环境的对象, CatalystInstance 本身是一个接口。由于 ReactInstanceManager 管理着对象的生命周期,因此它需要与其所在的 Activity/Fragment 生命周期相关连,如下代码是必须的,类似的还有 onResume, onBackPressed 等。

@Overrideprotected void onPause() {  
 super.onPause();   
 if (mReactInstanceManager != null) {
       mReactInstanceManager.onPause();
   }
}

最后,通过 startReactApplication 启动相应的 React Application,第二个参数为 React JS 模块名。

Java 代码写完以后,进入项目的根目录,依次执行:
npm init
npm install --save react-native
curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig  

整个过程耗时比较长,其中npm init 执行时需要输入一些信息,官方并没有特殊要求,大部分默认即可。

完成后,打开 packge.json, 删除 scripts 下的内容,并填入:
"start": "node_modules/react-native/packager/packager.sh"
【注意】 Windows 下不需要这一步

然后在根目录下创建一个名为 index.anroid.js 的 js 文件,填入如下 示例内容:

'use strict';

var React = require('react-native');
var {
  Text,
  View
} = React;class DemoApp extends React.Component {
  render() {    return (
      <View style={styles.container}>
        <Text style={styles.hello}>React TextView</Text>
      </View>
    )
  }
}
var styles = React.StyleSheet.create({  container: {    flex: 1,    justifyContent: 'center',
  },  hello: {    fontSize: 25,    textAlign: 'center',    margin: 10,
  },
});

React.AppRegistry.registerComponent('DemoApp', () => DemoApp);

一切 OK 后,进入 Android Studio, 在 Terminal 中输入 npm start (Mac OS)  或者 react-native start(Windows),点击运行,第一次设置好调试服务器的 IP 与端口, Reload JS, 即可成功运行,如图。


坑5:BuildConfig.DEBUG  这个坑折腾了足足了半天的时间。官方的示例中直接使用了 setUseDeveloperSupport(BuildConfig.DEBUG) 并没有说明其实他们的 demo 已经重写了 DEBUG 的值,默认 BuildConfig.DEBUG 的值是 false,也就是关闭调试。如果直接按上面的代码实现,在出现诸如 Can't find variable: __fbBatchedBridge 错误时 App 会直接 Crash,没有机会修改 Debug Server 的 IP 和端口。所以,在调用 setUseDeveloperSupport 时可以直接传入 true 或者重写 DEBUG 的值。

坑6: Windows 下的 Visual Studio   在 Windows 下执行 npm install --save react-native 时有一系列的编译操作,需要诸如 VCBuild.exe 等程序, 为了保证执行成功, 需要安装 .NET Framework SDK 或者是 Visual Studio, 并且保证相关编译程序配置的正确。 出于配置上方便的考虑,推荐直接安装一个 Visual Studio =。=

7 Windows 下的 curl   Windows 下默认没有 curl,需要从 curl 官网下载一个 curl.exe 放入 System32 目录下。执行 curl 时还可能会出现证书相关的错误,可以尝试 curl -k -o .....

发布

开发阶段,App 可以通过 Packager 从调试服务器获取 js 等资源,App 发布时,js 等资源需要打入 apk 中,这个过程自然需要些额外工作。

如果 React Native 是通过 react-native init <Project Name> 生成的,正常情况下项目中会存在 react.gradle(如果没有,请升级 react-native 版本,当前 master 上的版本已经没有什么问题了,升级方法这里就不说了)。然后,直接执行 ./gradlew assembleRelase 即可。

如果 React Native 是集成到之前的原生项目中的,那项目中并不会存在 react.gradle 这个文件,这个时候就需要手动生成 assets 目录和 bundle 文件,从而将 js 等相关资源文件打入 apk 之中。目前这些可以通过插件或者命令行的方式进行,官方倾向于使用 react-native bundle。在开发完毕所有功能测试通过后,在 Android Studio 的 Terminal 中执行如下命令:

  1. 创建 assets 目录
    mkdir -p app/src/main/assets

  2. 执行 react-native bundle,生成 bundle 文件,执行相关操作
    react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/

一切成功的话在 assets 下出现 bundle 文件。

最后,在配置好 gradle 中相关签名信息后, 执行./gradlew assembleRelase 获得发布包

react-native bundle 的两个重要参数:

  • --dev 设置 DEV 标记,发布时置为 false

  • --minify 是否对 js bundle 混淆

总结

React Native 目前仍处在 0.xx 的版本中,未来的空间还很大,目前很多商业上的成功应用也说明了它的价值。但是其本身也并不完善,在写这篇文章的过程中就遇到了多次问题,如果有一些难以解决的问题,可以去 React Native 在 Github 上的 issue 里看看。
本文只是简单介绍了 React Native Android 的尝试过程,并没有涉及更深的领域和 React 的具体语法功能等。大家可以从下面的官方文档获取更多的信息。

默认情况下 React Native 是没有办法直接实现远程动态更新代码的,但是好消息是微软推出了 CodePush,有兴趣的话可以继续研究。

时间仓促加之水平有限,如果有任何错误欢迎大家批评指正。

参考


网易云免费体验馆,0成本体验20+款云产品! 


更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 JAVA虚拟机的类加载机制
【推荐】 如何开发一个可运维系统的一点体会