最近项目需求要做蓝牙自动配对,也就是在首次配对的时候跳过用户输入PIN码。网上有很多分享的如何实现自动配对。
以下做一个记录,方便以后查阅。
注意:
蓝牙开启需要开启蓝牙的相关权限还有 定位 权限
在AndroidManifest.xml中添加
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
并且开启运行时权限;
private void initPermission() {
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
//判断是否需要向用户解释为何要此权限
if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.READ_CONTACTS)) {
showMessageOKCancel("你必须允许这个权限,否则无法搜索到BLE设备", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},100);
}
});
return;
}
//请求权限
requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS},100);
}
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 100) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0,PERMISSION_DENIED = -1
//这里进行授权被允许的处理
//可以弹个Toast,感谢用户允许了。
scanLeDevice(true);
Toast.makeText(MainActivity.this, "谢谢!", Toast.LENGTH_SHORT).show();
} else {
//这里进行权限被拒绝的处理,就跳转到本应用的程序管理器
Toast.makeText(MainActivity.this, "请开启位置权限", Toast.LENGTH_SHORT).show();
Intent i = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
String pkg = "com.android.settings";
String cls = "com.android.settings.applications.InstalledAppDetails";
i.setComponent(new ComponentName(pkg, cls));
i.setData(Uri.parse("package:" + getPackageName()));
startActivity(i);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
A、获取蓝牙,有两种方式:
/**
- 两种获取BluetoothAdapter方式
**/
//方式一:通过BluetoothManager获取
BluetoothManager mBm = (BluetoothManager)this.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBm.getAdapter();
//方式二:通过getDefaultAdapter()获取
//mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter ==null) {
Log.e("没有蓝牙模块");
ToastUtils.showShort("没有蓝牙模块");
return;
}
B、打开蓝牙,有两种方式:
/**
- 打开蓝牙的两种方式
*/
//一:直接打开,不通过用户
// if (!mBluetoothAdapter.isEnabled()) {
// mBluetoothAdapter.enable();
// Log.e("开启蓝牙~");
// }
//二:优雅的打开
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enabIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabIntent, 100);
LogUtils.e("开启蓝牙~");
}
第二种打开会调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100) {
Toast.makeText(this, "蓝牙已启用", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "蓝牙未启用", Toast.LENGTH_SHORT).show();
}
}
C、搜索设备
//15秒搜索时间
private Handler mHandler = new Handler();
//15秒搜索时间
private static final long SCAN_PERIOD = 15000;
private void scanLeDevice(final boolean enable) {
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
}
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里可以把搜索到的设备保存起来
//device.getName();获取蓝牙设备名字
//device.getAddress();获取蓝牙设备mac地址
//这里的rssi即信号强度,即手机与设备之间的信号强度。
list.add(device);
mBleAdapter.setNewData(list);
LogUtils.e("name:" + device.getName());
}
});
}
};
搜索到设备后我是用List装,然后用RecyclerView和BaseRecyclerViewAdapterHelper配合显示
D、连接设备
mBleAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
//ToastUtils.showShort("点击了:" + position);
//这里只需要createBond就行了
BluetoothDevice device = list.get(position);
try {
//创建createBond
ClsUtils.createBond(device.getClass(), device);
} catch (Exception e) {
e.printStackTrace();
}
//建立蓝牙连接
mBluetoothGatt = device.connectGatt(MainActivity.this, false, mGattCallback);
}
});
因为跳过用户输入需要用到反射,用到了ClsUtils这个工具类,感谢一下大牛们得无私分享。在连接的时候,创建createBond。
蓝牙连接是通过广播判断状态,所以需要自定义
public class BluetoothConnectActivityReceiver extends BroadcastReceiver {
String strPsw = "111111";
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
if (intent.getAction().equals("android.bluetooth.device.action.PAIRING_REQUEST")) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
try {
/**
- cancelPairingUserInput()取消用户输入密钥框,
- 个人觉得一般情况下不要和setPin(setPasskey、setPairingConfirmation、
- setRemoteOutOfBandData)一起用,
- 这几个方法都会remove掉map里面的key:value(<<<<<也就是互斥的>>>>>>)。
*/
ClsUtils.setPin(device.getClass(), device, strPsw); // 手机和蓝牙采集器配对
//ClsUtils.setPasskey(device.getClass(), device, strPsw);
ToastUtils.showShort("配对信息===>>>>成功了~");
abortBroadcast();//如果没有将广播终止,则会出现一个一闪而过的配对框。
} catch (Exception e) {
LogUtils.e("反射异常:"+e);
// TODO Auto-generated catch block
ToastUtils.showShort("请求连接错误");
}
}
}
}
}
以上基本就是这样了,表述不清楚待提高。最后附上Activity主要代码。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.list_view)
RecyclerView listView;
BleAdapter mBleAdapter;
@BindView(R.id.swipeRefreshLayout)
SwipeRefreshLayout swipeRefreshLayout;
private BluetoothAdapter mBluetoothAdapter;
List<BluetoothDevice> list;
private BluetoothGatt mBluetoothGatt;
private BluetoothGattCharacteristic mCharacteristic;
private BluetoothGattCharacteristic rssiCharacteristic;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// View注入
ButterKnife.bind(this);
initPermission();
initView();
init();
// 注册Receiver来获取蓝牙设备相关的结果
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);
registerReceiver(searchDevices,intent);
}
private void initView() {
list = new ArrayList<>();
mBleAdapter = new BleAdapter(R.layout.item_ble_view, list);
// 设置布局管理器
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
listView.setLayoutManager(linearLayoutManager);
listView.setAdapter(mBleAdapter);
mBleAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
//ToastUtils.showShort("点击了:" + position);
//这里只需要createBond就行了
BluetoothDevice device = list.get(position);
try {
//创建createBond
ClsUtils.createBond(device.getClass(), device);
} catch (Exception e) {
e.printStackTrace();
}
//建立蓝牙连接
mBluetoothGatt = device.connectGatt(MainActivity.this, false, mGattCallback);
}
});
//下拉刷新
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//停止刷新
swipeRefreshLayout.setRefreshing(false);
list.clear();
}
});
}
private void init() {
/**
* 两种获取BluetoothAdapter方式
/
//方式一:通过BluetoothManager获取
BluetoothManager mBm = (BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBm.getAdapter();
//方式二:通过getDefaultAdapter()获取
//mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
LogUtils.e("没有蓝牙模块");
ToastUtils.showShort("没有蓝牙模块");
return;
}
/
* 打开蓝牙的两种方式
*/
//一:直接打开,不通过用户
// if (!mBluetoothAdapter.isEnabled()) {
// mBluetoothAdapter.enable();
// LogUtils.e("开启蓝牙~");
// }
//二:优雅的打开
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enabIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabIntent, 100);
LogUtils.e("开启蓝牙~");
}
}
private boolean mScanning;//是否正在搜索
private Handler mHandler = new Handler();
//15秒搜索时间
private static final long SCAN_PERIOD = 15000;
private void scanLeDevice(final boolean enable) {
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
}
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//在这里可以把搜索到的设备保存起来
//device.getName();获取蓝牙设备名字
//device.getAddress();获取蓝牙设备mac地址
//这里的rssi即信号强度,即手机与设备之间的信号强度。
list.add(device);
mBleAdapter.setNewData(list);
LogUtils.e("name:" + device.getName());
}
});
}
};
@Override
protected void onResume() {
list.clear();
scanLeDevice(true);
super.onResume();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 100) {
Toast.makeText(this, "蓝牙已启用", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "蓝牙未启用", Toast.LENGTH_SHORT).show();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void initPermission() {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//判断是否需要向用户解释为何要此权限
if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.READ_CONTACTS)) {
showMessageOKCancel("你必须允许这个权限,否则无法搜索到BLE设备", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
100);
}
});
return;
}
//请求权限
requestPermissions(new String[]{Manifest.permission.WRITE_CONTACTS},
100);
}
}
/**
*显示权限申请框
*/
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 100) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1
//这里进行授权被允许的处理
//可以弹个Toast,感谢用户爸爸允许了。
scanLeDevice(true);
Toast.makeText(MainActivity.this, "谢谢!", Toast.LENGTH_SHORT).show();
} else {
//这里进行权限被拒绝的处理,就跳转到本应用的程序管理器
Toast.makeText(MainActivity.this, "请开启位置权限", Toast.LENGTH_SHORT).show();
Intent i = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
String pkg = "com.android.settings";
String cls = "com.android.settings.applications.InstalledAppDetails";
i.setComponent(new ComponentName(pkg, cls));
i.setData(Uri.parse("package:" + getPackageName()));
startActivity(i);
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
-
gatt连接结果的返回
*/
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothProfile.STATE_DISCONNECTED) { //蓝牙连接
LogUtils.e("onConnectionStateChange" + "连接成功");
ToastUtils.showShort("连接成功");} }
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
/**
- Callback triggered as a result of a remote characteristic notification.
- @param gatt
- @param characteristic
*/
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
System.out.println("onCharacteristicChanged");
LogUtils.e("onCharacteristicChanged");
}
/**
- 写入数据时操作
- @param gatt
- @param characteristic
- @param status
*/
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
LogUtils.e("onCharacteristicWrite");
}
/**
- 读取返回值时操作
- @param gatt
- @param characteristic
- @param status
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
LogUtils.e("onCharacteristicRead");
}
/**
Callback indicating the result of a descriptor write operation.
@param gatt
@param descriptor
-
@param status
*/
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
LogUtils.e("onDescriptorWrite");
}
};/**
- 搜索BroadcastReceiver
*/
private final BroadcastReceiver searchDevices = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle b = intent.getExtras();
Object[] lstName = b.keySet().toArray();
// 显示所有收到的消息及其细节
for (int i = 0; i < lstName.length; i++) {
String keyName = lstName.toString();
LogUtils.e(keyName, String.valueOf(b.get(keyName)));
}
BluetoothDevice device = null;
// 搜索设备时,取得设备的MAC地址
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
String str = " 未配对|" + device.getName() + "|"
+ device.getAddress();
LogUtils.e(str);
// if (lstDevices.indexOf(str) == -1)// 防止重复添加
// lstDevices.add(str); // 获取设备名称和mac地址
// adtDevices.notifyDataSetChanged();
}
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING:
LogUtils.e("BlueToothTestActivity", "正在配对......");
break;
case BluetoothDevice.BOND_BONDED:
LogUtils.e("BlueToothTestActivity", "完成配对");
//connect(device);//连接设备
break;
case BluetoothDevice.BOND_NONE:
LogUtils.e("BlueToothTestActivity", "取消配对");
default:
break;
}
}
}
};
- 搜索BroadcastReceiver
}
再贴上ClsUtils类代码,也可以直接搜ClsUtils,可以搜到。
public class ClsUtils {
static BluetoothDevice remoteDevice;
/**
* 与设备配对 参考源码:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
/
static public boolean createBond(Class btClass, BluetoothDevice btDevice)
throws Exception {
Method createBondMethod = btClass.getMethod("createBond");
Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
return returnValue.booleanValue();
}
/*
* 与设备解除配对 参考源码:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
/
static public boolean removeBond(Class btClass, BluetoothDevice btDevice)
throws Exception {
Method removeBondMethod = btClass.getMethod("removeBond");
Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);
return returnValue.booleanValue();
}
static public boolean setPin(Class btClass, BluetoothDevice btDevice,String str) throws Exception {
try {
Method removeBondMethod = btClass.getDeclaredMethod("setPin",new Class[]{byte[].class});
Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,
new Object[]
{str.getBytes()});
Log.e("returnValue", "" + returnValue);
} catch (SecurityException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (IllegalArgumentException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
static public boolean setPasskey (Class btClass, BluetoothDevice btDevice,String str) throws Exception {
try {
Method removeBondMethod = btClass.getDeclaredMethod("setPasskey", new Class[]{byte[].class});
Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice, new Object[]{str.getBytes()});
Log.e("returnValue", "" + returnValue);
} catch (SecurityException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (IllegalArgumentException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
// 取消用户输入
static public boolean cancelPairingUserInput(Class btClass,BluetoothDevice device)throws Exception {
Method createBondMethod = btClass.getMethod("cancelPairingUserInput");
// cancelBondProcess()
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
return returnValue.booleanValue();
}
// 取消配对
static public boolean cancelBondProcess(Class btClass, BluetoothDevice device)throws Exception {
Method createBondMethod = btClass.getMethod("cancelBondProcess");
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
return returnValue.booleanValue();
}
//确认配对
static public void setPairingConfirmation(Class<?> btClass, BluetoothDevice device, boolean isConfirm) throws Exception {
Method setPairingConfirmation = btClass.getDeclaredMethod("setPairingConfirmation", boolean.class);
setPairingConfirmation.invoke(device, isConfirm);
}
/*
* @param clsShow
*/
static public void printAllInform(Class clsShow) {
try {
// 取得所有方法
Method[] hideMethod = clsShow.getMethods();
int i = 0;
for (; i < hideMethod.length; i++) {
Log.e("method name", hideMethod[i].getName() + ";and the i is:"
+ i);
}
// 取得所有常量
Field[] allFields = clsShow.getFields();
for (i = 0; i < allFields.length; i++) {
Log.e("Field name", allFields[i].getName());
}
} catch (SecurityException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (IllegalArgumentException e) {
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 执行时直接使用:
public static boolean pair(String strAddr, String strPsw) {
boolean result = false;
BluetoothAdapter bluetoothAdapter = BluetoothAdapter .getDefaultAdapter();
bluetoothAdapter.cancelDiscovery();
if (!bluetoothAdapter.isEnabled()) {
bluetoothAdapter.enable();
}
if (!BluetoothAdapter.checkBluetoothAddress(strAddr)) { // 检查蓝牙地址是否有效
Log.d("mylog", "devAdd un effient!");
}
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(strAddr);
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
try {
Log.d("mylog", "NOT BOND_BONDED");
ClsUtils.setPin(device.getClass(), device, strPsw); // 手机和蓝牙采集器配对
ClsUtils.createBond(device.getClass(), device);
remoteDevice = device; // 配对完毕就把这个设备对象传给全局的remoteDevice
result = true;
} catch (Exception e) {
// TODO Auto-generated catch block
Log.d("mylog", "setPiN failed!");
e.printStackTrace();
} //
} else {
Log.d("mylog", "HAS BOND_BONDED");
try {
ClsUtils.createBond(device.getClass(), device);
ClsUtils.setPin(device.getClass(), device, strPsw); // 手机和蓝牙采集器配对
ClsUtils.createBond(device.getClass(), device);
remoteDevice = device; // 如果绑定成功,就直接把这个设备对象传给全局的remoteDevice
result = true;
} catch (Exception e) {
// TODO Auto-generated catch block
Log.d("mylog", "setPiN failed!");
e.printStackTrace();
}
}
return result;
}
}
以上在红米not4x 、 华为荣耀8 、 华为荣耀青春版(不知道具体啥型号)、小米5、vivoX20、华为p9、华为荣耀9、华为Mate 10、小米5x、红米3X、红米not5A测试OK。听说在三星上不行,到时候找个测试下。
如有错误,请留言提出。谢谢~