‌React Native与Android原生Activity页面跳转全攻略‌

React Native与Android原生Activity页面跳转全攻略

在移动开发领域,React Native(以下简称“RN”)凭借“一套代码,多端运行”的优势迅速流行。但在实际项目中,我们常常需要与 Android 原生模块打通,例如:从 RN 界面直接跳转到某个原生 Activity,或者在原生 Activity 中返回结果后继续在 RN 中处理。本文将围绕以下几个核心场景展开详解,并提供完整代码示例、ASCII 图解与详细说明,帮助你轻松理解并快速上手:

  1. RN 调用原生 Activity(无参/有参)
  2. 原生 Activity 返回结果给 RN
  3. 原生侧启动 RN 界面(Deep Linking 与 Intent)
  4. React Navigation 与原生跳转的结合示例

一、环境与项目准备

  1. React Native 环境

    • 本文示例基于 React Native 0.65+,Node.js v14+,Android Studio 4.0+。
    • 假设项目已经通过 npx react-native init MyApp 成功创建,并能正常运行:

      cd MyApp
      npx react-native run-android
  2. Android 原生环境

    • 使用 Android Studio 打开 MyApp/android 目录。
    • 确保 minSdkVersion ≥ 21,编译 SDK 版本与目标 SDK 版本均为 30 及以上。
  3. 目录结构示例

    MyApp/
    ├── android/              ← Android 原生工程
    │   ├── app/
    │   │   ├── src/
    │   │   │   ├── main/
    │   │   │   │   ├── java/com/myapp/         ← Java 源码
    │   │   │   │   │   ├── MainActivity.java
    │   │   │   │   │   ├── MyApp.java
    │   │   │   │   │   └── MyModule.java        ← 我们将自定义 NativeModule
    │   │   │   │   ├── AndroidManifest.xml
    │   │   │   │   └── res/...
    │   └── build.gradle
    ├── ios/                  ← iOS 工程(本文不涉及)
    ├── index.js
    ├── App.js
    └── package.json
  4. AndroidManifest.xml

    • <application> 节点中,RN 默认的 MainActivity 会包含 <intent-filter>,用于处理 Deep Linking 或启动入口。后续如果我们新建原生页面 SecondActivity,也需要在这里注册。
    <!-- android/app/src/main/AndroidManifest.xml -->
    <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme">
    
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
    
            <!-- Deep Linking 示例方式 -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="myapp" android:host="open" />
            </intent-filter>
        </activity>
    
        <!-- 1. 新增: 原生 SecondActivity 注册 -->
        <activity
            android:name=".SecondActivity"
            android:label="Second Page"
            android:exported="true">
        </activity>
    
    </application>
    • 以上我们在 MainActivity 中配置了 Deep Linking(myapp://open 可唤起 RN 界面)。
    • 同时新建原生 SecondActivity,未来可在 RN 中通过 NativeModule 直接启动它。

二、场景一:React Native 调用原生 Activity(无参/有参)

RN 与原生的交互通常通过 NativeModule 进行桥接。我们需要在 Android 端实现一个自定义 Module,用来封装 startActivity() 的逻辑,再在 RN 端通过 NativeModules 调用。

2.1 在 Android 原生端:创建 MyModule.java

  1. 新建 Java 文件
    android/app/src/main/java/com/myapp/ 下新建 MyModule.java

    // android/app/src/main/java/com/myapp/MyModule.java
    package com.myapp;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.util.Log;
    
    import androidx.annotation.NonNull;
    
    import com.facebook.react.bridge.ActivityEventListener;
    import com.facebook.react.bridge.Arguments;
    import com.facebook.react.bridge.Callback;
    import com.facebook.react.bridge.Promise;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.WritableMap;
    
    public class MyModule extends ReactContextBaseJavaModule implements ActivityEventListener {
    
        private static final String TAG = "MyModule";
        private static final int REQUEST_CODE = 1234;
        private Promise mPendingPromise;
    
        public MyModule(@NonNull ReactApplicationContext reactContext) {
            super(reactContext);
            reactContext.addActivityEventListener(this);
        }
    
        @NonNull
        @Override
        public String getName() {
            return "MyModule"; // RN 端通过 NativeModules.MyModule 访问
        }
    
        /**
         * 2.1.1 无参启动 SecondActivity
         */
        @ReactMethod
        public void startSecondActivity() {
            Activity currentActivity = getCurrentActivity();
            if (currentActivity == null) {
                Log.e(TAG, "Activity 为空,无法跳转");
                return;
            }
            Intent intent = new Intent(currentActivity, SecondActivity.class);
            currentActivity.startActivity(intent);
        }
    
        /**
         * 2.1.2 带参数启动,并希望获取返回结果 (startActivityForResult)
         * @param message 要传递的字符串参数
         * @param promise  用于回调给 JS 端结果
         */
        @ReactMethod
        public void startSecondActivityForResult(String message, Promise promise) {
            Activity currentActivity = getCurrentActivity();
            if (currentActivity == null) {
                promise.reject("ACTIVITY_NULL", "Activity 为空,无法跳转");
                return;
            }
            mPendingPromise = promise; // 保存 promise,待 onActivityResult 回调时使用
    
            Intent intent = new Intent(currentActivity, SecondActivity.class);
            intent.putExtra("message", message);
            currentActivity.startActivityForResult(intent, REQUEST_CODE);
        }
    
        // 2.1.3 onActivityResult 回调处理
        @Override
        public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
            if (requestCode == REQUEST_CODE) {
                if (mPendingPromise == null) return;
                if (resultCode == Activity.RESULT_OK) {
                    String result = data.getStringExtra("result");
                    WritableMap map = Arguments.createMap();
                    map.putString("result", result);
                    mPendingPromise.resolve(map);
                } else {
                    mPendingPromise.reject("RESULT_ERROR", "SecondActivity 返回失败");
                }
                mPendingPromise = null;
            }
        }
    
        @Override
        public void onNewIntent(Intent intent) {
            // 不需要处理
        }
    }
    • getName():返回给 JS 侧使用的模块名,此处命名为 "MyModule"
    • startSecondActivity():无参启动。
    • startSecondActivityForResult(String, Promise):带一个 message 参数并期望在 SecondActivity 结束时通过 Promise 将结果回调给 JS。
    • onActivityResult(...):当原生 Activity 返回结果时,使用 mPendingPromise.resolve(...)reject(...) 将结果传回 JS。
  2. 新建 SecondActivity.java
    在同一目录下新建 SecondActivity.java,用于测试跳转与返回。

    // android/app/src/main/java/com/myapp/SecondActivity.java
    package com.myapp;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import androidx.annotation.Nullable;
    
    public class SecondActivity extends Activity {
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second); // 我们示例将新建对应布局
    
            TextView tvMessage = findViewById(R.id.tv_message);
            Button btnReturn = findViewById(R.id.btn_return);
    
            // 获取从 RN 传过来的 message
            String message = getIntent().getStringExtra("message");
            if (message != null) {
                tvMessage.setText("来自RN的参数: " + message);
            } else {
                tvMessage.setText("Hello from SecondActivity");
            }
    
            // 点击按钮后返回结果给 RN
            btnReturn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent resultIntent = new Intent();
                    resultIntent.putExtra("result", "原生Activity返回的数据");
                    setResult(RESULT_OK, resultIntent);
                    finish();
                }
            });
        }
    }
    • setContentView(R.layout.activity_second):需要在 android/app/src/main/res/layout/ 下新建一个 activity_second.xml 布局。
  3. 创建 activity_second.xml 布局

    <!-- android/app/src/main/res/layout/activity_second.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:gravity="center">
    
        <TextView
            android:id="@+id/tv_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello from SecondActivity"
            android:textSize="18sp" />
    
        <Button
            android:id="@+id/btn_return"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="点击返回给RN" />
    </LinearLayout>
  4. 注册 MyModule 到 React Native
    MainApplication.java 中,将 MyModule 添加到包列表。

    // android/app/src/main/java/com/myapp/MainApplication.java
    package com.myapp;
    
    import android.app.Application;
    import com.facebook.react.PackageList;
    import com.facebook.react.ReactApplication;
    import com.facebook.react.ReactNativeHost;
    import com.facebook.react.ReactPackage;
    import com.facebook.react.shell.MainReactPackage;
    import com.facebook.soloader.SoLoader;
    import java.util.List;
    
    public class MainApplication extends Application implements ReactApplication {
    
        private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
            @Override
            public boolean getUseDeveloperSupport() {
                return BuildConfig.DEBUG;
            }
    
            @Override
            protected List<ReactPackage> getPackages() {
                List<ReactPackage> packages = new PackageList(this).getPackages();
                // 1. 手动添加 MyPackage
                packages.add(new MyPackage());
                return packages;
            }
    
            @Override
            protected String getJSMainModuleName() {
                return "index";
            }
        };
    
        @Override
        public ReactNativeHost getReactNativeHost() {
            return mReactNativeHost;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            SoLoader.init(this, /* native exopackage */ false);
        }
    }
  5. 创建 MyPackage.java

    // android/app/src/main/java/com/myapp/MyPackage.java
    package com.myapp;
    
    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class MyPackage implements ReactPackage {
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(new MyModule(reactContext));
            return modules;
        }
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            return Collections.emptyList();
        }
    }
    • MyPackageMyModule 注册进 RN 桥接。
  6. Gradle 同步并编译

    cd android
    ./gradlew clean
    ./gradlew assembleDebug
    cd ..
    • 确保编译无误,再运行 npx react-native run-android,以验证原生修改未出错。

2.2 在 React Native 端:调用 MyModule

  1. 引入 NativeModules
    在 RN 端(例如 App.js)引入并调用:

    // App.js
    import React, { useState } from 'react';
    import { View, Text, Button, StyleSheet, NativeModules } from 'react-native';
    
    const { MyModule } = NativeModules;
    
    export default function App() {
      const [result, setResult] = useState(null);
    
      // 无参跳转
      const goToSecond = () => {
        MyModule.startSecondActivity();
      };
    
      // 带参跳转并接收返回
      const goToSecondForResult = async () => {
        try {
          const res = await MyModule.startSecondActivityForResult('你好,原生!');
          setResult(res.result);
        } catch (e) {
          console.error(e);
        }
      };
    
      return (
        <View style={styles.container}>
          <Text style={styles.title}>React Native 与原生 Activity 跳转示例</Text>
          <Button title="无参跳转到 SecondActivity" onPress={goToSecond} />
          <View style={styles.spacer} />
          <Button
            title="带参跳转并返回结果"
            onPress={goToSecondForResult}
          />
          {result && (
            <>
              <View style={styles.spacer} />
              <Text style={styles.resultText}>返回结果:{result}</Text>
            </>
          )}
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 16 },
      title: { fontSize: 20, fontWeight: 'bold', marginBottom: 24, textAlign: 'center' },
      spacer: { height: 16 },
      resultText: { marginTop: 16, fontSize: 16, color: 'green' },
    });
    • 点击第一个按钮,RN 调用 MyModule.startSecondActivity(),直接打开 SecondActivity
    • 点击第二个按钮,RN 调用 MyModule.startSecondActivityForResult('你好,原生!'),并等待返回结果;
    • SecondActivity 中拿到参数后在界面显示,点击“返回给RN”按钮,将结果通过 Intent 携带并回传,RN 端 await 后显示在页面上。

    示例对话图解:

    [RN App] --(startSecondActivityForResult)--> [SecondActivity]
         |                                           |
         |<-------- onActivityResult(result)---------|
         |
     setResult("原生Activity返回的数据")

三、场景二:原生 Activity 启动 React Native 界面(Deep Linking 与 Intent)

除了 RN 调用原生页面,有时需要在原生侧直接跳转到 RN 界面。例如:在某个业务模块里,点击“编辑”按钮需要从原生 Activity 跳回 RN 页面并携带参数。常见做法包括:

  1. Deep Linking(使用 URI 方式指向 RN 页面)
  2. Explicit Intent + 标记跳转参数

下面分别介绍这两种方式。

3.1 Deep Linking(URI Scheme)

Deep Linking 通过 URL Scheme 或 App Link 唤起应用,并由 RN 的 Linking 模块监听到对应路径,从而导航到 JS 路由中指定页面。

  1. AndroidManifest.xml 中配置 Deep Link
    我们在 MainActivity<intent-filter> 中已添加如下内容:

    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" android:host="open" android:pathPrefix="/profile" />
    </intent-filter>
    • 当其他原生页面或外部应用执行:

      Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("myapp://open/profile?userId=42"));
      startActivity(intent);

      就会触发 RN 的主 Activity 启动并携带该 URI。

  2. 在 RN 端监听 Linking
    App.js 或最顶部的组件中,使用 Linking 模块监听 url 事件,并根据路径进行导航(例如使用 React Navigation)。

    // App.js
    import React, { useEffect } from 'react';
    import { View, Text, Linking, Alert } from 'react-native';
    import { NavigationContainer } from '@react-navigation/native';
    import { createStackNavigator } from '@react-navigation/stack';
    
    function HomeScreen({ navigation }) {
      return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
          <Text>Home Screen</Text>
        </View>
      );
    }
    
    function ProfileScreen({ route }) {
      const { userId } = route.params || {};
      return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
          <Text>Profile Screen for userId: {userId}</Text>
        </View>
      );
    }
    
    const Stack = createStackNavigator();
    
    export default function App() {
      useEffect(() => {
        // 1. 获取应用初始启动时可能携带的 URL
        Linking.getInitialURL().then((url) => {
          if (url) {
            handleDeepLink({ url });
          }
        });
    
        // 2. 监听在应用已启动时的新 URL
        const subscription = Linking.addEventListener('url', handleDeepLink);
        return () => subscription.remove();
      }, []);
    
      const handleDeepLink = ({ url }) => {
        // 解析 URL,例如: myapp://open/profile?userId=42
        const parsed = Linking.parse(url);
        if (parsed.path === 'open/profile') {
          const userId = parsed.queryParams.userId;
          // 通过 navigation 导航到 ProfileScreen,传递参数
          navigationRef.current?.navigate('Profile', { userId });
        } else {
          Alert.alert('未知 Deep Link', url);
        }
      };
    
      // 需定义 navigationRef 用于从外部调用 navigation
      const navigationRef = React.useRef();
    
      return (
        <NavigationContainer ref={navigationRef}>
          <Stack.Navigator initialRouteName="Home">
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      );
    }
    • Linking.getInitialURL():当应用冷启动时,如果包含 Deep Link,会在这里拿到 URL。
    • Linking.addEventListener('url', callback):当应用后台时再次唤起带有 Deep Link 的 URL,会触发此监听器。
    • Linking.parse(url):RN 内置解析函数,将 URL 拆分为 { scheme, hostname, path, queryParams }
    • 由此我们可以根据 path === 'open/profile' 导航到 ProfileScreen,并传递 userId 参数。

    Deep Linking 图解:

    ┌──────────────────────────┐
    │ 原生/第三方 App 或 网页  │
    │  Intent(url=myapp://open/profile?userId=42)  │
    └──────────────────────────┘
               │
               ▼
    ┌─────────────────────────────────────────┐
    │ Android 系统根据 <intent-filter> 匹配  │
    │ → 唤起 RN MainActivity 并携带该 URL     │
    └─────────────────────────────────────────┘
               │
               ▼
    ┌─────────────────────────────────────────┐
    │ React Native: Linking.getInitialURL()   │
    │ → 获取 URL 并分发到 handleDeepLink     │
    └─────────────────────────────────────────┘
               │
               ▼
    ┌─────────────────────────────────────────┐
    │ RN NavigationContainer: navigate('Profile', { userId: 42 }) │
    └─────────────────────────────────────────┘
               │
               ▼
    ┌─────────────────────────────────────────┐
    │ 加载 ProfileScreen 并显示 userId 信息  │
    └─────────────────────────────────────────┘

3.2 Explicit Intent(原生直接跳转 RN 页面)

有时不想依赖 URL Scheme,希望在原生 Activity 中直接启动 RN 页面并携带参数。我们可以在原生侧构造一个 Intent 启动 MainActivity(RN 主 Activity),并在 Intent 中放置额外参数。然后在 RN 端通过 getInitialURL()Linking.getInitialURL() 获取这些参数。

  1. 在原生侧启动 MainActivity
    例如在另一个原生 Activity(如 NativeTriggerActivity)中:

    // android/app/src/main/java/com/myapp/NativeTriggerActivity.java
    package com.myapp;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    
    public class NativeTriggerActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_native_trigger);
    
            Button btnOpenRN = findViewById(R.id.btn_open_rn);
            btnOpenRN.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(NativeTriggerActivity.this, MainActivity.class);
                    // 给 RN 传递参数:
                    intent.putExtra("screen", "Profile");
                    intent.putExtra("userId", "99");
                    startActivity(intent);
                    finish();
                }
            });
        }
    }
    • MainActivity(ReactActivity)默认会加载 index.js,并且如果 Intent 中带有额外键值对,它们会注入到 RN 侧的启动 URL 或 initialProps 中。
  2. MainActivity.java 处理 Intent 并传递给 JS
    MainActivity 通常继承自 ReactActivity,我们可以重写 getLaunchOptions() 方法,将 Intent 中的参数传递给 JS 端:

    // android/app/src/main/java/com/myapp/MainActivity.java
    package com.myapp;
    
    import android.content.Intent;
    import android.os.Bundle;
    
    import com.facebook.react.ReactActivity;
    import com.facebook.react.ReactActivityDelegate;
    import com.facebook.react.ReactRootView;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class MainActivity extends ReactActivity {
    
        @Override
        protected String getMainComponentName() {
            return "MyApp";
        }
    
        // 重写:向 JS 传递 initialProps
        @Override
        protected Bundle getLaunchOptions() {
            Intent intent = getIntent();
            Bundle initialProps = new Bundle();
    
            if (intent != null && intent.hasExtra("screen")) {
                initialProps.putString("screen", intent.getStringExtra("screen"));
            }
            if (intent != null && intent.hasExtra("userId")) {
                initialProps.putString("userId", intent.getStringExtra("userId"));
            }
            return initialProps;
        }
    }
  3. 在 RN 端读取 initialProps 并导航
    index.jsApp.js 中,通过 AppRegistry.registerComponent 时,RN 会自动将 initialProps 传入根组件 App。我们可在 App.js 中读取这些 props 并启动导航。

    // App.js
    import React, { useEffect } from 'react';
    import { View, Text, Platform } from 'react-native';
    import { NavigationContainer } from '@react-navigation/native';
    import { createStackNavigator } from '@react-navigation/stack';
    
    function HomeScreen() {
      return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
          <Text>Home Screen</Text>
        </View>
      );
    }
    
    function ProfileScreen({ route }) {
      const { userId } = route.params || {};
      return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
          <Text>Profile for userId: {userId}</Text>
        </View>
      );
    }
    
    const Stack = createStackNavigator();
    
    export default function App(props) {
      const { screen, userId } = props; // 从 native getLaunchOptions 传入
    
      return (
        <NavigationContainer>
          <Stack.Navigator initialRouteName="Home">
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
    
          {/* 一旦组件挂载,就检查初始参数,进行导航 */}
          {screen === 'Profile' && Platform.OS === 'android' && (
            // 注意:需要访问 navigationRef 才能导航,在此简化示例
            <RedirectToProfile userId={userId} />
          )}
        </NavigationContainer>
      );
    }
    
    // RedirectToProfile.js(简化示例,实际需使用 navigationRef)
    import { useEffect } from 'react';
    import { useNavigation } from '@react-navigation/native';
    
    export function RedirectToProfile({ userId }) {
      const navigation = useNavigation();
      useEffect(() => {
        if (userId) {
          navigation.navigate('Profile', { userId });
        }
      }, [userId]);
      return null;
    }
    • MainActivity.getLaunchOptions() 会将原生传进来的 screenuserId 作为 initialProps 传给 RN 根组件。
    • 在 RN 渲染时,props.screen === 'Profile',通过一个类似 RedirectToProfile 的逻辑立即导航到 ProfileScreen,并传递 userId

    显式 Intent 跳转图解:

    ┌─────────────────────────────┐
    │ NativeTriggerActivity 点击 │
    │ Intent(MainActivity, extras.{screen:Profile,userId:99}) │
    └─────────────────────────────┘
                 │
                 ▼
    ┌─────────────────────────────┐
    │ MainActivity 加载 RN Bundle │
    │ getLaunchOptions 读取 extras │
    │ → initialProps = {screen, userId} │
    └─────────────────────────────┘
                 │
                 ▼
    ┌─────────────────────────────┐
    │ RN App.js 收到 props.screen=Profile │
    │ 立即 navigate('Profile',{userId})    │
    └─────────────────────────────┘
                 │
                 ▼
    ┌─────────────────────────────┐
    │ ProfileScreen 显示 userId=99 │
    └─────────────────────────────┘

四、场景三:React Navigation 与原生跳转结合示例

大多数 RN 项目都会使用 React Navigation 管理界面跳转。在上述 Deep Linking 与 Explicit Intent 中,我们对 React Navigation 的集成方式略显简化。接下来给出一个更完整的、在 App 启动时即检查 initialProps 并导航到指定页面的示例。

4.1 安装 React Navigation

yarn add @react-navigation/native @react-navigation/stack
yarn add react-native-screens react-native-safe-area-context

在 Android 端,确保在 MainActivity.java 中添加 ReactActivityDelegate 配置以启用 react-native-screens

// android/app/src/main/java/com/myapp/MainActivity.java
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.rnscreens.RNScreensPackage; // 引入 react-native-screens

public class MainActivity extends ReactActivity {

    @Override
    protected String getMainComponentName() {
        return "MyApp";
    }

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName()) {
            @Override
            protected ReactRootView createRootView() {
                // 启用 react-native-screens
                return new ReactRootView(MainActivity.this);
            }
        };
    }

    @Override
    protected Bundle getLaunchOptions() {
        Intent intent = getIntent();
        Bundle initialProps = new Bundle();
        if (intent != null && intent.hasExtra("screen")) {
            initialProps.putString("screen", intent.getStringExtra("screen"));
        }
        if (intent != null && intent.hasExtra("userId")) {
            initialProps.putString("userId", intent.getStringExtra("userId"));
        }
        return initialProps;
    }
}

4.2 App.js 完整示例

// App.js
import React, { useEffect, useRef, useState } from 'react';
import { View, Text, Button, Platform } from 'react-native';
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}

function ProfileScreen({ route }) {
  const { userId } = route.params || {};
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Profile Screen for userId: {userId}</Text>
    </View>
  );
}

const Stack = createStackNavigator();

export default function App(props) {
  const navigationRef = useNavigationContainerRef();
  const [initialRoute, setInitialRoute] = useState('Home');
  const [initialParams, setInitialParams] = useState({});

  useEffect(() => {
    // 1. 读取 initialProps
    const { screen, userId } = props;
    if (screen === 'Profile' && userId) {
      setInitialRoute('Profile');
      setInitialParams({ userId });
    }
  }, [props]);

  // 2. 使用 React Navigation 设置 initialState
  const linking = {
    prefixes: [], // 不再使用 Deep Linking 示例
    config: {
      initialRouteName: initialRoute,
      screens: {
        Home: 'home',
        Profile: 'profile/:userId',
      },
    },
  };

  return (
    <NavigationContainer ref={navigationRef} linking={linking}>
      <Stack.Navigator initialRouteName={initialRoute}>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen
          name="Profile"
          component={ProfileScreen}
          initialParams={initialParams} // 这里传入 initialParams
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
  • initialRouteinitialParams 来自于原生 getLaunchOptions() 中传递的 props。
  • Stack.Navigator 中,将 initialRouteName={initialRoute},并对 Profile 屏幕设置 initialParams={initialParams}

这样在启动时,若 props.screen === 'Profile',RN 会直接打开 ProfileScreen 并显示 userId,否则进入默认的 HomeScreen


五、常见问题与性能优化

5.1 Activity 频繁重启

  • 如果 RN 端多次调用 startActivity()startActivityForResult(),可能会造成多个 SecondActivity 重叠或频繁打开。
  • 建议在跳转前先检查当前是否已有该 Activity 在栈顶,必要时可设置 launchMode="singleTop" 或在 Intent 中添加 Intent.FLAG_ACTIVITY_SINGLE_TOP 等 flag。

5.2 回退栈管理

  • RN 与原生 Activity 共存时,Back 按钮的行为需要特别注意:

    • SecondActivity 中按 “返回” 会自动调用 finish() 回到 RN。
    • 如果在 RN 页面中集成了硬件返回键监听(BackHandler),需要先判断是否要拦截返回事件,否则可能会同时触发 RN 和原生的 Back 逻辑,导致页面回退异常。

5.3 性能与内存

  • 每次启动 Activity 都会涉及 RN JS Bundle 再次初始化,可能会有短暂的白屏或性能抖动。
  • 可以使用 Single Activity + Fragment 方案(将原生页面做成 Fragment),然后在同一个 Activity 内切换 Fragment,与 RN 协作更顺畅。
  • 对于简单交互,考虑使用 React Native Navigation 这类原生导航库,直接将 RN 界面作为原生 ActivityFragment,以获得更好的性能与过渡动画。

六、小结与学习路径

本文从最基础的 RN ↔ 原生 Activity 跳转案例入手,详细介绍了以下核心内容:

  1. RN 调用原生 Activity

    • 无参跳转:getCurrentActivity().startActivity(intent)
    • 有参跳转并返回结果:startActivityForResult(intent, requestCode) + Promise 回调
  2. 原生 Activity 调用 RN 界面

    • Deep Linking:通过 LINKING<intent-filter> 实现 URL Scheme 唤起 RN 并导航
    • Explicit Intent:在原生侧构造 Intent 并通过 getLaunchOptions() 将参数传给 RN。
  3. React Navigation 与原生跳转结合

    • App.js 中根据 initialProps 决定初始路由与参数,结合 navigationRef 实现“原生 → RN”导航。
  4. 常见问题与优化

    • Activity 重叠、回退冲突、性能优化等实战建议。

通过本文示例,你应能够完成以下几项关键能力:

  • 在 RN 代码中通过 NativeModules 调用 Android 原生页面,并在原生页面中返回结果给 JS。
  • 在 Android 原生侧通过 Intent/Deep Linking 唤起 RN 页面,并将参数传递给 JS 层。
  • 在 RN 端结合 React Navigation,根据不同启动参数控制首屏路由和参数传递。
  • 解决混合导航场景下的回退、栈管理与性能问题。

下一步推荐学习内容:

  1. React Native Navigation(Wix、React Navigation)更深入的自定义动画与原生交互。
  2. 单 Activity + Fragment 架构:将原生页面封装为 Fragment,与 RN 同在一个 Activity 中管理。
  3. 跨平台 Deep Linking:在 iOS 与 Android 上同时配置 Deep Linking、Universal Links 与 App Links,打造统一路径方案。
  4. React Native 原生 UI 组件开发:学习如何自定义原生 ViewFragment,并在 RN 中使用 requireNativeComponent 引用。

希望本文能帮你构建 React Native 与 Android 原生无缝衔接的页面导航体系,让你快速在项目中实现混合导航、原生跳转与 RN 交互。

最后修改于:2025年05月29日 11:09

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日