最近,正在做关于Android串口蓝牙遥控小车的APP,在此罗列出相关技术细节,用以备忘。
1.蓝牙权限的申请
在AndroidManifest.xml加入以下权限:
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
其中”android.permission.BLUETOOTH_PRIVILEGED” 用于蓝牙配对权限;
更改build.gradle(Module:app)中minSdkVersion项改为大于19;
2.检测用户设备是否支持蓝牙功能
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter==null){
AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this);
builder.setTitle("错误");
builder.setMessage("该设备不具备蓝牙功能,无法启动应用程序!");
builder.setCancelable(false);
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();
}
});builder.show();
}
其中 ActivityCollector.finishAll();为关闭应用程序;
3.询问用户蓝牙权限
当打开应用程序后,其应用程序会检测当前设备蓝牙功能是否启动。若没有开启,则会弹出窗口询问用户是否开启蓝牙功能;
private static final int REQUEST_ENABLE=1;
if(!mBluetoothAdapter.isEnabled()){
Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,REQUEST_ENABLE);
}
相关回调函数检测如下:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==REQUEST_ENABLE){
if(resultCode!=RESULT_OK){
ActivityCollector.finishAll();
}
}
}
检测用户是否开启蓝牙功能,若否则关闭应用程序;
4.蓝牙设备的搜索
蓝牙设备的搜索需要用到几个蓝牙设备的系统广播,如下所示:
BluetoothDevice.ACTION_FOUND //搜索到新设备
BluetoothAdapter.ACTION_DISCOVERY_FINISHED//搜索完成
注册的广播:
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
intentFilter.setPriority(Integer.MAX_VALUE);
registerReceiver(mBlueToothReceiver,intentFilter);
相关的广播接收器如下:
private final BroadcastReceiver mBlueToothReceiver=new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action))
{
BluetoothDevice
device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(device.getBondState()!=BluetoothDevice.BOND_BONDED){
//将新设备添加到列表中
mRecycleList.add(new
RecycleItems(device.getName(),device.getAddress()));
madapter.notifyDataSetChanged();
}
}
else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
{
if(isSearchDev)
{
isSearchDev=false;
msearchDevBtn.setText("搜索设备");
}
}
}
启动搜索蓝牙设备代码如下:
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
5.蓝牙设备的配对
蓝牙设备的配对需要用到的蓝牙设备的系统广播,如下所示:
BluetoothDevice.ACTION_PAIRING_REQUEST
自定义的广播如下:
com.SerialBlueTeeth.PairingDev
主要用于传递用户点击设备列表的位置信息;
将以上两个广播添加到蓝牙设备搜索的广播中,代码如下:
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
intentFilter.addAction("com.SerialBlueTeeth.PairingDev");
intentFilter.setPriority(Integer.MAX_VALUE);
registerReceiver(mBlueToothReceiver,intentFilter);
配对信息对话框代码如下:
madapter.setOnItemClickListener(new DevAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, final int position) {
if(position>mPairingDevCounter) {
final AlertDialog.Builder builder = new AlertDialog.Builder(searchDev.this);
builder.setTitle("提示");
mEditText=new EditText(builder.getContext());
builder.setView(mEditText);
builder.setMessage("请输入设备的PIN码或配对密码(注:配对密码通常为0000或1234)");
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent=new Intent("com.SerialBlueTeeth.PairingDev");
intent.putExtra("position",position);
sendBroadcast(intent);
}
});
builder.show();
}
}
});
更改后的广播接收器代码如下:
private final BroadcastReceiver mBlueToothReceiver=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(device.getBondState()!=BluetoothDevice.BOND_BONDED){
mRecycleList.add(new RecycleItems(device.getName(),device.getAddress()));
madapter.notifyDataSetChanged();
}
}else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
if(isSearchDev){
isSearchDev=false;
msearchDevBtn.setText("搜索设备");
}
}
else if("com.SerialBlueTeeth.PairingDev".equals(action))
{
int position=intent.getIntExtra("position",0);
if(position==0){
return;
}
mdevice=mBluetoothAdapter.getRemoteDevice(mRecycleList.get(position).getDevAddr());
if(mdevice.createBond()==false){
Toast.makeText(searchDev.this,"配对失败,请重试!",Toast.LENGTH_SHORT).show();
return;
}
}
else if(BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action))
{
mdevice.setPairingConfirmation(true);//确定配对
mdevice.setPin(mEditText.getText().toString().getBytes());//写入配对密码
}
}
};
6.蓝牙设备的连接与通信
特别说明,蓝牙设备的几个状态:
1.查找设备阶段;
2.蓝牙配对阶段;
3.设备连接阶段;
蓝牙配对成功不等于蓝牙设备已连接;
在配对阶段,经行主从机PIN码阶段,手机之间的配对会自动生成特定的UUID,但对于串口蓝牙设备则仅有一组特定的UUID{00001101-0000-1000-8000-00805F9B34FB};
关于蓝牙设备的连接和通信放置在服务中,数据的接受独自放在一个线程中,数据的发送则通过广播进行。具体代码如下:
public class SerialBlueToothService extends Service {
private static final UUID SerialBlueToothDev_UUID=UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private BluetoothAdapter mBluetoothAdapter=null;
private BluetoothSocket mBluetoothSocket=null;
private OutputStream mOutputStream=null;
private InputStream mInputStream=null;
private String DevAddr=null;
private boolean mFlag=true;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String BlueToothMsg=intent.getStringExtra("BLE_CMD");
writeSerialBlueTooth(BlueToothMsg);
}
};
@Override
public void onDestroy() {
super.onDestroy();
try{
mBluetoothSocket.close();
}catch (IOException e){
e.printStackTrace();
}
unregisterReceiver(mReceiver);
}
public SerialBlueToothService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
DevAddr=intent.getStringExtra("DevAddr");//通过建立Service,Intent传入蓝牙地址
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.example.SerialBLE.SEND_MSG");
intentFilter.addAction("com.example.SerialBLE.RECEIVE_MSG");
registerReceiver(mReceiver,intentFilter);
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
SerialBlueToothThread thread=new SerialBlueToothThread();
thread.start();
return super.onStartCommand(intent, flags, startId);
}
private int readSerialBlueTooth(){
int ret=0;
byte[] rsp=null;
if(!mFlag){
return -1;
}
try {
rsp=new byte[mInputStream.available()];
ret=mInputStream.read(rsp);
}catch (IOException e){
e.printStackTrace();
}
return ret;
}
private void writeSerialBlueTooth(String msg){
try{
mOutputStream.write(msg.getBytes());
mOutputStream.flush();
}catch (IOException e){
e.printStackTrace();
}
}
private class SerialBlueToothThread extends Thread{
@Override
public void run() {
super.run();
try{
BluetoothDevice device=mBluetoothAdapter.getRemoteDevice(DevAddr);
mBluetoothSocket=device.createRfcommSocketToServiceRecord(SerialBlueToothDev_UUID);
}catch (Exception e){
e.printStackTrace();
mFlag=false;
}
mBluetoothAdapter.cancelDiscovery();
try{
mBluetoothSocket.connect();
}catch (IOException e){
e.printStackTrace();
try{
mBluetoothSocket.close();
mFlag=false;
}catch (IOException e1){
e1.printStackTrace();
}
}
if(mFlag){
try {
mInputStream=mBluetoothSocket.getInputStream();
mOutputStream=mBluetoothSocket.getOutputStream();
}catch (IOException e){
e.printStackTrace();
}
}
while(true){
readSerialBlueTooth();
try {
Thread.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
7最后
由于作者知识有限,文章有所纰漏,还望指正。
注:关于蓝牙配对结果如何检测(是否有系统广播可以使用)求告知,谢谢!