
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 url_launcher 链接启动组件的使用方法,带你全面掌握在应用中打开链接、拨打电话、发送邮件等功能。
一、url_launcher 组件概述
在 Flutter for OpenHarmony 应用开发中,url_launcher 是一个非常实用的插件,用于在应用内打开外部链接、启动其他应用、拨打电话、发送邮件等。它提供了一个统一的 API,屏蔽了不同平台的差异,让开发者可以轻松实现各种跳转功能。
📋 url_launcher 组件特点
| 特点 | 说明 |
|---|---|
| 跨平台支持 | 支持 Android、iOS、Linux、macOS、Windows、OpenHarmony |
| 统一 API | 不同平台使用相同的 API 进行跳转 |
| 多种启动模式 | 支持浏览器、应用内 WebView、外部应用等模式 |
| 功能丰富 | 支持打开链接、拨打电话、发送邮件等 |
| 异步操作 | 所有跳转操作都是异步的 |
💡 使用场景:打开网页、拨打电话、发送邮件、打开应用商店、启动其他应用等。
二、OpenHarmony 平台适配说明
2.1 兼容性信息
本项目基于 url_launcher@6.3.3 开发,适配 Flutter 3.27.5-ohos-1.0.4。
2.2 支持的功能
在 OpenHarmony 平台上,url_launcher 支持以下功能:
| 功能 | 说明 | OpenHarmony 支持 |
|---|---|---|
| canLaunch() | 检查是否可以启动指定 URL | ✅ yes |
| launch() | 启动指定 URL(旧 API) | ✅ yes |
| launchUrl() | 启动指定 URL(新 API) | ✅ yes |
| closeWebView() | 关闭 WebView | ✅ yes |
三、项目配置与安装
3.1 添加依赖配置
首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 url_launcher 依赖。
打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:
yaml
dependencies:
flutter:
sdk: flutter
# 添加 url_launcher 依赖(OpenHarmony 适配版本)
url_launcher:
git:
url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
path: "packages/url_launcher/url_launcher"
配置说明:
- 使用 git 方式引用开源鸿蒙适配的 flutter_packages 仓库
url:指定 GitCode 托管的仓库地址path:指定 url_launcher 包的具体路径- 本项目基于
url_launcher@6.3.3开发,适配 Flutter 3.27.5-ohos-1.0.4
⚠️ 重要:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。
3.2 下载依赖
配置完成后,需要在项目根目录执行以下命令下载依赖:
bash
flutter pub get
执行成功后,你会看到类似以下的输出:
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!
3.3 权限配置说明
url_launcher 在 OpenHarmony 平台上需要配置网络权限(ohos.permission.INTERNET),否则在安装 hap 包时可能会报错 9568289。
3.3.1 配置应用权限等级
默认的应用权限等级是 normal,但某些权限需要 system_basic 等级。如果遇到权限问题,需要修改应用等级。
修改应用等级步骤:
- 打开
ohos/AppScope/app.json5文件 - 找到
app对象,修改bundleType或其他相关配置 - 具体配置请参考华为官方文档:安装hap时提示code9568289
3.3.2 在 module.json5 中添加权限
打开 ohos/entry/src/main/module.json5 文件,在 requestPermissions 数组中添加网络权限:
json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:network_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}
]
}
}
配置说明:
name:权限名称,这里是ohos.permission.INTERNETreason:申请权限的原因,引用字符串资源usedScene:使用场景,指定在哪些 Ability 中使用,以及使用时机
3.3.3 添加权限原因说明
打开 ohos/entry/src/main/resources/base/element/string.json 文件,添加权限原因的字符串:
json
{
"string": [
{
"name": "network_reason",
"value": "使用网络"
}
]
}
注意: 如果你的 string.json 文件已经有其他内容,只需在 string 数组中添加新的对象即可,不要覆盖原有内容。
3.4 依赖自动配置说明
执行 flutter pub get 后,OpenHarmony 平台的依赖会自动配置到 ohos/entry/oh-package.json5 文件中。你不需要手动配置 OpenHarmony 端的依赖,Flutter 构建系统会自动处理。
四、url_launcher 基础用法
4.1 导入包
在使用 url_launcher 之前,首先需要导入相关包:
dart
import 'package:url_launcher/url_launcher.dart';
4.2 检查是否可以启动 URL - canLaunch()
在尝试启动 URL 之前,建议先检查设备是否支持该 URL:
dart
// 检查是否可以启动 URL
bool canLaunchUrl = await canLaunch('https://www.example.com');
if (canLaunchUrl) {
// 可以启动
await launchUrl(Uri.parse('https://www.example.com'));
} else {
// 不支持
print('无法启动此 URL');
}
4.3 启动 URL(新 API)- launchUrl()
使用新的 launchUrl() 方法启动 URL,推荐使用此方法:
dart
// 在默认浏览器中打开 URL
final Uri url = Uri.parse('https://www.example.com');
await launchUrl(url);
// 在应用内 WebView 中打开 URL
await launchUrl(
url,
mode: LaunchMode.inAppWebView,
);
// 在外部应用中打开 URL
await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
4.4 启动 URL(旧 API)- launch()
旧版本的 launch() 方法仍然可用,但推荐使用新的 launchUrl():
dart
// 在默认浏览器中打开 URL
await launch('https://www.example.com');
// 在应用内 WebView 中打开 URL
await launch(
'https://www.example.com',
useSafariVC: true,
useWebView: true,
);
// 拨打电话
await launch('tel:1234567890');
// 发送邮件
await launch('mailto:example@example.com');
4.5 关闭 WebView - closeWebView()
当使用 inAppWebView 模式打开 URL 后,可以通过以下方式关闭 WebView:
dart
// 打开 URL
await launchUrl(
url,
mode: LaunchMode.inAppWebView,
);
// 5 秒后关闭
Future.delayed(const Duration(seconds: 5), () {
// 注意:closeWebView() 在某些平台上可能不可用
// 在 OpenHarmony 上,可以通过 headers 配置页面路径
});
五、常见使用场景
5.1 打开网页
dart
// 打开网页(使用默认浏览器)
final Uri url = Uri.parse('https://www.openharmony.cn');
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw Exception('无法打开链接');
}
5.2 拨打电话
dart
// 拨打电话
final Uri phoneUri = Uri(scheme: 'tel', path: '1234567890');
if (await canLaunchUrl(phoneUri)) {
await launchUrl(phoneUri);
} else {
print('不支持拨打电话');
}
5.3 发送邮件
dart
// 发送邮件
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'example@example.com',
query: 'subject=主题&body=邮件内容',
);
if (await canLaunchUrl(emailUri)) {
await launchUrl(emailUri);
}
5.4 打开应用商店
dart
// 打开华为应用商店
final Uri appStoreUri = Uri.parse(
'store://appgallery.huawei.com/app/detail?id=com.huawei.hmsapp.himovie'
);
if (await canLaunchUrl(appStoreUri)) {
await launchUrl(appStoreUri);
}
5.5 在应用内 WebView 中打开网页
dart
// 在应用内 WebView 中打开网页
await launchUrl(
Uri.parse('https://www.example.com'),
mode: LaunchMode.inAppWebView,
webViewConfiguration: const WebViewConfiguration(
enableJavaScript: true,
enableDomStorage: true,
),
);
六、完整示例代码
下面是一个完整的示例应用,展示了 url_launcher 的各种用法:
dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(const UrlLauncherDemo());
}
class UrlLauncherDemo extends StatelessWidget {
const UrlLauncherDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'URL Launcher 示例',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const UrlLauncherPage(),
);
}
}
class UrlLauncherPage extends StatefulWidget {
const UrlLauncherPage({super.key});
@override
State<UrlLauncherPage> createState() => _UrlLauncherPageState();
}
class _UrlLauncherPageState extends State<UrlLauncherPage> {
bool _hasCallSupport = false;
Future<void>? _launched;
String _phone = '';
@override
void initState() {
super.initState();
_checkCallSupport();
}
Future<void> _checkCallSupport() async {
bool canCall = await canLaunchUrl(Uri.parse('tel:123'));
setState(() {
_hasCallSupport = canCall;
});
}
Future<void> _launchInBrowser(String url) async {
final Uri uri = Uri.parse(url);
if (!await launchUrl(uri)) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('无法打开链接: $url')),
);
}
}
}
Future<void> _launchInWebView(String url) async {
final Uri uri = Uri.parse(url);
if (!await launchUrl(
uri,
mode: LaunchMode.inAppWebView,
)) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('无法在 WebView 中打开: $url')),
);
}
}
}
Future<void> _launchEmail() async {
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'example@example.com',
query: 'subject=主题&body=邮件内容',
);
if (!await launchUrl(emailUri)) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法打开邮件应用')),
);
}
}
}
Future<void> _makePhoneCall() async {
// 检查电话号码是否为空
if (_phone.isEmpty) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入电话号码')),
);
}
return;
}
try {
final Uri phoneUri = Uri(
scheme: 'tel',
path: _phone.trim(),
);
// 先检查是否可以启动
if (!await canLaunchUrl(phoneUri)) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法拨打电话')),
);
}
return;
}
// 启动拨号
await launchUrl(phoneUri);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('拨打电话失败: $e')),
);
}
}
}
Future<void> _launchAppGallery() async {
final Uri appGalleryUri = Uri.parse(
'store://appgallery.huawei.com/app/detail?id=com.huawei.hmsapp.himovie'
);
if (!await launchUrl(appGalleryUri)) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('无法打开应用商店')),
);
}
}
}
@override
Widget build(BuildContext context) {
const String testUrl = 'https://www.openharmony.cn';
return Scaffold(
appBar: AppBar(
title: const Text('URL Launcher 示例'),
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.blue.shade50,
Colors.indigo.shade50,
],
),
),
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSectionCard(
title: '打开网页',
icon: Icons.language,
color: Colors.blue,
child: Column(
children: [
const Text(
testUrl,
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () => _launchInBrowser(testUrl),
icon: const Icon(Icons.open_in_browser),
label: const Text('在浏览器中打开'),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton.icon(
onPressed: () => _launchInWebView(testUrl),
icon: const Icon(Icons.web),
label: const Text('在 WebView 中打开'),
),
),
],
),
],
),
),
const SizedBox(height: 16),
_buildSectionCard(
title: '拨打电话',
icon: Icons.phone,
color: Colors.green,
child: Column(
children: [
TextField(
onChanged: (value) => _phone = value,
decoration: const InputDecoration(
labelText: '输入电话号码',
prefixIcon: Icon(Icons.phone),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.phone,
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _hasCallSupport ? _makePhoneCall : null,
icon: const Icon(Icons.call),
label: Text(_hasCallSupport ? '拨打电话' : '不支持拨打电话'),
),
],
),
),
const SizedBox(height: 16),
_buildSectionCard(
title: '发送邮件',
icon: Icons.email,
color: Colors.orange,
child: ElevatedButton.icon(
onPressed: _launchEmail,
icon: const Icon(Icons.send),
label: const Text('发送邮件'),
),
),
const SizedBox(height: 16),
_buildSectionCard(
title: '打开应用商店',
icon: Icons.store,
color: Colors.purple,
child: ElevatedButton.icon(
onPressed: _launchAppGallery,
icon: const Icon(Icons.apps),
label: const Text('打开华为应用商店'),
),
),
const SizedBox(height: 32),
],
),
),
);
}
Widget _buildSectionCard({
required String title,
required IconData icon,
required Color color,
required Widget child,
}) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const SizedBox(width: 12),
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 16),
child,
],
),
),
);
}
}
七、API 参数详解
7.1 LaunchMode(启动模式)
| 模式 | 说明 | OpenHarmony 支持 |
|---|---|---|
| LaunchMode.platformDefault | 由平台决定启动方式 | ✅ yes |
| LaunchMode.inAppWebView | 在应用内 WebView 中打开 | ✅ yes |
| LaunchMode.externalApplication | 在外部应用中打开 | ✅ yes |
| LaunchMode.externalNonBrowserApplication | 在非浏览器外部应用中打开 | ✅ yes |
7.2 WebViewConfiguration(WebView 配置)
| 参数 | 说明 | 类型 | OpenHarmony 支持 |
|---|---|---|---|
| enableJavaScript | 是否启用 JavaScript | bool | ✅ yes |
| enableDomStorage | 是否启用 DOM 存储 | bool | ✅ yes |
| headers | 请求头 | Map<String, String> | ✅ yes |
7.3 LaunchOptions(启动选项)
| 参数 | 说明 | 类型 | OpenHarmony 支持 |
|---|---|---|---|
| mode | 启动模式 | LaunchMode | ✅ yes |
| webViewConfiguration | WebView 配置 | WebViewConfiguration | ✅ yes |
| webOnlyWindowName | 窗口名称(仅 Web) | String? | ✅ yes |
八、常见问题与最佳实践
8.1 常见问题
Q1: 为什么打开链接失败?
A: 检查以下几点:
- 确认 URL 格式正确(使用
Uri.parse()解析) - 使用
canLaunchUrl()检查是否支持该 URL - 确认已正确配置网络权限
Q2: 如何在 OpenHarmony 上使用应用内 WebView?
A: 使用 LaunchMode.inAppWebView 模式:
dart
await launchUrl(
url,
mode: LaunchMode.inAppWebView,
);
Q3: 如何传递自定义请求头?
A: 使用 WebViewConfiguration 的 headers 参数:
dart
await launchUrl(
url,
mode: LaunchMode.inAppWebView,
webViewConfiguration: WebViewConfiguration(
headers: {
'Custom-Header': 'value',
},
),
);
8.2 最佳实践
1. 始终使用 Uri.parse()
dart
// ❌ 错误示例
await launchUrl('https://www.example.com');
// ✅ 正确示例
await launchUrl(Uri.parse('https://www.example.com'));
2. 使用 canLaunchUrl() 检查
dart
// 检查是否可以启动
final Uri url = Uri.parse('https://www.example.com');
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
// 处理不支持的情况
}
3. 选择合适的启动模式
dart
// 需要用户留在应用内 - 使用 inAppWebView
await launchUrl(url, mode: LaunchMode.inAppWebView);
// 需要打开外部应用 - 使用 externalApplication
await launchUrl(url, mode: LaunchMode.externalApplication);
4. 处理异常
dart
try {
await launchUrl(url);
} catch (e) {
print('启动失败: $e');
// 提示用户
}
九、总结
恭喜你!通过这篇文章的学习,你已经掌握了 Flutter 中 url_launcher 链接启动的全面知识。
🎯 核心要点
- 基础用法 :使用
launchUrl()启动各种 URL - 多种模式:支持浏览器、WebView、外部应用等启动模式
- 异步操作 :所有跳转操作都是异步的,必须使用
await等待 - 错误处理 :使用
canLaunchUrl()检查是否支持,处理异常情况 - 权限配置:OpenHarmony 需要配置网络权限
📚 使用场景指南
| 场景 | 推荐方法 | 启动模式 |
|---|---|---|
| 打开网页 | launchUrl() | platformDefault 或 inAppWebView |
| 拨打电话 | launchUrl(Uri(scheme: 'tel')) | externalApplication |
| 发送邮件 | launchUrl(Uri(scheme: 'mailto')) | externalApplication |
| 打开应用商店 | launchUrl() | externalApplication |
| 应用内浏览 | launchUrl() | inAppWebView |
🚀 进阶方向
掌握了 url_launcher 后,你还可以探索以下方向:
- 深链接:学习使用 uni_links 处理应用深链接
- 应用跳转:学习如何从其他应用跳转到你的应用
- WebView 高级:学习使用 webview_flutter 实现更复杂的网页展示
- 文件处理:学习使用 file_selector 选择文件