以前开发蓝牙通讯,还需要实现蓝牙配对。需要主动跳转到手机设置界面进行PIN码配对,然后配对通过之后才能进行蓝牙链接。而使用BLE库,我们可以直接通过蓝牙设备的UUID进行连接(通过GATT服务),在当前应用内就能直接连接了。而不用通过系统设置。市面上的各种手环的自动匹配链接,电子秤的自动连接等等都是通过GATT进行通讯和链接的。
<!-- 蓝牙搜索配对 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 操纵蓝牙的开启-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 如果应用必须安装在支持蓝牙的设备上,可以将下面的required的值设置为true。-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
//不支持蓝牙设备
finish();
} else {
//支持蓝牙设备
}final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
//开启设备的蓝牙链接
bluetoothAdapter.enable();//开启蓝牙
//动态判断是否拥有位置权限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION ,然后再执行蓝牙扫描
} else {
//动态判断是否拥有位置权限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION,然后再执行蓝牙扫描
}PS:这个方法需要android.Manifest.permission.BLUETOOTH_CONNECT 权限才能使用。官方是建议我们通过Intent让系统设置进行开启蓝牙的。
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
//处理返回结果
}
});Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //创建一个蓝牙启动的意图
launcher.launch(enableBtIntent);//使用launcer启动这个意图就可以了。public class BluetoothFoundReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//监听蓝牙状态之后,发送消息
try {
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
//开始扫描
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//结束扫描
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//发现设备,每扫码到一个设备,都会触发一次
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//我们可以得到蓝牙设备
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//蓝牙开关状态
int statue = bluetoothAdapter.getState();
switch (statue) {
case BluetoothAdapter.STATE_OFF:
Log.e(TAG, "蓝牙状态:,蓝牙关闭");
break;
case BluetoothAdapter.STATE_ON:
Log.e(TAG, "蓝牙状态:,蓝牙打开");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.e(TAG, "蓝牙状态:,蓝牙正在关闭");
break;
case BluetoothAdapter.STATE_TURNING_ON:
Log.e(TAG, "蓝牙状态:,蓝牙正在打开");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}bluetoothFoundReceiver = new BluetoothFoundReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//连接蓝牙,断开蓝牙
filter.addAction(BluetoothDevice.ACTION_FOUND);//找到设备的广播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索完成的广播
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//状态改变 配对开始时,配对成功时
registerReceiver(bluetoothFoundReceiver, filter);@Override
protected void onDestroy() {
if (bluetoothFoundReceiver != null)
unregisterReceiver(bluetoothFoundReceiver); //停止监听
super.onDestroy();
}其实,我们只需要蓝牙状态的监听就可以了BluetoothAdapter.ACTION_STATE_CHANGED 其他的设备查找,配对。可以不用,因为触发到广播的设备查找效率太低,而且多次重复查找时,还会出现耗时变长。设备无法查找到的情况。
bluetoothAdapter.startLeScan(leScanCallback); //查找
bluetoothAdapter.stopLeScan(leScanCallback); //停止查找BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
//不进行权限验证
ScanCallback callback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();//得到设备
// Log.e(TAG, "发现设备" + device.getName());
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "搜索错误" + errorCode);
}
};
scanner.startScan(callback);PS:这种查找方式,不会触发蓝牙的遍历广播。我们如果开启广播进行监听设备扫描情况。如果通过startScan方法,广播中不会有回调。上面是一个通用搜索模式,我们还可以配置自己的过滤条件。例如:
ScanFilter sn =new ScanFilter.Builder().setDeviceName("蓝牙设备的名称").setServiceUuid(ParcelUuid.fromString("我们的设备的Service UUID")).build();
List<ScanFilter> scanFilters=new ArrayList<>();
scanFilters.add(sn);
scanner.startScan(scanFilters, new ScanSettings.Builder().build(),callback);if (bluetoothAdapter.isDiscovering()) {//是否在扫描
bluetoothAdapter.cancelDiscovery(); //停止扫描
}
//查找蓝牙
bluetoothAdapter.startDiscovery();PS:不管是BluetoothLeScanner 还是bluetoothAdapter.startDiscovery() 去查找蓝牙设备。都不建议一直重复扫描。否则会出现无法扫描到设备,没有任何扫描结果等等情况。因为扫描是一个耗时耗电的操作。
BluetoothDevice device;// 当我们通过扫描得到device对象之后,进行Gatt服务创建
BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback);String SERVICE_UUID="00000-000000-000000-000000";//这个是我要链接的蓝牙设备的ServiceUUID
BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
//GATT的链接状态回调
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
Log.v(TAG, "连接成功");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "连接断开");
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
//TODO 在实际过程中,该方法并没有调用
Log.e(TAG, "连接中....");
}
}
//获取GATT服务发现后的回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "GATT_SUCCESS"); //服务发现
for (BluetoothGattService bluetoothGattService : gatt.getServices()) {
Log.e(TAG, "Service_UUID" + bluetoothGattService.getUuid()); // 我们可以遍历到该蓝牙设备的全部Service对象。然后通过比较Service的UUID,我们可以区分该服务是属于什么业务的
if (SERVICE_UUID.equals(bluetoothGattService.getUuid().toString())) {
for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) {
prepareBroadcastDataNotify(gatt, characteristic); //给满足条件的属性配置上消息通知
}
return;//结束循环操作
}
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
}
}
//蓝牙设备发送消息后的自动监听
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// readUUID 是我要链接的蓝牙设备的消息读UUID值,跟通知的特性的UUID比较。这样可以避免其他消息的污染。
if (READ_UUID.equals(characteristic.getUuid().toString())) {
try {
String chara = new String(characteristic.getValue(), "UTF-8");
Log.e(TAG, "消息内容:" + chara);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
};@SuppressLint("MissingPermission")
private void prepareBroadcastDataNotify(BluetoothGatt mBluetoothGatt, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "CharacteristicUUID:" + characteristic.getUuid().toString());
int charaProp = characteristic.getProperties();
//判断属性是否支持消息通知
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
BluetoothGattDescriptor descriptor =
characteristic.getDescriptor(UUID.fromString(UUIDManager.READ_DEDSCRIPTION_UUID));
if (descriptor != null) {
//注册消息通知
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
}String data ="0x12";
BluetoothGattCharacteristic writeCharact = bluetoothGattService.
getCharacteristic(UUID.fromString(WRITE_UUID));
//查找UUID是写的特性,并检测是否拥有写权限
if (writeCharact == null || writeCharact.getProperties() != BluetoothGattCharacteristic.PROPERTY_WRITE) {
return ;//该特性没有写的权限。所以无法传入
}
// 当数据传递到蓝牙之后
// 会回调BluetoothGattCallback里面的write方法
writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
// 将需要传递的数据转为16进制数
writeCharact.setValue(data);
bluetoothGatt.writeCharacteristic(writeCharact);if (bluetoothGatt != null) {
bluetoothGatt.close();
bluetoothGatt.disconnect();
bluetoothGatt = null;
}gattCallback.disConnectBlue();//关闭GATT服务回调监听很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub