项目中需要用到android Ble蓝牙4.0开发技术,于是开启了蓝牙填坑之旅,说实话,蓝牙开发坑真多,跳出一个又进入下一个,每次遇到 问题,就觉得不可能解决了,还好在自己的摸索中,都一一的化解了,以此来记录安卓蓝牙开发的心得。
接手的蓝牙开发项目,原来的同事已经写好,不用再去写,开始也就大概看了看android蓝牙开发相关资料,对比项目中的蓝牙开发代码,发现发现搜索、连接蓝牙有一样的处理也有不一样的处理,想着项目不一样,代码处理肯定不一样,于是就没去深究,毕竟项目运行起来使用正常,然而,没过几天,客户提出蓝牙搜索太慢,然后就去调试,先看代码是搜索蓝牙8秒钟才显示搜索到的蓝牙设备列表,查看日志,其实不到8秒就搜索到外围设备,只是搜索8秒后停止扫描才显示蓝牙设备,那就把显示成3、4秒呗。
正这样想,同事拿来一部荣耀8,装的APP怎么就是搜不到蓝牙设备,然后调试其他手机,都能搜索到,于是就纳闷了,荣耀8怎么就是搜索不到蓝牙设备呢,其他手机都能,再看一遍代码,也没有专门针对荣耀8处理的地方,再想,荣耀8是系统7.0的手机,是不是跟权限没开有关呢,对比其他华为手机,申请的蓝牙权限也都开着,荣耀8就是搜索不到,是不是荣耀8手机有问题,同事告知下载一个第三方蓝牙调试工具查查看,令人疑惑的是荣耀8用第三方蓝牙调试工具立刻就能扫描显示出来,试了几次都是立刻扫描出来,当时心想,我靠,什么鬼,这么迅速就能把蓝牙设备扫描显示出来,领导也说,别人的调试工具都能做这么快,你也得做这么快,想着目前的代码都是几秒后才能搜索到,目前的荣耀8还搜索不出来,客户那边还一个劲儿的给老板吐槽太慢,于是压力剧增,真正开始了蓝牙开发不归之旅。
但是再考虑索慢时,领导说,得把荣耀8搜索不到的问题先解决,因为荣耀8是项目实施人员给客户安装硬件调试数据要用的,那就先把搜索慢暂时放一放,想着一边搜索慢没还没着落,荣耀8手机就是搜索不到,想想就头大,查找荣耀8为什么搜索不到蓝牙设备的资料,也没发现有效的办法,这时,用荣耀手机的人说,另一个公司的项目app(简称B吧,荣耀8搜索不到蓝牙设备的项目简称A吧)就能正常搜索到蓝牙设备,一打开调试还真是每次都能搜索到,看了一下两个项目的代码:
经过2个项目对比,发现就蓝牙搜索多久停止不一样,荣耀8能搜索到蓝牙设备的时间项目代码设置是10秒,而搜索不到的项目是设置8秒,带着半信半疑把项目A的蓝牙搜索时间由8秒改成10秒,运行,奇迹还真是出现了,荣耀8可以正常搜索到蓝牙设备了,反复测了好多次都能正常搜索到蓝牙设备,我是不信邪的人,还改成8秒试试,果然还是搜索不到。
于是和领导说,你看荣耀8必须10秒才能搜索 到蓝牙设备,搜索到的蓝牙设备列表不晚点展示不行啊,领导自然 是不同意,拿出第三方蓝牙提哦啊是工具说,那第三方就能很快搜索到,你想想其他法吧,想着有的手机快,有的手机慢,提出意见,能不能搜索到一个设备就展示到列表,不必等10秒后再展示,这样体验好些,反正领导说不行,既然领导说不行,那就回去研究吧。
查了很多蓝牙搜索慢怎么办?看了半天,当然这期间调试过各种其他方法,都没用,功夫不负有心人,其中一个博客说:
btAdapt.startDiscovery();搜索返回的结果慢, 现在都用这个 btAdapt.startLeScan(getmLeScanCallback); 其他也没多说什么,喜出望外,这不是正是我要的吗,于是改蓝牙搜索,上代码:
测试没有其他问题,打包发布,然而客户不给定位权限,问公司怎么一点搜索不出来,告知需要允许定位,才能搜索到,客户觉得一个蓝牙搜索你跟我要定位,很是不理解,老板也认为这样的体验也不好,客户不给权限,就无法使用,非常不友好。告知领导6.0以上必须给定位权限,否则搜不到, 这个没法啊,领导说你看第三方的蓝牙调试工具,在6.0以上的手机,不用打开定位,而且搜索的更快,第三方都能做到,你想办法吧, 我当然不苟同,下载了几个第三方蓝牙调试工具,有的确实要定位权限不允许定位就无法搜索到,有的确实禁止定位权限也能快速搜索到蓝牙设备,这么啪啪打脸,只能埋头去想办法了,同时,为了保证程序兼容性,公司又买了几部6.0以上的安卓手机,让我更惊心的是,魅族6和华为畅享7不仅要允许定位权限,GPS定位还要打开,GPS不打开,也是搜索不到蓝牙设备,想起以前开发的项目,由于安卓碎片化,不同的手机厂商对定位,GPS管理不一样,有的打开定位权限GPS权限也给了,有的打开GPS定位权限也给了,有的是他们两个独立,处理起来很麻烦,而蓝牙有的要GPS,有的不要GPS,6.0一下还不需要任何定位权限,想着这处理起来太麻烦,还是安心去找不要定位权限的方法吧,其实当时心里是没底的,虽然第三方已经实现这种不需要定位权限就能搜索到,心想,第三方也不知用的是什么黑科技,于是就查找android系统6.0一上的手机不需要定位权限就能把蓝牙设备搜索出来等相关资料,其中最有帮助的查到 btAdapt.startLeScan(getmLeScanCallback) 这个方法已过时,google已经在api里将其划掉,不推荐使用,推荐使用:
第一天毫无结果,灰头土脸,昏天暗地,焦头烂额地回家。
第二天继续查找相关资料,在一个偶然的机会,看到一篇博客说,把app中的build.gradle中的:
至于为什么会这样,可以查找 android compileSdkVersion、buildToolsVersion,targetSdkVersion之间的区别,网上有很多解释,保证能解答你的疑惑,下面附上一些简单的解释
targetSDKVersion
简单来说就代表着你的App能够适配的系统版本,意味着你的App在这个版本的手机上做了充分的 前向 兼容性处理和实际测试。其实我们写代码时都是经常干这么一件事,就是 if(Build.VERSION.SDK_INT >= 23) { ... } ,这就是兼容性处理最典型的一个例子。如果你的target设置得越高,其实调用系统提供的API时,所得到的处理也是不一样的,甚至有些新的API是只有新的系统才有的Android6.0普通权限normal permission 和 危险权限dangerous permission 。
Normal Permission:写在xml文件里,那么App安装时就会默认获得这些权限,即使是在Android6.0系统的手机上,用户也无法在安装后动态取消这些normal权限,这和以前的权限系统是一样的,不变。
Dangerous Permission:还是得写在xml文件里,但是App安装时具体如果执行授权分以下几种情况:
1、targetSDKVersion < 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
2、targetSDKVersion < 23 & API(手机系统) >= 6.0 :安装时默认获得权限,但是用户可以在安装App完成后动态取消授权( 取消时手机会弹出提醒,告诉用户这个是为旧版手机打造的应用,让用户谨慎操作 )。
3、targetSDKVersion >= 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
4、targetSDKVersion >= 23 & API(手机系统) >= 6.0 :安装时不会获得权限,可以在运行时向用户申请权限。用户授权以后仍然可以在设置界面中取消授权,用户主动在设置界面取消后,在app运行过程中可能会出现crash。
其他:
1.虽然解决了大问题,但是目前项目中发现,连续扫描-断开-扫描四五次,就无法再搜索道外围蓝牙设备,尤其是在每次搜索1-2秒内扫描断开,4、5次无论如何也是搜索不到,需要重新进入界面才能扫描到,每次扫描花费在4、5秒以上的,出现4、5次扫不到的几率就很低了,当然,4、5秒是老板和客户无法忍受的。
2.蓝牙连接成功自动断开;
正这样想,同事拿来一部荣耀8,装的APP怎么就是搜不到蓝牙设备,然后调试其他手机,都能搜索到,于是就纳闷了,荣耀8怎么就是搜索不到蓝牙设备呢,其他手机都能,再看一遍代码,也没有专门针对荣耀8处理的地方,再想,荣耀8是系统7.0的手机,是不是跟权限没开有关呢,对比其他华为手机,申请的蓝牙权限也都开着,荣耀8就是搜索不到,是不是荣耀8手机有问题,同事告知下载一个第三方蓝牙调试工具查查看,令人疑惑的是荣耀8用第三方蓝牙调试工具立刻就能扫描显示出来,试了几次都是立刻扫描出来,当时心想,我靠,什么鬼,这么迅速就能把蓝牙设备扫描显示出来,领导也说,别人的调试工具都能做这么快,你也得做这么快,想着目前的代码都是几秒后才能搜索到,目前的荣耀8还搜索不出来,客户那边还一个劲儿的给老板吐槽太慢,于是压力剧增,真正开始了蓝牙开发不归之旅。
但是再考虑索慢时,领导说,得把荣耀8搜索不到的问题先解决,因为荣耀8是项目实施人员给客户安装硬件调试数据要用的,那就先把搜索慢暂时放一放,想着一边搜索慢没还没着落,荣耀8手机就是搜索不到,想想就头大,查找荣耀8为什么搜索不到蓝牙设备的资料,也没发现有效的办法,这时,用荣耀手机的人说,另一个公司的项目app(简称B吧,荣耀8搜索不到蓝牙设备的项目简称A吧)就能正常搜索到蓝牙设备,一打开调试还真是每次都能搜索到,看了一下两个项目的代码:
private BluetoothAdapter btAdapt; private List<BluetoothDevice> listDevices = new ArrayList<BluetoothDevice>(); private List<TieBean> listDeviceName = new ArrayList<TieBean>(); private boolean bIsConnected; private BluetoothDevice myBluetooth; private public BluetoothGatt mBluetoothGatt; private TextView tv_seacher_ble; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏 setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//锁屏 tv_seacher_ble = (LinearLayout) findViewById(R.id.tv_seacher_ble);//linear Layout BluetoothAdapter btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能 IntentFilter intent = new IntentFilter(); intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果 intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); mActivity.registerReceiver(searchDevicesBroadcast, intent); tv_seacher_ble.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { btAdapt.startDiscovery(); } }); } //搜索到的蓝牙设备在这里 private BroadcastReceiver searchDevicesBroadcast = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (TextUtils.isEmpty(device.getName())) { return; } ToastUtil.showToast(mActivity, "蓝牙设备 " + device.getName()); String str = device.getName() + "|" + device.getAddress(); if (listDeviceName.indexOf(str) == -1) {// 防止重复添加 listDevices.add(device); // 获取设备名称和mac地址 listDeviceName.add(new TieBean(str)); } } }; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { //断开设备 if (msg.what == 1) { mBluetoothGatt.disconnect(); //连接设备 } else if (msg.what == 2) { ToastUtil.showToast(mActivity, "开始搜索蓝牙设备"); if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果蓝牙还没开启 ToastUtil.showToast(mActivity, "请先打开蓝牙"); return; } if (btAdapt.isDiscovering()) btAdapt.cancelDiscovery(); listDevices.clear(); listDeviceName.clear(); btAdapt.startDiscovery(); //定时器操作 new Handler().postDelayed(new Runnable() { @Override public void run() { //ToastUtil.showToast(mActivity,"搜索蓝牙完毕"); btAdapt.cancelDiscovery(); closeProgressDialog(); //其中BuildBean,TieAdapter是在app的build.gradle中dependencies {compile 'com.dou361.dialogui:jjdxm-dialogui:1.0.3'}; TieAdapter adapter = new TieAdapter(mContext, listDeviceName, true); BuildBean buildBean = DialogUIUtils.showMdBottomSheet(mActivity, true, "", listDeviceName, 0, new DialogUIItemListener() { @Override public void onItemClick(CharSequence text, int position) { myBluetooth = listDevices.get(position); //根据蓝牙地址获取远程蓝牙设备 final BluetoothDevice device = btAdapt.getRemoteDevice(myBluetooth.getAddress()); if (device == null) { return; } mBluetoothGatt = device.connectGatt(mActivity, false, mGattCallback); } }); buildBean.mAdapter = adapter; buildBean.show(); } }, 8000); //延时8s执行 } else if (msg.what == 3) { tv_seacher_ble.setText("断开蓝牙"); } else if (msg.what == 4) { tv_seacher_ble.setText("连接蓝牙"); mBluetoothGatt.close(); } super.handleMessage(msg); } };两个项目中的蓝牙适配器的获取,去发现,动态注册广播、广播接收蓝牙搜索结果、蓝牙连接都一样,至于手机是否支持ble4.0,蓝牙是否开启的逻辑,连接上对数据的处理、断开mBluetoothGatt.disconnect(),关闭mBluetoothGatt.close() 的逻辑网上有很多,我这里就不在详说;
经过2个项目对比,发现就蓝牙搜索多久停止不一样,荣耀8能搜索到蓝牙设备的时间项目代码设置是10秒,而搜索不到的项目是设置8秒,带着半信半疑把项目A的蓝牙搜索时间由8秒改成10秒,运行,奇迹还真是出现了,荣耀8可以正常搜索到蓝牙设备了,反复测了好多次都能正常搜索到蓝牙设备,我是不信邪的人,还改成8秒试试,果然还是搜索不到。
于是和领导说,你看荣耀8必须10秒才能搜索 到蓝牙设备,搜索到的蓝牙设备列表不晚点展示不行啊,领导自然 是不同意,拿出第三方蓝牙提哦啊是工具说,那第三方就能很快搜索到,你想想其他法吧,想着有的手机快,有的手机慢,提出意见,能不能搜索到一个设备就展示到列表,不必等10秒后再展示,这样体验好些,反正领导说不行,既然领导说不行,那就回去研究吧。
查了很多蓝牙搜索慢怎么办?看了半天,当然这期间调试过各种其他方法,都没用,功夫不负有心人,其中一个博客说:
btAdapt.startDiscovery();搜索返回的结果慢, 现在都用这个 btAdapt.startLeScan(getmLeScanCallback); 其他也没多说什么,喜出望外,这不是正是我要的吗,于是改蓝牙搜索,上代码:
package com.hand.hand16.activity; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import com.dou361.dialogui.DialogUIUtils; import com.dou361.dialogui.adapter.TieAdapter; import com.dou361.dialogui.bean.BuildBean; import com.dou361.dialogui.bean.TieBean; import com.dou361.dialogui.listener.DialogUIItemListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private TextView tv_seacher_ble; public boolean bIsConnected; private TieAdapter tieAdapter; private Vibrator vibrator; private BluetoothDevice mBluetoothDevice; private BluetoothAdapter btAdapt; List<BluetoothDevice> listDevices = new ArrayList<BluetoothDevice>(); List<TieBean> listDeviceName = new ArrayList<TieBean>(); private BluetoothDevice myBluetooth; public BluetoothGatt mBluetoothGatt;//*使用和处理数据 public BluetoothGattCharacteristic mNotifyCharacteristic; private boolean mScanning; //蓝牙服务 private String strDevice = ""; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 1) { //断开设备 mBluetoothGatt.disconnect(); } else if (msg.what == 2) { //连接设备 if (btAdapt == null || !btAdapt.isEnabled() || btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果蓝牙还没开启 Toast.makeText(MainActivity.this,"请先打开蓝牙",Toast.LENGTH_SHORT).show(); Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 0); return; } if (mScanning) { btAdapt.stopLeScan(getmLeScanCallback); } listDevices.clear(); listDeviceName.clear(); mScanning = true; btAdapt.startLeScan(getmLeScanCallback); //开始搜索 //定时器操作 new Handler().postDelayed(new Runnable() { @Override public void run() { mScanning = false; btAdapt.stopLeScan(getmLeScanCallback); //其中BuildBean,TieAdapter是在build.gradle中dependencies {compile 'com.dou361.dialogui:jjdxm-dialogui:1.0.3'}; tieAdapter = new TieAdapter(mContext, listDeviceName, true); BuildBean buildBean = DialogUIUtils.showMdBottomSheet(mActivity, true, "", listDeviceName, 0, new DialogUIItemListener() { @Override public void onItemClick(CharSequence text, int position) { mBluetoothDevice = btAdapt.getRemoteDevice(listDevices.get(position).getAddress()); if (mBluetoothDevice == null) { return; } mBluetoothGatt = mBluetoothDevice.connectGatt(mActivity, false, mGattCallback); } }); if (listDevices.size() > 0) { buildBean.mAdapter = tieAdapter; buildBean.show(); } else { Toast.makeText(MainActivity.this,"未搜索到蓝牙设备",Toast.LENGTH_SHORT).show(); } } }, 2000); //延时2s执行 } else if (msg.what == 3) { tv_seacher_ble.setText("断开蓝牙"); bIsConnected = true; Toast.makeText(MainActivity.this,"蓝牙连接成功",Toast.LENGTH_SHORT).show(); } else if (msg.what == 4) { tv_seacher_ble.setText("连接蓝牙"); bIsConnected = false; mBluetoothGatt.close(); mBluetoothGatt = null; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); supportRequestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏 setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//锁屏 tv_seacher_ble = (TextView) findViewById(R.id.tv_main_id); final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); btAdapt = bluetoothManager.getAdapter(); // btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能,除了搜索方法欢了,获取蓝牙适配器的方法也换了 //添加了震动 vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); tv_seacher_ble.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final AlertDialog.Builder myDialog = new AlertDialog.Builder(MainActivity.this); //nomalDialog.setIcon(R.drawable.icon_dialog); myDialog.setTitle("提示"); myDialog.setMessage("是否断开蓝牙设备?"); myDialog.setPositiveButton("是", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { Message message = new Message(); message.what = 1; handler.sendMessage(message); } }); myDialog.setNegativeButton("否", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }); myDialog.show(); } else { vibrator.vibrate(200);//震动指定时间 //蓝牙搜索 Message message = new Message(); message.what = 2; handler.sendMessage(message); } } }); BluetoothAdapter.LeScanCallback getmLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bytes) { runOnUiThread(new Runnable() { @Override public void run() { boolean isSame = true; if (bluetoothDevice != null && bluetoothDevice.getName() != null) { LogUtil.e("蓝牙设备==" + bluetoothDevice.getName() + "|" + bluetoothDevice.getAddress()); strDevice = bluetoothDevice.getName() + "|" + bluetoothDevice.getAddress(); //去重 for (BluetoothDevice bd : listDevices) { if (bd.getAddress().equalsIgnoreCase(bluetoothDevice.getAddress())) { isSame = false; } } if (isSame) { listDevices.add(bluetoothDevice); listDeviceName.add(new TieBean(strDevice)); // tieAdapter.notifyDataSetChanged(); } } } }); } }; private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, final int status, final int newState) { LogUtil.e("StateChange,status=" + status + ",newState==" + newState + ",Services=" + mBluetoothGatt.discoverServices()); if (status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) {//2 LogUtil.e("去连接GATT server"); } else if (newState == BluetoothProfile.STATE_CONNECTING) { LogUtil.e("连接中"); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { LogUtil.e("已断开断开蓝牙Disconnected"); Message message = new Message(); message.what = 4; handler.sendMessage(message); } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { LogUtil.e("断开中"); } } else if (status == 133) { //防止出现133,搜索到列表连接不上 LogUtil.e("出现133"); Message message = new Message(); message.what = 5; handler.sendMessage(message); } else if (status == 8) { //自动断开 LogUtil.e("自动断开了"); Message message = new Message(); message.what = 6; handler.sendMessage(message); } else if (status == 40) { //距离太远 Message message = new Message(); message.what = 7; handler.sendMessage(message); }else { //其他连接不到设备的情况 }Message message = new Message(); message.what = 8; handler.sendMessage(message); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { LogUtil.e("ServicesDiscovered状态status==" + status + ",Services==" + gatt.getServices()); if (status == BluetoothGatt.GATT_SUCCESS) { Message message = new Message(); message.what = 3; handler.sendMessage(message); //激活要开启的服务 displayGattServices(gatt.getServices()); LogUtil.e("Discovered获取GATT_SUCCESS"); } else { LogUtil.e("onServicesDiscovered received: " + status); bIsConnected = false; } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } }; //打开服务 private void displayGattServices(List<BluetoothGattService> gattServices) { if (gattServices == null) return; String uuid = null; for (BluetoothGattService gattService : gattServices) { uuid = gattService.getUuid().toString(); List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics(); for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { uuid = gattCharacteristic.getUuid().toString(); if (uuid.equals(SPP_UUID)) { final BluetoothGattCharacteristic characteristic = gattCharacteristic; final int charaProp = characteristic.getProperties();
//这里根据charaProp 去做读、写、通知,真正的连接成功是在这里做判断的
// Message message = new Message(); //message.what = 3; // handler.sendMessage(message) }}
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mScanning) {
btAdapt.stopLeScan(getmLeScanCallback);
}
if (NotNull.isNotNull(mBluetoothGatt)) {
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
}
android.os.Process.killProcess(android.os.Process.myPid());
}
}
测试没有其他问题,打包发布,然而客户不给定位权限,问公司怎么一点搜索不出来,告知需要允许定位,才能搜索到,客户觉得一个蓝牙搜索你跟我要定位,很是不理解,老板也认为这样的体验也不好,客户不给权限,就无法使用,非常不友好。告知领导6.0以上必须给定位权限,否则搜不到, 这个没法啊,领导说你看第三方的蓝牙调试工具,在6.0以上的手机,不用打开定位,而且搜索的更快,第三方都能做到,你想办法吧, 我当然不苟同,下载了几个第三方蓝牙调试工具,有的确实要定位权限不允许定位就无法搜索到,有的确实禁止定位权限也能快速搜索到蓝牙设备,这么啪啪打脸,只能埋头去想办法了,同时,为了保证程序兼容性,公司又买了几部6.0以上的安卓手机,让我更惊心的是,魅族6和华为畅享7不仅要允许定位权限,GPS定位还要打开,GPS不打开,也是搜索不到蓝牙设备,想起以前开发的项目,由于安卓碎片化,不同的手机厂商对定位,GPS管理不一样,有的打开定位权限GPS权限也给了,有的打开GPS定位权限也给了,有的是他们两个独立,处理起来很麻烦,而蓝牙有的要GPS,有的不要GPS,6.0一下还不需要任何定位权限,想着这处理起来太麻烦,还是安心去找不要定位权限的方法吧,其实当时心里是没底的,虽然第三方已经实现这种不需要定位权限就能搜索到,心想,第三方也不知用的是什么黑科技,于是就查找android系统6.0一上的手机不需要定位权限就能把蓝牙设备搜索出来等相关资料,其中最有帮助的查到 btAdapt.startLeScan(getmLeScanCallback) 这个方法已过时,google已经在api里将其划掉,不推荐使用,推荐使用:
BluetoothLeScanner scanner = btAdapt.getBluetoothLeScanner(); scanner.startScan(scanCallback);喜出过望,难道是用的方法过时了,所以需要定位权限,上代码:
ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); final BluetoothDevice bluetoothDevice = result.getDevice(); runOnUiThread(new Runnable() { @Override public void run() { boolean isSame = true; if (bluetoothDevice != null && bluetoothDevice.getName() != null) { LogUtil.e("蓝牙设备==" + bluetoothDevice.getName() + bluetoothDevice.getAddress()); strDevice =bluetoothDevice.getName() + "|" + bluetoothDevice.getAddress(); for (BluetoothDevice bd : listDevices) { if (bd.getAddress().equalsIgnoreCase(bluetoothDevice.getAddress())) { isSame = false; } } if (isSame) { listDevices.add(bluetoothDevice); listDeviceName.add(new TieBean(strDevice)); if (bleDeviceAdapter != null) { bleDeviceAdapter.notifyDataSetChanged(); } } } } }); } @Override public void onBatchScanResults(final List<ScanResult> results) { super.onBatchScanResults(results); runOnUiThread(new Runnable() { @Override public void run() { LogUtil.d("扫描onBatchScanResults==" + results.size()); } }); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); LogUtil.e("扫描失败onScanFailed, errorCode==" + errorCode); } };停止扫描的方法是:
scanner.stopScan(scanCallback);然而,虽然使用了新方法,速度也是一样快,但是还是需要定位权限,魅族6和华为畅享7还得打开GPS,看来这个也不是正解,当然项目中的扫描外围蓝牙设备的方法也都换成了scanner.startScan(scanCallback);
第一天毫无结果,灰头土脸,昏天暗地,焦头烂额地回家。
第二天继续查找相关资料,在一个偶然的机会,看到一篇博客说,把app中的build.gradle中的:
defaultConfig { applicationId "com.demo.ble" minSdkVersion 18 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" }中的 targetSdkVersion 25改成 targetSdkVersion 22,就不需要定位权限也可以搜索道蓝牙设备了,看到网上很少有人这样说的,搜了那么多资料,就一篇这样说的,带着半信半疑将targetSdkVersion改成 22, 编译,也没报任何错,运行,还真是又出奇迹,测试了手上的所有手机,都不需要定位,不要GPS就可以搜索到蓝牙设备。
至于为什么会这样,可以查找 android compileSdkVersion、buildToolsVersion,targetSdkVersion之间的区别,网上有很多解释,保证能解答你的疑惑,下面附上一些简单的解释
targetSDKVersion
简单来说就代表着你的App能够适配的系统版本,意味着你的App在这个版本的手机上做了充分的 前向 兼容性处理和实际测试。其实我们写代码时都是经常干这么一件事,就是 if(Build.VERSION.SDK_INT >= 23) { ... } ,这就是兼容性处理最典型的一个例子。如果你的target设置得越高,其实调用系统提供的API时,所得到的处理也是不一样的,甚至有些新的API是只有新的系统才有的Android6.0普通权限normal permission 和 危险权限dangerous permission 。
Normal Permission:写在xml文件里,那么App安装时就会默认获得这些权限,即使是在Android6.0系统的手机上,用户也无法在安装后动态取消这些normal权限,这和以前的权限系统是一样的,不变。
Dangerous Permission:还是得写在xml文件里,但是App安装时具体如果执行授权分以下几种情况:
1、targetSDKVersion < 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
2、targetSDKVersion < 23 & API(手机系统) >= 6.0 :安装时默认获得权限,但是用户可以在安装App完成后动态取消授权( 取消时手机会弹出提醒,告诉用户这个是为旧版手机打造的应用,让用户谨慎操作 )。
3、targetSDKVersion >= 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
4、targetSDKVersion >= 23 & API(手机系统) >= 6.0 :安装时不会获得权限,可以在运行时向用户申请权限。用户授权以后仍然可以在设置界面中取消授权,用户主动在设置界面取消后,在app运行过程中可能会出现crash。
其他:
1.虽然解决了大问题,但是目前项目中发现,连续扫描-断开-扫描四五次,就无法再搜索道外围蓝牙设备,尤其是在每次搜索1-2秒内扫描断开,4、5次无论如何也是搜索不到,需要重新进入界面才能扫描到,每次扫描花费在4、5秒以上的,出现4、5次扫不到的几率就很低了,当然,4、5秒是老板和客户无法忍受的。
2.蓝牙连接成功自动断开;