flutter-permission_handler、flutter_blue(蓝牙使用)、image_picker、flutter_progress_hud
前言
本篇文章主要介绍 permission_handler、flutter_blue
使用,案例中会使用 image_picker、flutter_progress_hud
,因此后面两个也会简单介绍下
image_gallery_saver(单纯的保存图片到相册)
蓝牙广播外设demo-swift版本:如果想念 Object-C
版本,前面也有,这个就是那个翻译改动而来
flutter_progress_hud、image_picker
先简单介绍一下这两个,一个是加载框(flutter_progress_hud
),一个是图片选择器(支持照片和图片)(flutter_progress_hud
)
flutter_progress_hud
使用比较简单不多说了,用的也比较多,一般请求网络数据使用,如果还有不满足,可以搜索一下其他的,这个只是最基础的
Scaffold(
appBar: AppBar(
title: const Text("ProgressHUD"),
),
//嵌套在外层,展示加载框时会在中间,并盖住
//里面有些属性可以微调,可以点进去查看
body: ProgressHUD(
child: Container(),
),
);
显示或者隐藏
//显示HUD
ProgressHUD.of(context)?.show();
//显示带文字的HUD
ProgressHUD.of(context)?.showWithText(text);
//隐藏HUD
ProgressHUD.of(context)?.dismiss();
简单看一下效果吧
image_picker
这个用的也比较多,拍照、相册选择图片,一般只要用户头像、上传反馈的都会用到,也比较常见
import 'package:image_picker/image_picker.dart';
//声明参数
final ImagePicker _picker = ImagePicker();
使用相机拍摄照片
_picker.pickImage(source: ImageSource.camera).then((value) {
print(value);
}).catchError((error) {
//失败了走这里,可以在这里判断权限
Permission.camera.status
});
使用相册选择图片
_picker.pickImage(source: ImageSource.gallery).then((value) {
print(value);
}).catchError((error) {
//注意相册使用的是这个权限
Permission.photos.status
});
可以看到image_picker
使用过程中设计到了权限,后面会给出权限的使用
permission_handler
介绍权限的使用,会给出几个案例,同时也是蓝牙使用时判断权限时会用到的一个库
参数与基础使用
默认的权限状态
//常见的有下面几种状态
/*
denied, //没授权默认是这个,也保不准特殊情况是表示拒绝的,最好是先请求权限后用于判断
granted, //正常使用
restricted, //被操作系统拒绝,例如家长控制等
limited, //被限制了部分功能,适用于部分权限,例如相册的
permanentlyDenied, //这个权限表示永久拒绝,不显示弹窗,用户可以手动调节(也有可能是系统关闭了该权限导致的)
*/
请求权限,在使用前,可以通过 request
来请求权限,避免有些权限没办法给出准确提示,且可能出错
//提前请求权限,如果没给过权限,可以触发权限,以便于获取权限信息
final status = await Permission.camera.request(); //请求单个权限
Map<Permission, PermissionStatus> statuses = await [
Permission.photosAddOnly,
Permission.photos
].request(); //同时请求多个全新啊
//可以同时请求多个 await,被限制的部分权限理论也可以使用才是,根据情况作出判断
if (status != PermissionStatus.granted) {
//无相机权限,请前往设置打开
//openAppSettings();
return;
}
//使用对应功能即可
await _picker.pickImage(source: ImageSource.camera)
直接获取权限,但是初次使用并不会触发对应的权限请求弹窗,因此可能判断出错,适用于使用对应功能失败后给出相应的提示
//直接拍照获取图片
_picker.pickImage(source: ImageSource.camera).then((value) {
print(value);
}).catchError((error) {
//可能用户拒绝了权限,因此会走到这里
print(error);
//可以请求用失败的时候在提示权限问题,或者打开授权页面
Permission.camera.status.then((status) {
print(status);
if (status == PermissionStatus.denied || status == PermissionStatus.permanentlyDenied) {
//拒绝,可以跳转权限页面
showToast(context, "相机权限被拒绝");
}else if (status == PermissionStatus.granted) {
//正常访问
showToast(context, "相机权限可以正常使用");
}
});
});
注意
:不是所有的三方没给权限都会走到catch
,也可能直接报错闪退,因此最好先判断权限后给出提示
//简单给出几个常用权限,里面还有很多
Map<Permission, PermissionStatus> statuses = await [
Permission.camera, //相机
Permission.photosAddOnly, //写入相册
Permission.photos, //读取相册
Permission.locationWhenInUse, //使用应用期间获取定位
Permission.bluetooth, //获取蓝牙
].request();
跳转权限操作界面
如果用户没打开权限,可以提示给用户打开权限,下面可以跳转到应用的权限界面,方便用户更新权限
openAppSettings();
android权限声明
需要注意的是,使用什么权限搜索一下即可,使用如下所示(需要注意的是,其有些权限跟ios端是不一样的,例如)
<uses-permission android:name="android.permission.CAMERA" />
<!--相册的读写权限,跟ios端不一样哈,其他的有些也是,根据平台动态调整即可-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
ios端权限声明
在 info.plist
中设置对应权限即可,$(PRODUCT_BUNDLE_NAME)
为app的名字,动态最好
<string>$(PRODUCT_BUNDLE_NAME)想在app使用期间获取您的定位,以便于更新位置</string>
<key>NSBluetoothPeripheralUsageDescription</key>
此外,还需要在 podfile
中加入下面的脚本(放到最后即可),根据对应权限需要,将 PERMISSION_???=1
前面的注释 #
去掉即可(上面的不要取消),这里假设去掉的是 camera
post_install do |installer|
installer.pods_project.targets.each do |target|
... # Here are some configurations automatically generated by flutter
# Start of the permission_handler configuration
target.build_configurations.each do |config|
# You can enable the permissions needed here. For example to enable camera
# permission, just remove the `#` character in front so it looks like this:
#
# ## dart: PermissionGroup.camera
# 'PERMISSION_CAMERA=1'
#
# Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.calendar
# 'PERMISSION_EVENTS=1',
## dart: PermissionGroup.reminders
# 'PERMISSION_REMINDERS=1',
## dart: PermissionGroup.contacts
# 'PERMISSION_CONTACTS=1',
## dart: PermissionGroup.camera
## 假设这里面用到了相机权限,只取消掉其前面的注释即可
'PERMISSION_CAMERA=1',
## dart: PermissionGroup.microphone
# 'PERMISSION_MICROPHONE=1',
## dart: PermissionGroup.speech
# 'PERMISSION_SPEECH_RECOGNIZER=1',
## dart: PermissionGroup.photos
# 'PERMISSION_PHOTOS=1',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=1',
## dart: PermissionGroup.notification
# 'PERMISSION_NOTIFICATIONS=1',
## dart: PermissionGroup.mediaLibrary
# 'PERMISSION_MEDIA_LIBRARY=1',
## dart: PermissionGroup.sensors
# 'PERMISSION_SENSORS=1',
## dart: PermissionGroup.bluetooth
# 'PERMISSION_BLUETOOTH=1',
## dart: PermissionGroup.appTrackingTransparency
# 'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
## dart: PermissionGroup.criticalAlerts
# 'PERMISSION_CRITICAL_ALERTS=1'
]
end
# End of the permission_handler configuration
end
end
再具体点的权限,都可以通过搜索一下获取即可,具体的就不多讲述了(ios的文档给出了一些,先贴出来方便大家使用吧)
Permission | Info.plist | Macro |
---|---|---|
PermissionGroup.calendar | NSCalendarsUsageDescription | PERMISSION_EVENTS |
PermissionGroup.reminders | NSRemindersUsageDescription | PERMISSION_REMINDERS |
PermissionGroup.contacts | NSContactsUsageDescription | PERMISSION_CONTACTS |
PermissionGroup.camera | NSCameraUsageDescription | PERMISSION_CAMERA |
PermissionGroup.microphone | NSMicrophoneUsageDescription | PERMISSION_MICROPHONE |
PermissionGroup.speech | NSSpeechRecognitionUsageDescription | PERMISSION_SPEECH_RECOGNIZER |
PermissionGroup.photos | NSPhotoLibraryUsageDescription | PERMISSION_PHOTOS |
PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse | NSLocationUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationWhenInUseUsageDescription | PERMISSION_LOCATION |
PermissionGroup.notification | PermissionGroupNotification | PERMISSION_NOTIFICATIONS |
PermissionGroup.mediaLibrary | NSAppleMusicUsageDescription, kTCCServiceMediaLibrary | PERMISSION_MEDIA_LIBRARY |
PermissionGroup.sensors | NSMotionUsageDescription | PERMISSION_SENSORS |
PermissionGroup.bluetooth | NSBluetoothAlwaysUsageDescription, NSBluetoothPeripheralUsageDescription | PERMISSION_BLUETOOTH |
PermissionGroup.appTrackingTransparency | NSUserTrackingUsageDescription | PERMISSION_APP_TRACKING_TRANSPARENCY |
PermissionGroup.criticalAlerts | PermissionGroupCriticalAlerts | PERMISSION_CRITICAL_ALERTS |
flutter_blue 与 蓝牙权限
官方提供的蓝牙库,使用非常简单,使用前需要先判断权限
注
:ios系统有些版本是没有权限弹窗的,声明了就可以直接使用,但不代表不需要判断申请了,后面大多数版本还是需要用到的
外设端demo:被连接的外设广播端,由swift
编写,前面也有object-c
编写的版本
蓝牙广播外设demo-swift版本:根据上面的广播端代码翻译改动而来(都是自己的😂)
本案例demo:中心设备端,在本案例中的bluetooth
文件中
PS
: 为什么没有 flutter 的广播端案例,因为没提供,flutter 跨平台作为一个中心设备连接已经很给力了,目前蓝牙对android、ios支持不错,对鸿蒙支持很拉跨😂
蓝牙权限配置
ios端配置
这里面配置就比较固定,只不过有一个alway
权限的,表示允许后台持续运行蓝牙,如果不是必须的话无需填写,否则易造成审核问题,目前无需申请定位,也不用打开即可正常使用蓝牙
info.plist
<key>NSBluetoothAlwaysUsageDescription</key>
<string>$(PRODUCT_BUNDLE_NAME)想使用您的蓝牙,以便于持续跟设备连接发送信息</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>$(PRODUCT_BUNDLE_NAME)想使用您的蓝牙,以便于跟设备发送信息</string>
podfile
//取消这一行的注释即可
'PERMISSION_BLUETOOTH=1',
安卓端配置
安卓端还有点不一样,android 12
后,推出新权限,可以在在 manifest.xml
中填写如下代码,也可以在代码中动态申请
前面两个是兼容 android12
之前的版本,所以限制了最大版本,后面则是分开两个,需要注意的是,现在都需要申请定位权限了,否则功能无法正常使用
manifest.xml
<!-- android6之前使用该权限即可 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- Required if your app derives physical location from Bluetooth scan results.-->
<!-- android6之后需要使用蓝牙也要声明定位权限,且用户还要打开 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Request legacy Bluetooth permissions on older devices.-->
<!-- android6~12 之间使用该权限-->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<!-- android12 之后权限分开-->
<!-- android12 扫描周边设备需要该权限-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- android12 连接交互使用的权限-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- android12 广播功能,让别人也能连接搞设备,一般和connect一起使用-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
build.gradle
Android {
defaultConfig {
minSdkVersion: 19
flutter 端中心设备使用
设备最为中心设备扫描周边设备,大致经过了下面几个阶段:
扫描
-> 连接
-> 查找服务
-> 查找特征
-> 使用特征进行交互
细节直接看代码即可,下面简单上一个权限的判断,android
实际还要请求定位,即:
ios
请求一个 bluetooth
权限,android
请求 bluetooth、bluetoothScan、bluetoothConnect、locationWhenInUse
四个权限,自己动态设置即可
ps
:除了要判断权限
还有蓝牙是否打开
,此外这里面权限判断不如原生端准确,一些系统版本的不需要权限,但是这里可能会返回拒绝,因此最好给这些特殊版本一些提示,以便于"没给权限"
也能继续执行(否则他们永远无法使用蓝牙了,只能更新系统)
//仅仅是一个案例,实际可以写的严谨简单一些,先判断打开,后判断权限都可以,扫描前检测即可
//请求一下权限,某些机型蓝牙默认可以直接使用,无需权限,但是权限会反馈被拒绝状态
//因此一些机型,可以请求后给出继续向后执行的弹窗,避免用户永远无法使用该功能,失败了注意反馈即可
[Permission.bluetoothScan, Permission.bluetoothConnect, Permission.bluetooth].request().then((status) {
print(status);
//只会有一个弹窗,android端多个权限,但一般一般显示了一个就结束了,主要看ios的
//android端注意定位权限, Permission.locationWhenInUse 即可
if (status[Permission.bluetooth] != PermissionStatus.granted) {
print("没有蓝牙权限"); //这一个是都有的,可以用这个判断,当然最好分平台判断
}
});
//还要判断蓝牙是否已经打开
_bluetooth.isOn.then((value) {
//获取蓝牙打开状态
});
扫描、连接、查找服务和特征逻辑如下所示,此外还设置类的通知,以便于能够及时收到消息
//开始扫描并连接设备
void startConnectDevice({isRepeat = false}) {
bool isSearched = false; //为了避免连接过程中没有停止扫描导致的多次连接问题,但不得不连接后在取消扫描
try {
_bluetooth.scanResults.listen((results) async {
//扫描回调,try-catch是统一处理里面的失败情况
if (isSearched) return; //已经找到了就结束
for (ScanResult res in results) {
final device = res.device;
//检查是否是我们需要的设备,device.name有时候不一定会有,最好使用advertisementData.localName
final name = res.advertisementData.localName;
if (name != "") print(name);
if (!isSearched && name.contains("marshal_")) {
print("找到了我们需要的设备");
isSearched = true; //标记已经找到了
//找到了我们要连接的设备,并连接,autoConnect需要设置为 false,默认为true其不会自动连接
print("开始连接");
//鸿蒙这个 autoConnect 会一次也连接不上,设置为 false 则会出现时而连得上时而连不上,支持不太好,根据情况选择
await device.connect(timeout: const Duration(seconds: 10), autoConnect: true);
print("连接成功");
// 连接成功后停止扫描,据说有些android手机停止扫描会出现连接功能异常,连接成功后在停止扫描
await _bluetooth.stopScan();
//开始查找服务
print("开始查找服务");
List<BluetoothService> services = await device.discoverServices();
//遍历服务使用我们想要的特征值
print("遍历服务找特征值");
print(services.length);
services.forEach((service) async {
var characteristics = service.characteristics;
//找我们的特征值
for(BluetoothCharacteristic char in characteristics) {
print(char.uuid.toString());
//通过uuid过滤出我们需要的特征值
if (char.uuid.toString().substring(0, 2) == "12") {
print("找到我们特征值");
//假设我们用这个,获取到特征值后保存,可以用来读写
_currentCharacteristic = char;
await setNotifiy();//设置通知,可以接收传递过来的消息
await readMessage(); //随便读取一下消息吧
}
}
});
}
}
});
}catch(error) {
print(error);
isSearched = false;
print('一般连接失败后会走这里,然后停止扫描');
_bluetooth.stopScan();
}
print("开始扫描");
_bluetooth.startScan(scanMode: ScanMode.balanced, timeout: const Duration(seconds: 30),);
}
接下来看看读写消息代码,比较简单,需要注意的是,发送接收的字符串一般使用 utf8
转化
此外,如果传输数据量比较大的话,需要多次传递就行了(因为设备缘故一次不能传递数据过,由于传输长度限制问题,单次长度建议跟 UUID 长度一样16字节最佳,当然自己可以尝试更长的字符串(一般超过20字节就会丢失了))
//设置通知
Future<void> setNotifiy() async {
//可以监听外设端主动发送过来的消息
await _currentCharacteristic!.setNotifyValue(true);
_currentCharacteristic!.value.listen((event) {
print(event);
});
}
//读取外设消息
Future<void> readMessage() async {
if (_currentCharacteristic == null) return;
List<int> value = await _currentCharacteristic!.read();
print("value");
print(value);
final string = utf8.decode(value);
print(string);
}
//向外设发送消息
void sendMessage(String text) {
print(text);
_controller.clear();
_currentCharacteristic?.write(utf8.encode(text)).then((value) => print(value));
}
最后
快来尝试一下吧,两个案例都是通的,如果想玩更有趣的,可以更新两端代码,相互交互,写一个情侣间的小型传输工具也不是未尝不可😂
转载自:https://juejin.cn/post/7162473345689059359