Flutter近距离服务插件nearby_service的使用
Flutter近距离服务插件nearby_service的使用
Overview
Nearby Service 是一个用于在Flutter中创建P2P网络连接的插件。它支持发送文本消息和文件,可以在没有互联网连接的情况下轻松创建任何类型的信息共享应用程序。该插件目前仅支持Android-Android和iOS-iOS设备之间的通信。
访问Xenikii官网 | 查看LICENSE | Pub包页面
Table of Contents
About
Peer-to-peer (P2P) 网络是一种去中心化的网络架构,每个参与者(称为peer)都可以作为客户端和服务器。这意味着P2P网络可以直接交换资源(如文件、数据或服务),而无需中央权威或服务器。
About Android Plugin
对于Android,nearby_service
使用Wi-Fi Direct作为P2P网络,通过android.net.wifi.p2p
模块实现。需要权限ACCESS_FINE_LOCATION
和NEARBY_WIFI_DEVICES
(针对Android 13+)。确保设备上的Wi-Fi已启用。
About iOS Plugin
对于iOS,P2P连接通过Multipeer Connectivity框架实现。此框架会自动选择最佳网络技术——如果两个设备在同一网络上,则使用Wi-Fi;否则使用对等Wi-Fi或蓝牙。
Features
-
Device Preparation
- 请求使用Wi-Fi Direct的权限(仅限Android)
- 检查Wi-Fi状态(仅限Android)
- 打开设置以启用Wi-Fi(Android和iOS)
- 角色选择 - 浏览器或广告商(仅限iOS)
-
Connecting to the Device from a P2P Network
- 监听发现的设备(peers)
- 在P2P网络上创建连接
- 监控连接设备的状态
-
The Data Transmission Channel
- 建立数据交换通道
- 监控通道状态
-
Typed Data Exchange
- 在P2P网络上传输文本数据
- 接收成功传输文本消息的确认
- 在P2P网络上传输文件
- 确认或拒绝接收文件
Setup
Android Setup
所有必要的Android权限已经在插件的AndroidManifest.xml
中配置好,因此无需额外添加。
iOS Setup
在Info.plist
中添加以下内容:
<key>NSBonjourServices</key>
<array>
<string>_mp-connection._tcp</string>
</array>
<key>UIRequiresPersistentWiFi</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>[Your description here]</string>
Usage
以下是使用nearby_service
插件的基本步骤:
-
导入包:
import 'package:nearby_service/nearby_service.dart';
-
创建
NearbyService
实例:final _nearbyService = NearbyService.getInstance();
-
初始化插件:
await _nearbyService.initialize( data: NearbyInitializeData(iosDeviceName: iosDeviceName), );
-
对于Android,请求权限并检查Wi-Fi是否开启:
final granted = await _nearbyService.android?.requestPermissions(); if (granted ?? false) { // 继续下一步 } final isWifiEnabled = await _nearbyService.android?.checkWifiService(); if (isWifiEnabled ?? false) { // 继续下一步 }
-
对于iOS,让用户选择是浏览器还是广告商:
_nearbyService.ios?.setIsBrowser(value: isBrowser);
-
开始发现P2P网络:
final result = await _nearbyService.discover(); if (result) { // 继续监听peers }
-
监听peers:
_nearbyService.getPeersStream().listen((event) => peers = event);
-
连接到设备:
final result = await _nearbyService.connect(device); if (result) { // 继续监听设备 }
-
监听连接设备:
_connectedDeviceSubscription = _nearbyService.getConnectedDeviceStream(device).listen( (event) async { final wasConnected = connectedDevice?.status.isConnected ?? false; final nowConnected = event?.status.isConnected ?? false; if (wasConnected && !nowConnected) { // 返回到发现状态 } connectedDevice = event; }, );
-
启动通信通道:
final messagesListener = NearbyServiceMessagesListener( onData: (message) { // 处理从NearbyServiceMessagesListener接收到的消息 }, ); final filesListener = NearbyServiceFilesListener( onData: (pack) async { // 处理从NearbyServiceFilesListener接收到的文件包 }, ); await _nearbyService.startCommunicationChannel( NearbyCommunicationChannelData( connectedDevice.info.id, messagesListener: messagesListener, filesListener: filesListener, ), );
-
发送消息:
_nearbyService.send( OutgoingNearbyMessage( content: NearbyMessageTextRequest.create(value: message), receiver: connectedDevice.info, ), );
Data Sharing
Text Messages
描述操作逻辑:
-
设备A发送消息:
_nearbyService.send( OutgoingNearbyMessage( content: NearbyMessageTextRequest.create(value: message), receiver: connectedDevice.info, ), );
-
设备B接收消息:
final messagesListener = NearbyServiceMessagesListener( onData: (message) { // message是ReceivedNearbyMessage,内容为NearbyMessageTextRequest }, );
-
设备B发送响应:
_nearbyService.send( OutgoingNearbyMessage( receiver: connectedDevice.info, content: NearbyMessageTextResponse(id: requestId), ), );
-
设备A接收响应:
final messagesListener = NearbyServiceMessagesListener( onData: (message) { // message是ReceivedNearbyMessage,内容为NearbyMessageTextResponse }, );
Resource Messages
描述操作逻辑:
-
设备A发送文件请求:
_nearbyService.send( OutgoingNearbyMessage( content: NearbyMessageFilesRequest.create(files: files), receiver: connectedDevice.info, ), );
-
设备B接收文件请求:
final messagesListener = NearbyServiceMessagesListener( onData: (message) { // message是ReceivedNearbyMessage,内容为NearbyMessageFilesRequest }, );
-
设备B确认接收文件:
_nearbyService.send( OutgoingNearbyMessage( receiver: connectedDevice!.info, content: NearbyMessageFilesResponse( id: request.id, isAccepted: isAccepted, ), ), );
-
设备A接收确认:
final messagesListener = NearbyServiceMessagesListener( onData: (message) { // message是ReceivedNearbyMessage,内容为NearbyMessageFilesResponse }, );
Exceptions
NearbyServiceUnsupportedPlatformException
: 插件不支持的平台NearbyServiceUnsupportedDecodingException
: 解码错误NearbyServiceInvalidMessageException
: 发送无效消息
Additional Options
- 获取当前设备信息:
getCurrentDeviceInfo()
- 获取通信通道状态:
communicationChannelState
- 设置日志级别:
logLevel
Demo
Android
iOS
示例代码
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:nearby_service/nearby_service.dart';
enum AppState { idle, discovering, connected }
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nearby Service Example',
home: Scaffold(
appBar: AppBar(title: const Text('Nearby Service Example')),
body: Container(
alignment: Alignment.topCenter,
padding: const EdgeInsets.all(20),
child: const AppBody(),
),
),
);
}
}
class AppBody extends StatefulWidget {
const AppBody({super.key});
@override
State<AppBody> createState() => _AppBodyState();
}
class _AppBodyState extends State<AppBody> {
late final _nearbyService = NearbyService.getInstance(logLevel: NearbyServiceLogLevel.debug);
AppState _state = AppState.idle;
bool _isIosBrowser = true;
List<NearbyDevice> _peers = [];
StreamSubscription? _peersSubscription;
CommunicationChannelState _communicationChannelState = CommunicationChannelState.notConnected;
Timer? _connectionCheckTimer;
NearbyDevice? _connectedDevice;
@override
void initState() {
_initialize();
super.initState();
}
@override
void dispose() {
_peersSubscription?.cancel();
_connectionCheckTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_state == AppState.idle) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (Platform.isIOS)
IOSRoleSelector(
isIosBrowser: _isIosBrowser,
onSelect: (value) => setState(() => _isIosBrowser = value),
),
ElevatedButton(
onPressed: _startProcess,
child: const Text('Start'),
),
],
);
} else if (_state == AppState.discovering) {
return ListView(
children: [
if (Platform.isIOS)
Text('You are ${_isIosBrowser ? 'Browser' : 'Advertiser'}'),
if (_peers.isEmpty) const Text('Searching for peers...'),
..._peers.map(
(e) => PeerWidget(
device: e,
isIosBrowser: _isIosBrowser,
onConnect: _connect,
communicationChannelState: _communicationChannelState,
),
),
],
);
} else if (_state == AppState.connected) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ConnectedDeviceView(
device: _connectedDevice!,
onDisconnect: _disconnect,
),
const SizedBox(height: 50),
TextMessagingView(
onSend: (message) => _send(NearbyMessageTextRequest.create(value: message)),
),
const SizedBox(height: 50),
FilesMessagingView(
onSend: (files) {
_send(
NearbyMessageFilesRequest.create(files: files),
);
},
),
],
),
);
}
return Container();
}
Future<void> _initialize() async {
await _nearbyService.initialize();
}
Future<void> _startProcess() async {
final platformsReady = await _checkPlatforms();
if (platformsReady) {
await _discover();
}
}
Future<bool> _checkPlatforms() async {
if (Platform.isAndroid) {
final isGranted = await _nearbyService.android?.requestPermissions();
final wifiEnabled = await _nearbyService.android?.checkWifiService();
return (isGranted ?? false) && (wifiEnabled ?? false);
} else if (Platform.isIOS) {
_nearbyService.ios?.setIsBrowser(value: _isIosBrowser);
return true;
} else {
return false;
}
}
Future<void> _discover() async {
final result = await _nearbyService.discover();
if (result) {
setState(() {
_state = AppState.discovering;
});
_peersSubscription = _nearbyService.getPeersStream().listen((event) {
setState(() {
_peers = event;
});
});
}
}
Future<void> _connect(NearbyDevice device) async {
final result = await _nearbyService.connectById(device.info.id);
if (result || device.status.isConnected) {
final channelStarting = _tryCommunicate(device);
if (!channelStarting) {
_connectionCheckTimer = Timer.periodic(
const Duration(seconds: 3),
(_) => _tryCommunicate(device),
);
}
}
}
bool _tryCommunicate(NearbyDevice device) {
NearbyDevice? selectedDevice;
try {
selectedDevice = _peers.firstWhere(
(element) => element.info.id == device.info.id,
);
} catch (_) {
return false;
}
if (selectedDevice.status.isConnected) {
try {
_startCommunicationChannel(device);
} finally {
_connectionCheckTimer?.cancel();
_connectionCheckTimer = null;
}
return true;
}
return false;
}
void _startCommunicationChannel(NearbyDevice device) {
if (_communicationChannelState != CommunicationChannelState.notConnected) {
return;
}
_nearbyService.getCommunicationChannelStateStream().listen((event) {
_communicationChannelState = event;
});
_nearbyService.startCommunicationChannel(
NearbyCommunicationChannelData(
device.info.id,
filesListener: NearbyServiceFilesListener(
onData: (pack) => _filesListener(Scaffold.of(context).context, pack),
),
messagesListener: NearbyServiceMessagesListener(
onData: _messagesListener,
onCreated: () {
setState(() {
_connectedDevice = device;
_state = AppState.connected;
});
},
onError: (e, [StackTrace? s]) {
setState(() {
_connectedDevice = null;
_state = AppState.idle;
});
},
onDone: () {
setState(() {
_connectedDevice = null;
_state = AppState.idle;
});
},
),
),
);
}
Future<void> _disconnect() async {
try {
await _nearbyService.disconnectById(_connectedDevice!.info.id);
} finally {
await _nearbyService.endCommunicationChannel();
await _nearbyService.stopDiscovery();
await _peersSubscription?.cancel();
setState(() {
_peers = [];
_state = AppState.idle;
});
}
}
void _messagesListener(ReceivedNearbyMessage<NearbyMessageContent> message) {
if (_connectedDevice == null) return;
message.content.byType(
onTextRequest: (request) {
AppSnackBar.show(
context,
title: request.value,
subtitle: message.sender.displayName,
).whenComplete(
() => _send(NearbyMessageTextResponse(id: request.id)),
);
},
onTextResponse: (response) {
AppSnackBar.show(
context,
title: 'Message ${response.id} was delivered',
subtitle: message.sender.displayName,
);
},
onFilesRequest: (request) {
AppSnackBar.show(
context,
title: 'Request to receive ${request.files.length} files',
subtitle: message.sender.displayName,
actionName: 'Accept the files?',
onAcceptAction: () {
_send(
NearbyMessageFilesResponse(id: request.id, isAccepted: true),
);
},
);
},
onFilesResponse: (response) {
AppSnackBar.show(
context,
title: 'Response ${response.id} for the files '
'${response.isAccepted ? 'is accepted' : 'was denied'}',
subtitle: message.sender.displayName,
);
},
);
}
Future<void> _filesListener(
BuildContext context,
ReceivedNearbyFilesPack pack,
) async {
await FilesSaver.savePack(pack);
if (context.mounted) {
AppSnackBar.show(context, title: 'Files pack was saved');
}
}
Future<void> _send(NearbyMessageContent content) async {
if (_connectedDevice == null) return;
await _nearbyService.send(
OutgoingNearbyMessage(
content: content,
receiver: _connectedDevice!.info,
),
);
}
}
希望这个详细的指南能帮助你更好地理解和使用nearby_service
插件。如果有任何问题或建议,请随时联系我!
更多关于Flutter近距离服务插件nearby_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter近距离服务插件nearby_service的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用nearby_service
插件的示例代码。这个插件允许你的应用使用Google Play服务的近距离服务功能(Nearby Connections API)来与其他设备进行通信。
首先,确保你已经在pubspec.yaml
文件中添加了nearby_service
依赖:
dependencies:
flutter:
sdk: flutter
nearby_service: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,我们编写一个基本的Flutter应用,展示如何使用nearby_service
插件。
主应用代码(main.dart
)
import 'package:flutter/material.dart';
import 'package:nearby_service/nearby_service.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nearby Service Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NearbyServiceDemo(),
);
}
}
class NearbyServiceDemo extends StatefulWidget {
@override
_NearbyServiceDemoState createState() => _NearbyServiceDemoState();
}
class _NearbyServiceDemoState extends State<NearbyServiceDemo> {
NearbyService? _nearbyService;
String _status = "Not started";
List<String> _endpoints = [];
@override
void initState() {
super.initState();
initNearbyService();
}
void initNearbyService() async {
_nearbyService = NearbyService();
_nearbyService!.startAdvertising(
serviceName: "MyService",
strategy: Strategy.P2P_CLUSTER,
callback: (endpointId, endpointInfo, status) {
setState(() {
if (status == Status.CONNECTED) {
_endpoints.add(endpointId);
_status = "Connected to ${_endpoints.length} devices";
} else if (status == Status.DISCONNECTED) {
_endpoints.remove(endpointId);
_status = "Connected to ${_endpoints.length} devices";
}
});
},
);
_nearbyService!.startDiscovery(
serviceName: "MyService",
strategy: Strategy.P2P_CLUSTER,
callback: (endpointId, endpointInfo, status) {
setState(() {
if (status == Status.CONNECTED) {
_endpoints.add(endpointId);
_status = "Connected to ${_endpoints.length} devices";
} else if (status == Status.DISCONNECTED) {
_endpoints.remove(endpointId);
_status = "Connected to ${_endpoints.length} devices";
}
});
},
);
}
void sendMessage(String endpointId, String message) async {
await _nearbyService!.sendPayload(
endpointId: endpointId,
payload: Payload(
type: PayloadType.BYTES,
content: Uint8List.fromList(message.codeUnits),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Nearby Service Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status: $_status'),
SizedBox(height: 16),
if (_endpoints.isNotEmpty)
ListView.builder(
shrinkWrap: true,
itemCount: _endpoints.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Endpoint: ${_endpoints[index]}'),
trailing: IconButton(
icon: Icon(Icons.send),
onPressed: () {
sendMessage(_endpoints[index], "Hello, Nearby!");
},
),
);
},
),
],
),
),
);
}
@override
void dispose() {
_nearbyService?.stopAllEndpoints();
super.dispose();
}
}
注意事项
-
权限:确保在
AndroidManifest.xml
中添加了必要的权限,例如BLUETOOTH
,BLUETOOTH_ADMIN
,ACCESS_FINE_LOCATION
,ACCESS_COARSE_LOCATION
等。 -
Google Play服务:该插件依赖于Google Play服务,因此你的设备或模拟器需要安装Google Play服务。
-
策略选择:根据你的需求选择合适的策略(
Strategy.P2P_CLUSTER
,Strategy.P2P_POINT_TO_POINT
,Strategy.P2P_STAR
)。 -
错误处理:在实际应用中,添加更多的错误处理和用户反馈,以提高应用的健壮性和用户体验。
这个示例展示了如何启动广告、发现其他设备、连接设备以及发送消息。你可以根据实际需求进一步扩展和修改这个示例。