与本机应用程序集成时,React-native 应用程序在每次导航时都会重新启动

Posted

技术标签:

【中文标题】与本机应用程序集成时,React-native 应用程序在每次导航时都会重新启动【英文标题】:React-native app is being restarted on every navigation when integrated with native app 【发布时间】:2019-02-28 22:38:54 【问题描述】:

我们正在尝试将新的 React Native 应用程序集成到现有的原生 android 应用程序中。遵循 RN 官方 docs 我们设法让它工作,但在导航方面存在一些问题。

我们有原生和非原生 (JS) 屏幕,我们需要一种在所有屏幕之间导航的好方法,无论屏幕是否为原生。

我们尝试采用native-navigation 和react-native-navigation 来看看是否有任何解决我们的问题,但它们都没有真正起作用。

目前,我们已经像这样注册了所有 RN 屏幕:

const provide = (store, Screen) => 
      return props => (
        <Provider store=store>
          <Screen ...props />
        </Provider>
      );
    ;

    const store = configureStore();

    AppRegistry.registerComponent('Home', () => provide(store, HomeComponent));

我们还创建了一个名为“Navigator”的本机模块,该模块具有名为openComponent 的导航方法,该方法接受屏幕名称及其道具。以下是openComponent 的实现方式:

// our native module code ...
     @ReactMethod
     public void openComponent(String name, String extra) 

         try 
             Intent intent = new Intent(this.getReactApplicationContext(), MyReactActivity.class);
             intent.putExtra("moduleName", name);
             intent.putExtra("extra", extra);

             getCurrentActivity().startActivityForResult(intent, 0);
         
         catch (Exception e) 
             e.printStackTrace();
             Crashlytics.logException(e.getCause());
         
     

然后,每当我们想在 RN 端导航时,我们只需使用目标屏幕道具调用我们的自定义导航器。

当前方法的问题是,每当我们导航到基于 RN 的屏幕时,RN 部分都会重新启动,这会导致 Redux 存储为空。

下面是我们的 ReactActivity.java 类的“onCreate”方法的样子:

@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);

        Bundle initialProperties = new Bundle();
        initialProperties.putString("loginToken", HJSession.getSession().getSessionId());
        initialProperties.putString("username", HJSession.getSession().getUserName());
        initialProperties.putString("userId", HJSession.getSession().getUserId().toString());

        String moduleName = "topics";
        Bundle bundle = getIntent().getExtras();

        if (bundle != null) 
            moduleName = bundle.getString("moduleName");
            try 
                String extra = bundle.getString("extra");
                initialProperties.putString("extra", extra);
            
            catch (Exception e) 
                e.printStackTrace();
                Crashlytics.logException(e.getCause());
            
        

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setJSMainModulePath("index")
                .addPackages(Arrays.<ReactPackage>asList(
                        new MainReactPackage(),
                        new RNFirebasePackage(),
                        new RNFirebaseMessagingPackage(),
                        new RNFirebaseNotificationsPackage(),
                        new RNI18nPackage(),
                        new VectorIconsPackage(),
                        new HJRNPackages(),
                        new NativeNavigationPackage()
                ))
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();

        mReactRootView.startReactApplication(mReactInstanceManager, moduleName, initialProperties);

        setContentView(mReactRootView);
    

【问题讨论】:

当你从 Native 到 React 时,你正在经历 Redux 的变化,对吧?还是在 React Navigation 中?如果您要从 N 更改为 RN,那么您不应该在本地保留您的 Redux 状态吗?我只是要求澄清。 @AshwinMothilal 好吧,在我们的例子中,不适合在每次导航时都在本地(在硬盘上)保存我们的状态。但是RN部分无论如何都不应该重新启动,对吧? 你还没有回答这部分,当你从 Native 到 React 时,你正在经历 Redux 的变化,对吧?还是在 React Navigation 中? 是的,当我们从 Native 转到 React 以及从 React 到另一个 React 屏幕时,我们会遇到重新加载,因为我们使用的是上面原始问题中提到的相同“openComponent”函数,谢谢 我希望您在gitlabgithub 上为您的问题上传一个复制存储库 【参考方案1】:

ReactActivity.java 中可以查看Bundle savedInstanceState

...为了控制 React 应用程序何时被实例化:

@Override
protected void onCreate(Bundle savedInstanceState) 

    super.onCreate(savedInstanceState);

    /* it's always NULL on first run */
    if (savedInstanceState == null) 

        Bundle initialProperties = new Bundle();
        initialProperties.putString("loginToken", HJSession.getSession().getSessionId());
        initialProperties.putString("username", HJSession.getSession().getUserName());
        initialProperties.putString("userId", HJSession.getSession().getUserId().toString());

        String moduleName = "topics";
        Bundle bundle = getIntent().getExtras();

        if (bundle != null) 
            moduleName = bundle.getString("moduleName");
            try 
                String extra = bundle.getString("extra");
                initialProperties.putString("extra", extra);
             catch (Exception e) 
                Crashlytics.logException(e.getMessage());
                Log.e("ReactActivity", e.getMessage());
            
        

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(getApplication())
            .setJSMainModulePath("index")
            .addPackages(Arrays.<ReactPackage>asList(
                new MainReactPackage(),
                new RNFirebasePackage(),
                new RNFirebaseMessagingPackage(),
                new RNFirebaseNotificationsPackage(),
                new RNI18nPackage(),
                new VectorIconsPackage(),
                new HJRNPackages(),
                new NativeNavigationPackage()
            ))
            .setUseDeveloperSupport(BuildConfig.DEBUG)
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build();

        mReactRootView.startReactApplication(mReactInstanceManager, moduleName, initialProperties);
        setContentView(mReactRootView);
    

【讨论】:

赞成,但在React Native 中最好不要使用本机解决方案。感谢您的帮助。 @AngelHotxxx 问题不是什么更好(...),而是如何防止 React 应用重新启动(原因是,onCreate() 可能不止一次运行)。 【参考方案2】:

实际上,对于您的问题案例,您应该在 Gitlab 或 Github 上上传一小部分有此问题的项目并将其链接放在这里,因此我们可以提供更好的帮助。

确实,我是 javascriptReactReact Native 开发人员,我无法帮助本地方面的任何人,但毫无疑问,我相信您和您的同事为您的应用程序选择了错误的方式。

React Native 是一个不稳定的JavaScript 项目,其本地代码不稳定且会随时间变化,因此您应该使用JavaScript 编写所有功能。就像Sophie Albert 在this article 中所说,他们想对React Native 做出重大改变,所以,最好所有代码都用JavaScript 编写而不是本机(Java,Objective C)。

起初,我认为您在react-native-navigation 上的选择是错误的。为什么不使用react-navigation

因为 99.7% 的 react-navigation 基于 JavaScript 并由 Facebook 团队更改原生端,所以不会影响您的项目,开发和调试非常容易。当然,您可以使用 Redux 这样的每个趋势库,因为您的项目基于 JavaScript

我和我的同事正在为Sheypoor 开发一个大型的React Native 应用程序,除了基于JavaScript 的所有应用程序的启动屏幕之外,在我们的输入测试中,我们甚至没有遇到任何崩溃或错误或不需要的重启。

如果可能,请将导航回滚到完整的 JavaScript 导航库,例如我们选择的 react-navigation。如果您上传了一个复制存储库,我可以比这种情况更好地帮助您。但是我放了一些我们的代码结构来帮助你回滚到react-navigation:

我们项目的index.js

import  AppRegistry  from 'react-native';
import App from './app/App';
import  name as appName  from './app.json';

AppRegistry.registerComponent(appName, () => App);

我们应用的根文件,App.js 文件:

import React from 'react';
import  Provider  from 'react-redux';
import RootNavigation from './RootNavigation';
import  onNavigationStateChange  from './utils/routes';
import configureStore from './redux/configureStore';

const store = configureStore();

const App = () => (
  <Provider store=store>
    <RootNavigation onNavigationStateChange=onNavigationStateChange />
  </Provider>
);

export default App;

RootNavigation.js 文件,但现在不是用于我们之前的提交。由于它的复杂性,我不放较新的版本:

import  createSwitchNavigator  from 'react-navigation';
import  Loading, Dashboard, SignInStack, ListingManagement  from './screens';

const RootNavigation = createSwitchNavigator(
  
    Loading,
    SignInStack,
    Dashboard,
    ListingManagement
  ,
  
    initialRouteName: 'SignInStack'
  
);

export default RootNavigation;

最后,package.json 的早期版本:


  "name": "sheypoor",
  "version": "0.0.1",
  "private": true,
  "scripts": 
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "android": "npx react-native run-android",
    "ios": "npx react-native run-ios",
    "physical-android": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res",
    "test": "jest",
    "eslint": "eslint .",
    "clean": "react-native-clean-project",
    "pre-commit": "lint-staged"
  ,
  "lint-staged": 
    "*.js": [
      "eslint --fix ."
    ]
  ,
  "dependencies": 
    "formik": "^1.3.0",
    "lint-staged": "^7.3.0",
    "prop-types": "^15.6.2",
    "react": "16.5.0",
    "react-native": "0.57.1",
    "react-native-confirmation-code-field": "^1.2.2",
    "react-native-vector-icons": "^5.0.0",
    "react-navigation": "^2.16.0",
    "react-redux": "^5.0.7",
    "redux": "^4.0.0",
    "yup": "^0.26.6"
  ,
  "devDependencies": 
    "babel-eslint": "^9.0.0",
    "babel-jest": "23.6.0",
    "babel-plugin-module-resolver": "^3.1.1",
    "babel-plugin-root-import": "^6.1.0",
    "eslint": "^5.5.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^3.0.1",
    "eslint-import-resolver-babel-plugin-root-import": "^1.1.1",
    "eslint-plugin-flowtype": "^2.50.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.1.1",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-react": "^7.11.1",
    "eslint-plugin-react-native": "^3.3.0",
    "eslint-plugin-sort-imports-es6-autofix": "^0.3.0",
    "flow-bin": "^0.78.0",
    "jest": "23.6.0",
    "metro-react-native-babel-preset": "0.45.6",
    "prettier": "^1.14.3",
    "react-native-clean-project": "^3.0.0",
    "react-native-config": "^0.11.5",
    "react-test-renderer": "16.5.0",
    "redux-devtools-extension": "^2.13.5"
  ,
  "jest": 
    "preset": "react-native"
  ,
  "rnpm": 
    "assets": [
      "./app/assets/fonts"
    ]
  

有了这些代码和配置,我们连一个错误都没有。

【讨论】:

以上是关于与本机应用程序集成时,React-native 应用程序在每次导航时都会重新启动的主要内容,如果未能解决你的问题,请参考以下文章

react-native 应用程序视图加载时,如何在本机 ıos 上调用 ViewController 类?

如何将 FCM(firebase 云消息传递)与 react-native 集成

React-native 和 Firebase ListView 集成

尝试启动反应本机应用程序时出错

初始化本机ReactLocalization模块时出错了。请检查您的配置:您是否运行'react-native link'?

从使用 Expo SDK 构建的 React-Native 应用程序将照片分享到 Instagram