Flutter统一链接处理插件uni_links5的使用
Flutter统一链接处理插件uni_links5的使用
1. 插件简介
uni_links
是一个Flutter插件,用于处理应用程序链接(App Links)和通用链接(Universal Links)以及自定义URL方案(Custom URL schemes)。这些链接类似于网页浏览器中的链接,可以激活你的应用,并可能包含信息以加载应用的特定部分或从网站(或其他应用)继续某些用户活动。
2. 安装
要使用该插件,首先需要在 pubspec.yaml
文件中添加 uni_links
作为依赖项:
dependencies:
uni_links: ^5.0.0
2.1 Android配置
Uni Links 支持两种类型的Android链接:App Links 和 Deep Links。
- App Links 只支持
https
方案,并且需要指定主机以及托管文件assetlinks.json
。 - Deep Links 可以使用任何自定义方案,不需要主机或托管文件。但为了确保唯一性,建议使用唯一的方案+主机组合,例如
HST0000001://host.com
。
你需要在 android/app/src/main/AndroidManifest.xml
中声明至少一个Intent过滤器:
<manifest ...>
<application ...>
<activity ...>
<!-- Deep Links -->
<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="[YOUR_SCHEME]"
android:host="[YOUR_HOST]" />
</intent-filter>
<!-- App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="[YOUR_HOST]" />
</intent-filter>
</activity>
</application>
</manifest>
你可以通过添加 android:pathPrefix
属性来进一步指定路径:
<data
android:scheme="[YOUR_SCHEME_OR_HTTPS]"
android:host="[YOUR_HOST]"
android:pathPrefix="/[NAME][/NAME...]" />
2.2 iOS配置
iOS 支持两种类型的链接:通用链接(Universal Links)和自定义URL方案(Custom URL schemes)。
- 通用链接 只支持
https
方案,并且需要指定主机、权限以及托管文件apple-app-site-association
。 - 自定义URL方案 可以使用任何自定义方案,没有主机限制或权限要求。但为了确保唯一性,建议使用唯一的方案,例如
hst0000001
或myIncrediblyAwesomeScheme
。
对于 通用链接,你需要在 ios/Runner/Runner.entitlements
文件中添加 com.apple.developer.associated-domains
权限:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:[YOUR_HOST]</string>
</array>
</dict>
</plist>
你也可以通过Xcode的界面来创建这个文件:
- 打开Xcode,双击
ios/Runner.xcworkspace
文件。 - 在项目导航器中选择
Runner
根项。 - 选择
Runner
target,然后选择Signing & Capabilities
选项卡。 - 点击
+ Capability
按钮,添加Associated Domains
。 - 将第一个域从
webcredentials:example.com
更改为applinks:
+ 你的主机(例如my-fancy-domain.com
)。
对于 自定义URL方案,你需要在 ios/Runner/Info.plist
文件中声明方案:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>[ANY_URL_NAME]</string>
<key>CFBundleURLSchemes</key>
<array>
<string>[YOUR_SCHEME]</string>
</array>
</dict>
</array>
3. 使用方法
3.1 初始链接 (String)
getInitialLink
返回应用启动时的链接(如果有)。你应该在应用生命周期的早期处理这个链接,并且只处理一次。
import 'package:uni_links/uni_links.dart';
import 'package:flutter/services.dart' show PlatformException;
Future<void> initUniLinks() async {
try {
final initialLink = await getInitialLink();
// 解析链接并根据需要处理
} on PlatformException {
// 处理异常
}
}
3.2 初始链接 (Uri)
getInitialUri
与 getInitialLink
类似,但返回的是 Uri
对象。
try {
final initialUri = await getInitialUri();
// 使用 Uri 并根据需要处理
} on FormatException {
// 处理格式异常
} on PlatformException {
// 处理平台异常
}
3.3 链接变化事件 (String)
通常你会检查 getInitialLink
,同时监听链接的变化。
StreamSubscription? _sub;
Future<void> initUniLinks() async {
// 检查初始链接
// 监听链接变化
_sub = linkStream.listen((String? link) {
// 解析链接并根据需要处理
}, onError: (err) {
// 处理异常
});
// 注意:不要忘记在 dispose 中取消订阅
}
3.4 链接变化事件 (Uri)
uriLinkStream
与 linkStream
类似,但返回的是 Uri
对象。
StreamSubscription? _sub;
Future<void> initUniLinks() async {
// 检查初始 URI
// 监听 URI 变化
_sub = uriLinkStream.listen((Uri? uri) {
// 使用 Uri 并根据需要处理
}, onError: (err) {
// 处理异常
});
// 注意:不要忘记在 dispose 中取消订阅
}
3.5 应用启动时的链接处理
如果应用是从终止状态启动的(即不在后台运行),getInitialLink
会返回启动应用的链接,而流不会产生链接。如果应用是在后台运行的,流会生成链接,而 getInitialLink
可能为 null
或是应用启动时的初始链接。
因此,你应该始终检查初始链接(或 URI),并订阅链接变化的流。
4. 完整示例代码
以下是一个完整的示例代码,展示了如何在Flutter应用中使用 uni_links
插件处理链接:
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:uni_links/uni_links.dart';
bool _initialUriIsHandled = false;
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
[@override](/user/override)
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Uri? _initialUri;
Uri? _latestUri;
Object? _err;
StreamSubscription? _sub;
final _scaffoldKey = GlobalKey();
final _cmds = getCmds();
final _cmdStyle = const TextStyle(
fontFamily: 'Courier', fontSize: 12.0, fontWeight: FontWeight.w700);
[@override](/user/override)
void initState() {
super.initState();
_handleIncomingLinks();
_handleInitialUri();
}
[@override](/user/override)
void dispose() {
_sub?.cancel();
super.dispose();
}
/// 处理传入的链接 - 应用程序已经启动时从操作系统接收的链接
void _handleIncomingLinks() {
if (!kIsWeb) {
// 处理应用已经启动时的链接
_sub = uriLinkStream.listen((Uri? uri) {
if (!mounted) return;
print('got uri: $uri');
setState(() {
_latestUri = uri;
_err = null;
});
}, onError: (Object err) {
if (!mounted) return;
print('got err: $err');
setState(() {
_latestUri = null;
if (err is FormatException) {
_err = err;
} else {
_err = null;
}
});
});
}
}
/// 处理初始 Uri - 应用启动时的链接
///
/// **注意**: `getInitialLink`/`getInitialUri` 应该在整个应用生命周期中只处理一次。
Future<void> _handleInitialUri() async {
if (!_initialUriIsHandled) {
_initialUriIsHandled = true;
_showSnackBar('_handleInitialUri called');
try {
final uri = await getInitialUri();
if (uri == null) {
print('no initial uri');
} else {
print('got initial uri: $uri');
}
if (!mounted) return;
setState(() => _initialUri = uri);
} on PlatformException {
print('falied to get initial uri');
} on FormatException catch (err) {
if (!mounted) return;
print('malformed initial uri');
setState(() => _err = err);
}
}
}
[@override](/user/override)
Widget build(BuildContext context) {
final queryParams = _latestUri?.queryParametersAll.entries.toList();
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('uni_links 示例应用'),
),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
children: [
if (_err != null)
ListTile(
title: const Text('错误', style: TextStyle(color: Colors.red)),
subtitle: Text('$_err'),
),
ListTile(
title: const Text('初始 Uri'),
subtitle: Text('$_initialUri'),
),
if (!kIsWeb) ...[
ListTile(
title: const Text('最新 Uri'),
subtitle: Text('$_latestUri'),
),
ListTile(
title: const Text('最新 Uri (路径)'),
subtitle: Text('${_latestUri?.path}'),
),
ExpansionTile(
initiallyExpanded: true,
title: const Text('最新 Uri (查询参数)'),
children: queryParams == null
? const [ListTile(dense: true, title: Text('null'))]
: [
for (final item in queryParams)
ListTile(
title: Text(item.key),
trailing: Text(item.value.join(', ')),
)
],
),
],
_cmdsCard(_cmds),
const Divider(),
if (!kIsWeb)
ListTile(
leading: const Icon(Icons.error, color: Colors.red),
title: const Text(
'强制退出此示例应用',
style: TextStyle(color: Colors.red),
),
onTap: () {
if (Platform.isIOS) {
exit(0);
} else {
SystemNavigator.pop();
}
},
),
],
),
);
}
Widget _cmdsCard(List<String>? commands) {
Widget platformCmds;
if (commands == null) {
platformCmds = const Center(child: Text('不支持的平台'));
} else {
platformCmds = Column(
children: [
const [
if (kIsWeb)
Text('将此路径附加到 Web 应用的 URL,替换 `#/`:\n')
else
Text('要填充上述字段,请打开终端并运行:\n'),
],
intersperse(
commands.map<Widget>((cmd) => InkWell(
onTap: () => _printAndCopy(cmd),
child: Text('\n$cmd\n', style: _cmdStyle),
)),
const Text('或')),
[
Text(
'(点击上面的任何命令将其打印到控制台/日志并复制到设备剪贴板)',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall,
),
]
].expand((el) => el).toList(),
);
}
return Card(
margin: const EdgeInsets.only(top: 20.0),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: platformCmds,
),
);
}
Future<void> _printAndCopy(String cmd) async {
print(cmd);
await Clipboard.setData(ClipboardData(text: cmd));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制到剪贴板')),
);
}
void _showSnackBar(String msg) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final context = _scaffoldKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(msg),
));
}
});
}
}
List<String>? getCmds() {
late final String cmd;
var cmdSuffix = '';
const plainPath = 'path/subpath';
const args = 'path/portion/?uid=123&token=abc';
const emojiArgs =
'?arr%5b%5d=123&arr%5b%5d=abc&addr=1%20Nowhere%20Rd&addr=Rand%20City%F0%9F%98%82';
if (kIsWeb) {
return [
plainPath,
args,
emojiArgs,
// 无法创建无效 URL,因为浏览器会确保其有效性
];
}
if (Platform.isIOS) {
cmd = '/usr/bin/xcrun simctl openurl booted';
} else if (Platform.isAndroid) {
cmd = '\$ANDROID_HOME/platform-tools/adb shell \'am start'
' -a android.intent.action.VIEW'
' -c android.intent.category.BROWSABLE -d';
cmdSuffix = "'";
} else {
return null;
}
return [
'$cmd "unilinks://host/$plainPath"$cmdSuffix',
'$cmd "unilinks://example.com/$args"$cmdSuffix',
'$cmd "unilinks://example.com/$emojiArgs"$cmdSuffix',
'$cmd "unilinks://@@malformed.invalid.url/path?"$cmdSuffix',
];
}
List<Widget> intersperse(Iterable<Widget> list, Widget item) {
final initialValue = <Widget>[];
return list.fold(initialValue, (all, el) {
if (all.isNotEmpty) all.add(item);
all.add(el);
return all;
});
}
更多关于Flutter统一链接处理插件uni_links5的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter统一链接处理插件uni_links5的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter应用中使用uni_links
插件来统一处理链接的示例代码。这个插件允许你的应用在启动时或前台运行时捕获和处理设备上的深度链接(deep links)。
首先,你需要在你的pubspec.yaml
文件中添加uni_links
依赖:
dependencies:
flutter:
sdk: flutter
uni_links: ^0.5.1 # 请检查最新版本号
然后运行flutter pub get
来获取依赖。
接下来,你需要在你的应用中进行以下设置:
-
iOS配置:
- 在你的
Info.plist
文件中添加以下内容以允许应用响应自定义URL Scheme:<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>yourappscheme</string> <!-- 替换为你的应用scheme --> </array> </dict> </array>
- 在你的
-
Android配置:
- 在你的
android/app/src/main/AndroidManifest.xml
文件中添加以下内容来声明你的Intent Filter:<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="yourapp.com" /> <!-- 替换为你的应用链接 --> <data android:scheme="http" android:host="yourapp.com" /> <!-- 可选,如果你的应用也支持http --> </intent-filter> </activity>
- 在你的
-
Flutter代码实现:
-
在你的
main.dart
文件中,添加以下代码来处理链接:import 'package:flutter/material.dart'; import 'package:uni_links/uni_links.dart'; void main() { runApp(MyApp()); // 初始化uni_links监听器 _initUniLinks(); } void _initUniLinks() async { // 获取启动时的链接(如果有) final InitialUri? initialUri = await getInitialUri(); handleDeepLink(initialUri); // 监听后续的链接 uriLinkStream.listen((Uri? uri) { handleDeepLink(uri); }, onError: (err) { print('Error in listening to uri: $err'); }); } void handleDeepLink(Uri? uri) { if (uri != null) { print('Received URI: $uri'); // 在这里处理URI,例如导航到特定的页面 // Navigator.pushNamed(context, '/yourRoute', arguments: {'uri': uri.toString()}); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Deep Link Example', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter Deep Link Example'), ), body: Center( child: Text('Open your app with a deep link to see it in action!'), ), ); } }
-
在这个示例中,我们:
- 添加了
uni_links
依赖。 - 配置了iOS和Android以允许应用响应深度链接。
- 在Flutter代码中初始化了
uni_links
监听器,以处理应用启动时的链接以及后续接收到的链接。
你可以根据需要修改handleDeepLink
函数中的逻辑,以处理不同的URI并执行相应的操作,例如导航到应用中的特定页面。