Android BlueTooth蓝牙知识全解

蓝牙开发基本概念了解:

RFCOMM协议

  RFCOMM是一个简单传输协议,其目的是针对如何在两个不同设备上的应用之间保证一条完整的通信路径,并在它们之间保持一通信段。

  RFCOMM是为了兼容传统的串口应用,同时取代有线的通信方式,蓝牙协议栈需要提供与有线串口一致的通信接口而开发出的协议。RFCOMM协议提供对基于L2CAP协议的串口仿真,基于ETSI07.10。可支持在两个BT设备之间同时保持高达60路的通信连接。

MAC硬件地址

MAC(MediaAccess Control介质访问控制)MAC地址是烧录在网卡NIC里的.MAC地址也叫硬件地址是由48比特长(6字节)16进制的数字组成


蓝牙的建立过程是一个复杂的过程,即使有过相当一段工作和使用经验的人,如果不仔细去了解还是理解不全。

    平时我们用蓝牙耳机听音乐,和不同的设备共享文件,打电话等,都有一个配对--连接--传输数据的过程。

    配对,其实就是一个认证的过程。


为什么不配对便无法建立连接?

    任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用认证的方式进行数据交互。同时为了保证使用的方便性,以配对的形式完成两个蓝牙设备之间的首次通讯认证,经配对之后,随后的通讯连接就不必每次都要做确认。所以认证码的产生是从配对开始的,经过配对,设备之间以PIN码建立约定的link key用于产生初始认证码,以用于以后建立的连接。

    所以不配对,两个设备之间便无法建立认证关系,无法进行连接及其之后的操作,所以配对在一定程度上保证了蓝牙通信的安全,当然这个安全保证机制是比较容易被破解的,因为现在很多个人设备没有人机接口,所以PIN码都是固定的而且大都设置为通用的0000或者1234之类的,所以很容易被猜到并进而建立配对和连接。


蓝牙的连接过程

    现在的蓝牙芯片供应商提供的技术支持能力相当强大,有完整的硬件和软件解决方案。对于应用而言,提供了固件用于实现底层协议栈,提供了profile库及源代码规范了各种应用,开发人员只要专注于应用程序开发就可以了。对于蓝牙底层的一些东西往往不甚了了。以前我也是这样子的,最近在做一个自动搜索以实现自动连接的应用,发现还是需要了解一些底层的机制的。
    我们可以很容易的进行操作在一个手机和免提设备之间建立连接,那么这个连接是怎么建立起来的呢?
    首先,主设备(master,即发起连接的设备)会寻呼(page)从设备(slave,接收连接的设备),master会以跳频的方式去寻呼slave,slave会固定间隔地去扫描(scan)外部寻呼,即page scan,当scan 到外部page时便会响应response该page,这样两个设备之间便会建立link的连接,即ACL链路的连接。当ACL 链路连接建立后,主设备会发起channel的连接请求,即L2CAP的连接,建立L2CAP的连接之后,主设备采用SDP去查询从设备的免提服务,从中得到rfcomm的通道号,然后主设备会发起rfcomm的连接请求建立rfcomm的连接。然后就建立了应用的连接。即link establish->channel establish->rfcomm establish->connection



蓝牙设备之间的通信主要包括了以下几个步骤:

0.打开蓝牙开关

1.设置蓝牙设备可见性(主叫模式)

2.搜索蓝牙设备(比较常见的方式是用ListView列举出已搜索到的蓝牙设备)

3.绑定(配对)蓝牙设备

4.连接蓝牙设备

5.数据交互

6.断开蓝牙连接

7.断开蓝牙绑定(配对)

8.关闭蓝牙开关

PS:在这里可以大家思考下,就拿手机与智能终端(比如蓝牙血压计),谁是Client谁是Server角色?为什么呢?


下面列举出常见类:

BluetoothAdapter类:代表了一个本地的蓝牙适配器。它是所有蓝牙交互的的入口点。利用它你可以发现其他蓝牙设备,查询绑定了的设备,使用已知的MAC地址实例化一个蓝牙设备和建立一个BluetoothServerSocket(作为服务器端)来监听来自其他设备的连接。

BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态(其信息是封装在BluetoothSocket中)。

BluetoothSocket类:代表了一个蓝牙套接字的接口(类似于TCP中的套接字),它是应用程序通过输入、输出流与其他蓝牙设备通信的连接点。

BlueboothServerSocket类:代表打开服务连接来监听可能到来的连接请求(属于server端),为了连接两个蓝牙设备必须有一个设备作为服务器打开一个服务套接字。当远端设备发起连接连接请求的时候,并且已经连接到了的时候,BlueboothServerSocket类将会返回一个BluetoothSocket。

操作蓝牙的权限:

<span style="font-family:KaiTi_GB2312;"><uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permissionandroid:name="android.permission.BLUETOOTH" /></span>

关于蓝牙部分在源码中的位置:

Framework层:

packages/apps/Settings/src/com/android/settings/。这里是蓝牙UI的入口,基本上有关蓝牙UI上的处理都是在这个目录的bluetooth文件夹下实现的。frameworks/base/core/java/android/bluetooth。这个文件基本属于API的类型,向上层UI提供了所需要的API。

Jni层:frameworks/base/core/jni。(这里还没有深入去看过)

最好的入门示例:E:\android-sdks\samples\android-17\BluetoothChat

借用例子中的部分代码:

获取本地蓝牙适配器:如果返回null则表明该设备不支持蓝牙功能;

<span style="font-family:KaiTi_GB2312;">mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();</span>

在界面中请求用户打开蓝牙;

<span style="font-family:KaiTi_GB2312;">// If BT is not on, request that it be enabled.
        // setupChat() will then be called during onActivityResult
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
        }</span>
静默的后台打开蓝牙;

<span style="font-family:KaiTi_GB2312;">BluetoothAdapter.enable();</span>
搜索蓝牙设备(们):

<span style="font-family:KaiTi_GB2312;">    private void ensureDiscoverable() {
        if(D) Log.d(TAG, "ensure discoverable");
        if (mBluetoothAdapter.getScanMode() !=
            BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
            startActivity(discoverableIntent);
        }
    }</span>
静默的后台搜索:

<span style="font-family:KaiTi_GB2312;">BluetoothAdapter.startDiscover();</span>

查找蓝牙设备,此时注册一个广播接收器用于接收查找的结果

<span style="font-family:KaiTi_GB2312;">// Register for broadcasts when a device is discovered
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);

        // Register for broadcasts when discovery has finished
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);</span>
广播接收到的结果,这里仅注册了ACTION_FOUND与ACTION_DISCOVERY_FINISH。

实际上可以注册更多内容,详情可以进入BluetoothDevice类中查看。

<span style="font-family:KaiTi_GB2312;">// The BroadcastReceiver that listens for discovered devices and
    // changes the title when discovery is finished
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // If it's already paired, skip it, because it's been listed already
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }else{/**you can create bond here.*/}
            // When discovery is finished, change the Activity title
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                // TODO ...
                }
            }
        }
    };</span>
连接蓝牙设备并建立RFCCOM

<span style="font-family:KaiTi_GB2312;">// Get the device MAC address
        String address = data.getExtras()
            .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
        // Get the BluetoothDevice object
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);</span>

<span style="font-family:KaiTi_GB2312;">// Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                if (secure) {
                    tmp = device.createRfcommSocketToServiceRecord(
                            MY_UUID_SECURE);
                } else {
                    tmp = device.createInsecureRfcommSocketToServiceRecord(
                            MY_UUID_INSECURE);
                }
		mmSocket.connect();
            } catch (IOException e) {
                Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
            }</span>

需要注意的事项:

不要在Discovery过程中,尝试去配对、连接蓝牙设备。

Because discovery is a heavyweight procedure for the Bluetooth adapter, this method should always be called before attempting to connect to a remote device withandroid.bluetooth.BluetoothSocket.connect(). Discovery is not managed by the Activity, but is run as a system service, so an application should always call cancel discovery even if it did not directly request a discovery, just to be sure. 


所以适配蓝牙2.0,实质上是在适配 rfcomm。

createRfcommSocket 这个方法,指定连接的是 1号端口,
而服务端则还是用UUID来启动服务,至于其绑定了哪个端口,其实是不可知的。
最开始客户端用 createRfcommSocketToServiceRecord 这个方法,其实是利用UUID去找到
相应的服务,只不过不知道是不是我的UUID的问题,导致它找不到,所以报 service discovery failed这个错。
而直接用反射的方法去访问 1号端口,如果凑巧服务端也是在1号端口监听,那就没问题。如果不是,
并且没有服务在监听,就会报connection refused这个错。如果有别的服务在监听,然后又不理解
我的这个协议,就会报  File descriptor in bad state   这个错。(片段摘抄自:恶心的 Android 蓝牙







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值