欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

集成 React Native 到现有Android项目

程序员文章站 2022-07-02 22:08:23
...

ReactNative系列-Android混合开发(一)

ReactNative混合开发,Facebook官网文档比较精简,坑比较多,根据此文档,作为初学者并不能顺利的将ReactNative引入Android项目,且不能了解ReactNative是以一种怎样的形式存在Native项目中。

此为开篇,解说官方引入方案,附带了我的踩坑经验,引导小诀窍,让你傻瓜式一键集成ReactNative。后续文章会介绍云集项目中React的实践,优化方案。这是一篇入门篇,没有门槛,何来高楼。

希望这一个系列的React文章能够帮助大家快速了解ReactNative的混合开发与掌握云集App中React的使用,快速定位相关问题,人人成为ReactNative小能手。

本文需要有初步的React Native基础,至少有能够运行一个React Native demo的环境与项目。

适用人群:Android工程师 Or 对ReactNative感兴趣的开发者

加载原理

(1)、Facebook有能够将页面的JS,通过自研的Metro工具转化一个bundle的压缩文件;
(2)、同时开发了一套能够解析Bundle文件中JS转化为Native行为的黑盒子工具API;
(3)、集成至于Native的根本原理,就是将Facebook的黑盒API引入到Android项目,同时能够加载Bundle文件;
(4)、集成ReactNative到原生项目分两大步:第一步引入依赖,第二步初始化ReactNative,加载页面;

1、正确引入ReactNative相关依赖

1.1、准备,组建一个React Native项目

  • 首先按照开发环境搭建教程来安装 React Native 在 Android 平台上所需的一切依赖软件,创建一个React项目,
//初始化创建项目-AwesomeProject 
npm init AwesomeProject
//下载React相关依赖,React Native (JS, Android binaries)等
npm install 
//安装项目
react-native run-android

项目中package.json文件,可修改指定的React Native版本号

  • 利用Android Studio模式打开AwesomeProject项目,编译Gradle

小技巧:成功编译后,对引入第三方lib,打包成aar,非常便捷

1.2、集成React Native

  • Android项目Lib or App下创建node_modules\react-native\android目录,并从AwesomeProject项目中node_modules\react-native\android目录下所有的文件拷贝至该目录

    集成 React Native 到现有Android项目

1.3、集成配置 maven

  • 你的 app 中 build.gradle 文件中添加 React Native 依赖:
dependencies {
    implementation 'com.android.support:appcompat-v7:27.1.1'
    ...
    //如要指定特定的 React Native 版本,可以用具体的版本号替换 +,当然前提是你从 npm 里下载的是这个版本。
    implementation "com.facebook.react:react-native:+" // From node_modules
}
  • 在项目的 build.gradle 文件中为 React Native 添加一个 maven 依赖的入口,必须写在 “allprojects” 代码块中:

    allprojects {
        repositories {
            maven {
                // All of React Native (JS, Android binaries) is installed from npm
                //上述项目中引入react-native的相对目录位置
                url "$rootDir/xxx/node_modules/react-native/android"
            }
            ...
        }
        ...
    }
    

$rootDir表示项目根目录

1.4、集成React Native的基础Lib

  • 你的 app 中 build.gradle 文件中添加-图片依赖
dependencies {
    ...
    //匹配图片框架与对应的版本号
    implementation 'com.facebook.fresco:animated-gif:2.0.0'
    implementation "com.facebook.fresco:webpsupport:2.0.0"
}

匹配版本号,从引入的ReactNative文件中,找出react-native-0.61.3.pom,查询对应的fresco的版本号

//打开此文件node_modules\...\react-native\react-native-0.61.3.pom查看各个依赖版本号
...
<dependency>
      <groupId>com.facebook.fresco</groupId>
      <artifactId>imagepipeline-okhttp3</artifactId>
      <version>2.0.0</version>
      <scope>compile</scope>
</dependency>
<dependency>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>okhttp</artifactId>
     <version>3.12.1</version>
     <scope>compile</scope>
</dependency>
...
  • 引入第三方lib【推荐AAR方式 or 源码方式 】
//app 中 `build.gradle` 文件中添加 React Native 依赖
dependencies {
    ...
    implementation(name: 'react-native-video-5.0.2', ext: 'aar')
    implementation(name: 'react-native-vector-icons-6.6.0', ext: 'aar')
    ..........
}

小技巧:

1、通过Android Studio打开项目,在对应lib中,通过Gradle-assemble指令生成aar

2、第三方依赖某些适合并不完成与你的项目匹配,源码更容易修改调整

2、初始化ReactNative,加载开发页面Bundle

2.1、初始化React

applicatiion中,so初始化,ReactNativeHost初始化

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost =
            new ReactNativeHost(this) {

                /**
                 * 开发模式开发【红屏模式开关】
                 *
                 * @return
                 */
                @Override
                public boolean getUseDeveloperSupport() {
                    return BuildConfig.DEBUG;
                }

                /**
                 * 自定义React package链接处
                 *
                 * @return
                 */
                @Override
                protected List<ReactPackage> getPackages() {
                    @SuppressWarnings("UnnecessaryLocalVariable")
                    List<ReactPackage> packages = new PackageList(this).getPackages();
                    // Packages that cannot be autolinked yet can be added manually here, for example:
                    // packages.add(new MyReactNativePackage());
                    return packages;
                }

                /**
                 *  开发模式,地址路径
                 * @return
                 */
                @Override
                protected String getJSMainModuleName() {
                    return "index";
                }
            };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        /**
         * React相关so初始化
         */
        SoLoader.init(this, /* native exopackage */ false);
    }

   
}

需要在ReactNativeHost下重写getJSBundleFile(),指定bundle路径,否则将加载默认文件【assets://index.android.bundle】

2.2、加载React开发的页面,

public class MainActivity extends ReactActivity {

  @Override
  protected String getMainComponentName() {
    // Js入口AppRegistry.registerComponent中的appKey参数
    return "rnBuyer";
  }
}

到此为止普通的官方版的ReactNative已经集成完成,是不是很简单;【业务适用版集成方式,请关注后续React文章】

2.3、页面加载流程分解

①、React页面继承ReactActivity,逐步往上查看源码,可以看到ReactActivityDelegate.java中

........ 
//要么加载bundle入口
protected void loadApp(String appKey) {
    mReactDelegate.loadApp(appKey);
    getPlainActivity().setContentView(mReactDelegate.getReactRootView());
  }
.....

②、再次深入,facebook内部加载过程,ReactRootView.java呈现

ReactRootView.java
.....
  /**
   * Schedule rendering of the react component rendered by the JS application from the given JS
   * module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the JS
   * context of that manager. Extra parameter {@param launchOptions} can be used to pass initial
   * properties for the react component.
   */
  public void startReactApplication(
      ReactInstanceManager reactInstanceManager,
      String moduleName,
      @Nullable Bundle initialProperties,
      @Nullable String initialUITemplate) {
    
      //...无关代码
    
     //子线程加载bundle
      mReactInstanceManager.createReactContextInBackground();
    
      //...无关代码
  }
.....

③、在深入,ReactInstanceManager类中选择对应的bundle来源,区分开发模式情况,与release情况

-----------------ReactInstanceManager.java
//主线程选择bundle
@ThreadConfined(UI)
private void recreateReactContextInBackgroundInner() {
    Log.d(ReactConstants.TAG, "ReactInstanceManager.recreateReactContextInBackgroundInner()");
    PrinterHolder.getPrinter()
        .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: recreateReactContextInBackground");
    UiThreadUtil.assertOnUiThread();

     //开发者模式,取bundle方式, application中给mUseDeveloperSupport初始化赋值模式
    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()后面React加载会优先检查metro服务器是否打通
          mDevSupportManager.isPackagerRunning(
              new PackagerStatusCallback() {
                @Override
                public void onPackagerStatusFetched(final boolean packagerIsRunning) {
                  UiThreadUtil.runOnUiThread(
                      new Runnable() {
                        @Override
                        public void run() {
                          //metro通,会优先加载服务器的bundle,否则加载本地
                          if (packagerIsRunning) {
                             //服务器bundle
                            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);
                            //本地bundle
                            recreateReactContextInBackgroundFromBundleLoader();
                          }
                        }
                      });
                }
              });
        }
        return;
      }
    }

    //release模式下,加载bundle路径
    recreateReactContextInBackgroundFromBundleLoader();
  }

-----------------release模式下继续加载bundle,ReactInstanceManager.java 
//......无关代码
@ThreadConfined(UI)
private void runCreateReactContextOnNewThread(final ReactInstanceManager.ReactContextInitParams initParams) {
    //.....无关代码
    
    //加载bundle
    final ReactApplicationContext reactApplicationContext =createReactContext(initParams.getJsExecutorFactory().create(),initParams.getJsBundleLoader());
   //.....无关代码
  }  
//.....无关代码
private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor, JSBundleLoader jsBundleLoader) {
       //.....无关代码
      //加载初始化赋值的本地bundle
      catalystInstance.runJSBundle();
     //.....无关代码
  }
 //.....无关代码

-----------------DevServerHelper.java meto服务间相关交互检查实践类
 .... 
//通过http://localhost:8081/status 检测metro是否通顺
public void isPackagerRunning(final PackagerStatusCallback callback)
 ....

④、回顾整个流程,整个流程图如图:

集成 React Native 到现有Android项目

2.4、升级到ReactNative 0.61.3版本采坑

  • 仅支持so的armeabi_v7架构

  • 当Android 4.4系统中报xx.so找不到,但是在Android高版本中加载正常

    解决思路:xx.so与腾讯的xx.so冲突,报xx.so找不到,解析Apk,对比so的Md5后,发现xx.so非facebook提供的so,分析React的so加载流程,走串行链式加载方式

总结:

此次分享内容就是关于快速集成React到Native-Android的方式与踩坑,和解析了bundle在Android APP中是通过怎么流程进行加载的,方便大家快速上手集成,关于在云集项目中ReactNative是怎么封装使用的,见后续分享。

参考文献

2.4、升级到ReactNative 0.61.3版本采坑

  • 仅支持so的armeabi_v7架构

  • 当Android 4.4系统中报xx.so找不到,但是在Android高版本中加载正常

    解决思路:xx.so与腾讯的xx.so冲突,报xx.so找不到,解析Apk,对比so的Md5后,发现xx.so非facebook提供的so,分析React的so加载流程,走串行链式加载方式

总结:

此次分享内容就是关于快速集成React到Native-Android的方式与踩坑,和解析了bundle在Android APP中是通过怎么流程进行加载的,方便大家快速上手集成,关于在云集项目中ReactNative是怎么封装使用的,见后续分享。

参考文献

1、React Native中文官网