ReactNative项目OpenHarmony三方库集成实战:react-native-inappbrowser(也可以考虑WebView)

欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

项目基于 RN 0.72.90 开发

📋 前言

在移动应用开发中,应用内浏览器是一个常见需求,特别是在OAuth认证、第三方登录、网页内容展示等场景中。react-native-inappbrowser 是一个功能强大的应用内浏览器库,提供了在应用内打开网页的能力,支持自定义样式、深度链接、认证流程等功能,是实现安全网页浏览的理想选择。

🎯 库简介

基本信息

为什么需要应用内浏览器?

特性 外部浏览器 react-native-inappbrowser
用户体验 ⚠️ 跳出应用 ✅ 应用内体验
OAuth认证 ⚠️ 体验差 ✅ 完美支持
自定义样式 ❌ 不支持 ✅ 完全自定义
深度链接 ⚠️ 需手动处理 ✅ 自动处理
安全性 ⚠️ 较低 ✅ 沙盒环境
HarmonyOS 支持 ⚠️ 需适配 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
open 打开网页
close 关闭浏览器
openAuth 打开认证页面
closeAuth 关闭认证会话
isAvailable 检测是否支持
warmup 预热浏览器进程
mayLaunchUrl 预加载URL

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0 Release SDK; IDE : DevEco Studio 6.0.2; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

bash 复制代码
# RN 0.72 版本
npm install @react-native-ohos/react-native-inappbrowser-reborn@3.7.1-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-inappbrowser-reborn@3.8.0-rc.1

# 或者使用 yarn
yarn add @react-native-ohos/react-native-inappbrowser-reborn

2. 验证安装

安装完成后,检查 package.json 文件:

json 复制代码
{
  "dependencies": {
    "@react-native-ohos/react-native-inappbrowser-reborn": "^3.7.1-rc.1"
  }
}

🔧 HarmonyOS 平台配置 ⭐

1. 引入原生端代码

打开 harmony/entry/oh-package.json5,添加以下依赖:

json5 复制代码
"dependencies": {
  "@react-native-ohos/react-native-inappbrowser-reborn": "file:../../node_modules/@react-native-ohos/react-native-inappbrowser-reborn/harmony/inappbrowser.har"
}

点击右上角的 sync 按钮,或者在终端执行:

bash 复制代码
cd entry
ohpm install

2. 配置 CMakeLists

打开 entry/src/main/cpp/CMakeLists.txt,添加:

c 复制代码
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-inappbrowser-reborn/src/main/cpp" ./inappbrowser-reborn)
# RNOH_END: manual_package_linking_1

# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_inappbrowser_reborn)
# RNOH_END: manual_package_linking_2

3. 引入 InappbrowserRebornPackage

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

cpp 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
+ #include "InappbrowserRebornPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<RNOHGeneratedPackage>(ctx),
      + std::make_shared<InappbrowserRebornPackage>(ctx)
    };
}

打开 entry/src/main/ets/RNPackagesFactory.ts,添加:

typescript 复制代码
+ import { RNInAppBrowserPackage } from '@react-native-ohos/react-native-inappbrowser-reborn/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
    + new RNInAppBrowserPackage(ctx)
  ];
}

4. 创建 BrowserManagerAbility(必须)

步骤 1 : 在 entry/src/main/ets/entryability 下创建 BrowserManagerAbility.ets

typescript 复制代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class BrowserManagerAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/BrowserManagerPage', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

步骤 2 : 在 entry/src/main/module.json5 注册 BrowserManagerAbility:

json 复制代码
{
  "module": {
    "abilities": [
      {
        "name": "BrowserManagerAbility",
        "srcEntry": "./ets/entryability/BrowserManagerAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "visible": true
      }
    ]
  }
}

步骤 3 : 在 entry/src/main/ets/pages 下创建 BrowserManagerPage.ets

typescript 复制代码
import { BrowserPage } from '@react-native-ohos/react-native-inappbrowser-reborn/Index'

@Entry
@Component
struct BrowserManagerPage {
  build() {
    Row() {
      Column() {
        BrowserPage();
      }
      .width('100%')
    }
    .height('100%')
  }
}

步骤 4 : 在 entry/src/main/resources/base/profile/main_pages.json 添加配置:

json 复制代码
{
  "src": [
    "pages/Index",
    "pages/BrowserManagerPage"
  ]
}

5. 配置浏览器预热(可选)

如果需要预热应用内浏览器客户端,使启动速度显著加快,可以在 BrowserManagerAbility 中添加:

typescript 复制代码
import { RNInAppBrowserModule } from '@react-native-ohos/react-native-inappbrowser-reborn/ts';

export default class BrowserManagerAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    RNInAppBrowserModule.start();
  }
}

📖 API 详解

open - 打开网页

在应用内打开指定URL的网页。

类型(url: string, options?: InAppBrowserOptions) => Promise<InAppBrowserResult>

参数

  • url: 要打开的网页地址
  • options: 浏览器配置选项

iOS/Android 通用选项

属性 类型 说明
dismissButtonStyle string 关闭按钮样式(done/close/cancel)
preferredBarTintColor string 导航栏背景颜色
preferredControlTintColor string 导航栏按钮颜色

返回值

属性 类型 说明
type string 结果类型(cancel/dismiss)
url string 最终URL

使用场景

  • 打开外部网页
  • 显示隐私政策、用户协议
  • 查看帮助文档
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const openWebPage = async () => {
  try {
    const result = await InAppBrowser.open('https://reactnative.dev', {
      dismissButtonStyle: 'close',
      preferredBarTintColor: '#007AFF',
      preferredControlTintColor: 'white',
    });
    console.log('浏览器关闭:', result.type);
  } catch (error) {
    console.error('打开网页失败:', error);
  }
};

close - 关闭浏览器

手动关闭当前打开的浏览器。

类型() => void

使用场景

  • 自动关闭浏览器
  • 定时关闭
  • 条件关闭
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const openAndClose = async () => {
  await InAppBrowser.open('https://example.com');
  
  setTimeout(() => {
    InAppBrowser.close();
  }, 5000);
};

openAuth - 打开认证页面

打开用于OAuth认证的网页,支持深度链接回调。

类型(url: string, redirectUrl: string, options?: InAppBrowserOptions) => Promise<InAppBrowserAuthResult>

参数

参数 类型 说明
url string 认证页面URL
redirectUrl string 回调URL(深度链接)
options object 浏览器配置选项

返回值

属性 类型 说明
type string 结果类型(cancel/success)
url string 回调URL(包含认证信息)
message string 错误信息(如果有)

使用场景

  • OAuth 2.0 认证
  • 第三方登录(微信、QQ、微博等)
  • 统一身份认证
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const getDeepLink = (path = '') => {
  const scheme = 'myapp';
  return `${scheme}://${path}`;
};

const performOAuth = async () => {
  const loginUrl = 'https://auth.example.com/oauth';
  const redirectUrl = getDeepLink('callback');
  const authUrl = `${loginUrl}?redirect_url=${encodeURIComponent(redirectUrl)}`;

  try {
    if (await InAppBrowser.isAvailable()) {
      const result = await InAppBrowser.openAuth(authUrl, redirectUrl, {
        ephemeralWebSession: false,
      });
      
      if (result.type === 'success' && result.url) {
        console.log('认证成功,回调URL:', result.url);
        return result.url;
      }
    }
  } catch (error) {
    console.error('认证失败:', error);
  }
};

closeAuth - 关闭认证会话

关闭当前的认证会话。

类型() => void

tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const cancelAuth = () => {
  InAppBrowser.closeAuth();
};

isAvailable - 检测是否支持

检测设备是否支持应用内浏览器。

类型() => Promise<boolean>

使用场景

  • 在打开浏览器前检测
  • 兼容性处理
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const checkAvailability = async () => {
  const available = await InAppBrowser.isAvailable();
  if (available) {
    console.log('支持应用内浏览器');
  } else {
    console.log('不支持,请使用外部浏览器');
  }
  return available;
};

warmup - 预热浏览器

预热浏览器进程,加快后续打开速度。

类型() => void

使用场景

  • 应用启动时预热
  • 预计用户会打开网页前预热
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const warmupBrowser = () => {
  InAppBrowser.warmup();
  console.log('浏览器已预热');
};

mayLaunchUrl - 预加载URL

告诉浏览器可能要加载的URL,进行预加载优化。

类型(url: string, otherUrls?: string[]) => void

参数

参数 类型 说明
url string 最可能加载的URL
otherUrls string[] 其他可能的URL列表

使用场景

  • 列表页预加载详情页
  • 提升用户体验
tsx 复制代码
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const prepareUrl = (url: string) => {
  InAppBrowser.mayLaunchUrl(url, []);
  console.log('URL预加载完成');
};

📋 完整示例

ts 复制代码
import React, { useState } from 'react';
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  SafeAreaView,
  StatusBar,
  Alert,
  TextInput,
} from 'react-native';
import { InAppBrowser } from 'react-native-inappbrowser-reborn';

const App: React.FC = () => {
  const [url, setUrl] = useState('https://reactnative.dev');
  const [result, setResult] = useState<string>('');

  const getDeepLink = (path = '') => {
    const scheme = 'myapp';
    return `${scheme}://${path}`;
  };

  const openLink = async () => {
    try {
      const available = await InAppBrowser.isAvailable();
      if (!available) {
        Alert.alert('提示', '当前设备不支持应用内浏览器');
        return;
      }

      const response = await InAppBrowser.open(url, {
        dismissButtonStyle: 'close',
        preferredBarTintColor: '#007AFF',
        preferredControlTintColor: '#FFFFFF',
      });
    
      setResult(JSON.stringify(response, null, 2));
      Alert.alert('结果', `类型: ${response.type}`);
    } catch (error) {
      console.error('打开网页失败:', error);
      Alert.alert('错误', '打开网页失败');
    }
  };

  const openAuth = async () => {
    try {
      const available = await InAppBrowser.isAvailable();
      if (!available) {
        Alert.alert('提示', '当前设备不支持应用内浏览器');
        return;
      }

      const loginUrl = 'https://proyecto26.github.io/react-native-inappbrowser/';
      const redirectUrl = getDeepLink();
      const authUrl = `${loginUrl}?redirect_url=${encodeURIComponent(redirectUrl)}`;

      const response = await InAppBrowser.openAuth(authUrl, redirectUrl, {
        ephemeralWebSession: false,
      });

      setResult(JSON.stringify(response, null, 2));
    
      if (response.type === 'success' && response.url) {
        Alert.alert('认证成功', `回调URL: ${response.url}`);
      } else {
        Alert.alert('认证取消', `类型: ${response.type}`);
      }
    } catch (error) {
      console.error('认证失败:', error);
      Alert.alert('错误', '认证失败');
    }
  };

  const checkAvailability = async () => {
    try {
      const available = await InAppBrowser.isAvailable();
      setResult(`应用内浏览器可用: ${available}`);
      Alert.alert('检测结果', available ? '支持' : '不支持');
    } catch (error) {
      console.error('检测失败:', error);
    }
  };

  const warmupBrowser = () => {
    InAppBrowser.warmup();
    setResult('浏览器已预热');
    Alert.alert('提示', '浏览器已预热');
  };

  const prepareUrl = () => {
    InAppBrowser.mayLaunchUrl(url, []);
    setResult(`URL预加载完成: ${url}`);
    Alert.alert('提示', 'URL预加载完成');
  };

  const openAndAutoClose = async () => {
    try {
      InAppBrowser.open(url);
    
      setTimeout(() => {
        InAppBrowser.close();
        setResult('浏览器已自动关闭');
        Alert.alert('提示', '浏览器已自动关闭');
      }, 5000);
    } catch (error) {
      console.error('操作失败:', error);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      <View style={styles.header}>
        <Text style={styles.headerTitle}>应用内浏览器</Text>
      </View>

      <ScrollView style={styles.content}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>URL地址</Text>
          <TextInput
            style={styles.input}
            value={url}
            onChangeText={setUrl}
            placeholder="请输入URL"
            autoCapitalize="none"
            autoCorrect={false}
          />
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>基本功能</Text>
        
          <TouchableOpacity style={styles.button} onPress={openLink}>
            <Text style={styles.buttonText}>打开网页</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={openAuth}>
            <Text style={styles.buttonText}>OAuth认证</Text>
          </TouchableOpacity>

          <TouchableOpacity style={styles.button} onPress={checkAvailability}>
            <Text style={styles.buttonText}>检测可用性</Text>
          </TouchableOpacity>
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>高级功能</Text>
        
          <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={warmupBrowser}>
            <Text style={[styles.buttonText, styles.secondaryButtonText]}>预热浏览器</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={prepareUrl}>
            <Text style={[styles.buttonText, styles.secondaryButtonText]}>预加载URL</Text>
          </TouchableOpacity>

          <TouchableOpacity style={[styles.button, styles.secondaryButton]} onPress={openAndAutoClose}>
            <Text style={[styles.buttonText, styles.secondaryButtonText]}>打开并5秒后关闭</Text>
          </TouchableOpacity>
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>返回结果</Text>
          <View style={styles.resultContainer}>
            <Text style={styles.resultText}>{result || '暂无结果'}</Text>
          </View>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 16,
    backgroundColor: '#FFFFFF',
    borderBottomWidth: 1,
    borderBottomColor: '#E5E5EA',
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#333333',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  section: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333333',
    marginBottom: 12,
  },
  input: {
    borderWidth: 1,
    borderColor: '#E5E5EA',
    borderRadius: 8,
    paddingHorizontal: 16,
    paddingVertical: 12,
    fontSize: 16,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 10,
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  secondaryButton: {
    backgroundColor: '#F0F0F0',
  },
  secondaryButtonText: {
    color: '#333333',
  },
  resultContainer: {
    backgroundColor: '#F8F8F8',
    borderRadius: 8,
    padding: 12,
    minHeight: 100,
  },
  resultText: {
    fontSize: 14,
    color: '#666666',
    fontFamily: 'monospace',
  },
});

export default App;

⚠️ 注意事项

1. 必须配置 BrowserManagerAbility

本库需要在 HarmonyOS 端配置 BrowserManagerAbility,否则浏览器无法正常工作。请确保:

  • 已创建 BrowserManagerAbility.ets
  • 已在 module.json5 中注册
  • 已创建 BrowserManagerPage.ets
  • 已在 main_pages.json 中添加页面

2. 深度链接配置

如果使用 openAuth 进行 OAuth 认证,需要配置深度链接:

HarmonyOS : 在 module.json5 中配置 skills

3. HTTPS 要求

出于安全考虑,建议使用 HTTPS 协议的 URL。HTTP 协议可能在某些设备上被阻止。

4. 内存管理

长时间打开浏览器会占用内存,建议在不需要时及时关闭:

tsx 复制代码
useEffect(() => {
  return () => {
    InAppBrowser.close();
  };
}, []);

5. 预热优化

对于需要频繁打开浏览器的场景,建议在应用启动时预热:

tsx 复制代码
useEffect(() => {
  InAppBrowser.warmup();
}, []);

6. 错误处理

始终使用 try-catch 包裹浏览器操作:

tsx 复制代码
try {
  await InAppBrowser.open(url);
} catch (error) {
  console.error('打开失败:', error);
  Alert.alert('错误', '无法打开网页');
}

7. 平台差异

不同平台的浏览器选项支持程度不同:

选项 iOS Android HarmonyOS
dismissButtonStyle
preferredBarTintColor
preferredControlTintColor
readerMode
animated
modalPresentationStyle
相关推荐
北风toto2 小时前
Vue多文件学习项目综合案例——面经基础版,黑马vue教程
javascript·vue.js·学习
浪扼飞舟3 小时前
WPF输入验证(ValidationRule)
java·javascript·wpf
这是个栗子11 小时前
TypeScript(三)
前端·javascript·typescript·react
前端精髓14 小时前
移除 Effect 依赖
前端·javascript·react.js
lpfasd12315 小时前
TypeScript + Cloudflare 全家桶部署项目全流程
前端·javascript·typescript
前端Hardy16 小时前
字节/腾讯内部流出!Claude Code 2026王炸玩法!效率暴涨10倍
前端·javascript·vue.js
前端Hardy16 小时前
大厂都在偷偷用的 Cursor Rules 封装!告别重复 Prompt,AI 编程效率翻倍
前端·javascript·面试
kyriewen16 小时前
Vite:比Webpack快100倍的“闪电侠”,原理竟然这么简单?
前端·javascript·vite
竹林81816 小时前
RainbowKit快速集成多链钱包连接:从“连不上”到丝滑切换的踩坑实录
前端·javascript