Android设备的蓝牙通信

设置蓝牙

我们都知道,在手机的设置-蓝牙中,可以进行蓝牙设置的相关操作。

其实可以不离开自己的APP,直接完成蓝牙设置的主要操作,可以结合自己的业务需求,相应地提示用户开启相关设置,提升用户体验。

首先要知道,蓝牙连接需要知道待连接设备的MAC地址。

已配对设备的MAC地址是已知的,只要对方开启了蓝牙并在连接范围内,就能连接成功。

未配对设备则需要通过搜索才能知道MAC地址,知道MAC地址后如果直接请求和对方建立连接 ,则系统会提示先进行配对,不需要我们再对此做处理。

下面开始操作蓝牙,先添加蓝牙权限:

<manifest ... >
  <uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

如果希望应用启动设备发现或操作蓝牙设置,则还必须声明 BLUETOOTH_ADMIN 权限。

再获取蓝牙适配器:

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

如果设备没有蓝牙,mBluetoothAdapter将为null

开启蓝牙
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

这将显示对话框,请求用户允许启用蓝牙。

onActivityResult() 回调中将收到请求结果

注意:启用可检测性将会自动启用蓝牙。

获取已配对设备
mBluetoothAdapter.getBondedDevices();

获取设备后就可以连接设备了。

搜索蓝牙

搜索设备对于蓝牙适配器而言是一个非常繁重的操作过程,并且会消耗大量资源。

因此要及时使用 cancelDiscovery() 停止发现。

if (mAdapter.isDiscovering()) {
            mAdapter.cancelDiscovery();
        }
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        if (mReceiver == null) {
            mReceiver = 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);
                        //根据需求进行相应操作
                    } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                        //根据需求进行相应操作
                    }
                }
            };
        }
        mActivity.registerReceiver(mReceiver, filter);
        mAdapter.startDiscovery();

这里app将会在发现设备和搜索结束时收到广播,可以实现自己的业务需求。

开启可检测性

如果尚未在设备上启用蓝牙,则启用设备可检测性将会自动启用蓝牙。

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

以上代码将显示对话框,请求用户允许将设备设为可检测到,时间持续 300 秒。

通过EXTRA_DISCOVERABLE_DURATION来定义不同的持续时间。 应用可以设置的最大持续时间为 3600 秒,值为 0 则表示设备始终可检测到。 任何小于 0 或大于 3600 的值都会自动设为 120 秒。

蓝牙通信

原理
服务端

一台设备保持开放的 BluetoothServerSocket 来充当服务器,侦听传入的连接请求,并在接受一个请求后提供已连接的 BluetoothSocket(与 TCP Socket 相似)。 从 BluetoothServerSocket 获取 BluetoothSocket 后,可以(并且应该)舍弃 BluetoothServerSocket,除非需要接受更多连接。

BluetoothServerSocket mServerSocket= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

这将获取一个BluetoothServerSocket

BluetoothSocket socket = mServerSocket.accept();

调用 accept() 开始侦听连接请求,成功后返回一个BluetoothSocket

这是一个阻塞调用。它将在连接被接受或发生异常时返回。

客户端

另一台设备作为客户端,获取远程设备的 BluetoothDevice 对象后,与服务端建立连接:

BluetoothSocket mSocket = device.createRfcommSocketToServiceRecord(MY_UUID);

这将获取BluetoothSocket这里的UUID必须和远端设备一样,后续的连接才能成功。

通用唯一标识符 (UUID) 是用于唯一标识信息的字符串 ID 的 128 位标准化格式。

可以使用网络上的众多随机 UUID 生成器之一,然后使用 fromString(String) 初始化一个 UUID。

接着调用

mSocket.connect();

该方法将会阻塞,直至成功或抛出异常。

如果成功,则两台设备的蓝牙蓝牙连接已经建立,每台设备都可以从BluetoothSocket获取输入输出流,进行数据传输。

蓝牙聊天app浅析

这个app是Google的一个官方sample,地址点这里

在两台设备上都装了这个app后,就可以通过蓝牙进行聊天了。

因为是一个app,所以这个app既包含服务端代码,也包含客户端代码。

连接状态
// Constants that indicate the current connection state
    public static final int STATE_NONE = 0;       // we're doing nothing
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device

这是sample中的4个连接状态,分别表示空状态、监听状态、连接ing状态、已连接状态。

刚进入app为空状态,如果蓝牙开启则开启服务端并进入监听状态,客户端进行连接时进入连接ing状态,连接成功则进入已连接状态。

退出app时设为空状态,关闭socket,清空所有工作线程(下面会讲到的3个线程),此时会造成读取异常,但不应再连接蓝牙设备。

侦听线程

线程名AcceptThread,进入app后就开启该线程,用来侦听传入的连接请求,线程阻塞直至异常或接入成功。

侦听异常则重启服务端。

接入成功则开启“管理连接线程”,同时设置连接状态为已连接,此时可以(并且应该)舍弃 BluetoothServerSocket,除非需要接受更多连接。

连接线程

线程名ConnectThread,客户端开启该线程来连接服务端。

连接失败则重启服务端。

连接成功也是开启“管理连接线程”,同时设置状态为已连接。

管理连接线程

线程名ConnectedThread,这是所有流式传输读取和写入操作的专门线程。

因为 read(byte[]) 和 write(byte[]) 方法都是阻塞调用。

read(byte[]) 将会阻塞,直至从流式传输中读取内容。

write(byte[]) 通常不会阻塞,但如果远程设备没有足够快地调用 read(byte[]),并且中间缓冲区已满,则其可能会保持阻塞状态以实现流量控制。

因此,线程中的主无限循环专门用于读取 InputStream,如果读取异常,说明蓝牙中断,此时可以提示用户,并进行重连(app退出应关闭socket,也会造成读取异常,此时不应重连)。

可使用线程中单独的公共方法来发起对 OutputStream 的写入操作。

序列化和反序列化

蓝牙传输中,输入输出流传输的是字节,字节和String的转换容易,但有时候不能很方便地获取数据细节。

如果发送方把对象转换为字节传送,接收方再把字节转换为对象,即实现序列化和反序列化,就很方便了。

下面介绍如何实现这样的需求:

ObjectOutputStream

首先,收发双方定义实体类A,实现Serializable接口。

注意:实体类A在收发双方的路径必须相同,否则ObjectOutputStream会转换 失败!

接着双方就可以在输出输入流进行转换,示例代码如下:

A a = new A();
发送方
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
     ObjectOutputStream oos = new ObjectOutputStream(baos);
     oos.writeObject(a);
} catch (IOException e) {
     e.printStackTrace();
}
mOutStream.write(baos.toByteArray());
接收方
 mInStream.read(buffer);
 ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
 A a = null;
 try {
      ObjectInputStream ois = new ObjectInputStream(bais);
      try {
           a = (BluetoothPos) ois.readObject();
      } catch (ClassNotFoundException e) {
           e.printStackTrace();
      }
 } catch (IOException e) {
     e.printStackTrace();
 }
Json

类似于后台与客户端的网络通信,来实现序列化和反序列化。

优点:没有“实体类A在收发双方的路径必须相同”这一限制,更加灵活方便!

发送方

通过Gson将实体对象转换为json字符串,再以UTF-8编码得到字节矩阵,最后流式输出。

String json = new Gson().toJson(a);
try {
    mOutStream.write(json.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
接收方
 mInStream.read(buffer);
 String json = "";
 try {
      json = new String(buffer, "UTF-8").trim();
 } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
 }
 JsonReader reader = new JsonReader(new StringReader(json));
 reader.setLenient(true);
 A a= new Gson().fromJson(reader, A.class);

注意:这里的json字符串必须调用trim()方法去除空格并调用JsonReader处理,不然反序列化时会报异常MalformedJsonException

参考资料:https://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值