Android Bluetooth

Android Bluetooth

使用Android蓝牙API来进行蓝牙通信的四个任务:

  • 设置蓝牙
  • 检索周围匹配的或者可用的设备
  • 连接设备
  • 设备间传输数据

所有蓝牙APIs在android.bluetooth 包中。

创建蓝牙连接所要用到的类和接口:
  • BluetoothAdapter

    表示本地蓝牙适配器(蓝牙无线电广播)。BluetoothAdapter是所有蓝牙交互的入口点。你能够通过它发现其它蓝牙设备,查询一系列已经匹配的设备,使用已知的MAC地址实例化一个 BluetoothDevice,创建 BluetoothServerSocket 监听来自其他设备的通信。

  • BluetoothDevice
    表示远程蓝牙设备。用这个类通过 BluetoothSocket能够请求同远程设备的链接,或者查询设备的名字、地址、类和绑定状态。

  • BluetoothSocket
    表示蓝牙套接字通信(类似于TCP Socket)的接口。这是允许应用通过InputStream和OutputStream与其他蓝牙设备进行数据交换的连接点。

  • BluetoothServerSocket
    表示用于监听即将到来的请求的对外公开套接字(类似于TCP ServerSocket)。为了连接两个安卓设备,一个设备必须用这个类开放一个服务端的套接字。当远程的蓝牙设备向该设备发起连接请求时,在连接建立的时候BluetoothServerSocket会返回一个BluetoothSocket。

蓝牙权限

为了在应用中使用蓝牙特性,必须声明蓝牙权限 BLUETOOTH。如果还需要操作蓝牙设置,必须声明 BLUETOOTH_ADMIN 权限。

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
设置蓝牙

在应用能够通过蓝牙通信之前,需要校验设备是否支持蓝牙,如果支持,要确保蓝牙是处于开启状态。如果不支持蓝牙,应当提示用户不支持。如果支持蓝牙但是没有打开,可以在不离开应用的情况下请求应用开启蓝牙。

1.获取 BluetoothAdapter

所有的蓝牙Activity都需要BluetoothAdapter 。为了得到BluetoothAdapter,调用静态方法getDefaultAdapter()·。这个方法会返回一个BluetoothAdapter,他表示设备自身的蓝牙适配器(蓝牙无线电广播)。如果 getDefaultAdapter()返回为null则表示设备不支持蓝牙。整个系统只存在一个蓝牙适配器,应用通过它来交互。

2.打开蓝牙

调用isEnabled()去检查蓝牙是否已经启用。如果方法返回为false,则蓝牙未被启用。要请求启用蓝牙,调用 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int))并且携带action intent参数 ACTION_REQUEST_ENABLE 。这会显示一个对话框请求用户权限来启用蓝牙,如果用户点击“yes”,则系统会开始启用蓝牙。

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    Toast.makeText(this, "蓝牙无法使用", Toast.LENGTH_LONG).show();
} else { // 蓝牙可以使用
    if (!mBluetoothAdapter.isEnabled()) {   // 蓝牙未开启则开启蓝牙
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    }else{
        // 蓝牙已打开
    }
} 

传输给 startActivityForResult()的常量REQUEST_ENABLE_BT是一个本地定义的整型(必须大于零),在onActivityResult()实现中,系统将这个整型作为requestCode参数返回。

如果蓝牙启动成功,在 onActivityResult() 回调中,Activity接收到RESULT_OK结果码。如果蓝牙由于某一个错误没有启动,则返回码为 RESULT_CANCELED

private static final int REQUEST_ENABLE_BT = 1;

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_ENABLE_BT:   // 开启蓝牙的处理
            if (resultCode == Activity.RESULT_OK) {
                Toast.makeText(this, "蓝牙已开启!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "请开启蓝牙!", Toast.LENGTH_SHORT).show();
            }
    }
}

打开蓝牙还可以用enable()方法来开启,无需询问用户(无声息的开启蓝牙设备),这时就需要用到android.permission.BLUETOOTH_ADMIN权限。

mBluetoothAdapter.enable();

3.关闭蓝牙

关闭蓝牙直接用disable()方法即可:

if (mBluetoothAdapter != null) {
    if (mBluetoothAdapter.isEnabled()) {
        mBluetoothAdapter.disable();  // 关闭蓝牙
        Toast.makeText(BlueToothListActivity.this, "蓝牙已关闭", Toast.LENGTH_SHORT).show();
    }
}
查找设备

使用 BluetoothAdapter,能够找到远程蓝牙设备,不论是通过设备查找还是通过查询已匹配设备列表。

已匹配意味着两个设备都知道各自的存在,拥有一个能够用来授权的共享连接密钥,并且能够互相建立一个加密的连接。而已连接意味着当前设备间共享一个RFCOMM信道,并且能够互相传递数据。

目前Android蓝牙API在一个RFCOMM连接建立之前要求设备已经配对。

查询已匹配的设备

调用getBondedDevices()方法会返回代表已匹配设备BluetoothDevice的一个集合。能够查询所有已匹配设备,然后使用ArrayAdapter,将每个设备的名称显示给用户。

ArrayAdapter<String> pairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_item);

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
    for (BluetoothDevice device : pairedDevices) {
    	pairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
} else {
    // 获取res/values/strings.xml文件中的字符串
    //<string name="none_paired">没有适配的设备</string>
    String noDevices = getResources().getText(R.string.none_paired).toString();
    pairedDevicesArrayAdapter.add(noDevices);
}

其中,Activity对应的布局文件中应包含ListView,对应布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView android:id="@+id/paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

ArrayAdapter期望布局文件里只有一个TextView,连Layout都不能包含。ArrayAdapter对应的布局文件device_item.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:padding="5dp"
/>

之后找到ListView,将Adapter的数据绑定到页面:

ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(pairedDevicesArrayAdapter);
扫描新设备

调用startDiscovery()即可开始感应设备。这个处理过程是异步的,并且方法会立即返回一个boolean值,用于指明感应操作是否已经成功启动。感应过程通常包含一个大约十二秒的查询扫描操作,紧接着的页面扫描每个已找到的设备取出它的蓝牙名称。

public void searchNewDevices(){
    Toast.makeText(this, "开始搜索!", Toast.LENGTH_SHORT).show();
    
    if (mBluetoothAdapter.isDiscovering()) {
        mBluetoothAdapter.cancelDiscovery();
    }
    mBluetoothAdapter.startDiscovery();
}

为了接收关于每个感应到的设备的信息,必须为 ACTION_FOUND intent注册一个BroadcastReceiver。对每个设备,系统将会广播 ACTION_FOUND intent。这个intent携带额外的参数 EXTRA_DEVICE和 EXTRA_CLASS,分别包含一个相应的BluetoothDevice 和一个 BluetoothClass。

private ArrayAdapter<String> mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);

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());
            }
            // When discovery is finished
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            Toast.makeText(MainActivity.this, "搜索完成!", Toast.LENGTH_SHORT).show();
            if (mNewDevicesArrayAdapter.getCount() == 0) {
                // 获取res/values/strings.xml文件中的字符串
                // <string name="none_found">没有发现设备</string>
                String noDevices = getResources().getText(R.string.none_found).toString();
                mNewDevicesArrayAdapter.add(noDevices);
            }
        }
    }
};

// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
// Register for broadcasts when discovery has finished
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);

ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);

最后记得取消广播注册:

@Override
protected void onDestroy() {
    super.onDestroy();

    if (mBluetoothAdapter != null) {
        mBluetoothAdapter.cancelDiscovery();
    }
    // Unregister broadcast listeners
    unregisterReceiver(mReceiver);
}

这里需要注意的是,如果你的代码将运行在(Build.VERSION.SDK_INT >= 23)的设备上,那么务必加上以下权限,并在代码中动态的申请权限

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
private static final int REQUEST_PERMISSION_ACCESS_LOCATION = 1;

private void requestPermission() {
    if (Build.VERSION.SDK_INT >= 23) {
        int checkAccessFinePermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
        if (checkAccessFinePermission != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                                              REQUEST_PERMISSION_ACCESS_LOCATION);
            Log.e(getPackageName(), "没有权限,请求权限");
            return;
        }
        Log.e(getPackageName(), "已有定位权限");
        //这里可以开始搜索操作
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case REQUEST_PERMISSION_ACCESS_LOCATION: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.e(getPackageName(), "开启权限permission granted!");
                //这里可以开始搜索操作
            } else {
                Log.e(getPackageName(), "没有定位权限,请先开启!");
            }
        }
    }
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
连接设备
作为客户端连接

为了初始化一个到远程设备的连接,必须首先获得一个代表远程设备的 BluetoothDevice 对象。

// address is device MAC address
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

然后必须使用BluetoothDevice去获取一个 BluetoothSocket 并且初始化这个连接。

这里是基本的操作流程:

  1. 使用 BluetoothDevice,通过调用 createRfcommSocketToServiceRecord(UUID)得到一个 BluetoothSocket.这将初始化一个连接到 BluetoothDevice的BluetoothSocket。
  2. 调用 connect()初始化连接
    一旦调用这个接口,为了匹配UUID,系统会在远程设备上执行一个SDP查询操作。如果查询成功,并且远程设备接收这个连接,则在连接期间它会共享使用RFCOMM信道,并且connect()调用也会返回。这个方法是一个阻塞调用。如果因为任何原因,连接失败或者connect() 方法超时(大约超过12秒),则它会抛出一个异常。
    因为connect() 是一个阻塞调用,这个连接处理应当总是在主activity线程之外的一个独立线程中执行。
private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
 
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
 
        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }
 
    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();
 
        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
 
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
 
    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

注意cancelDiscovery()是在建立连接之前调用的。应当在连接之前总是执行这个操作,并且不用检查是否在运行,调用cancelDiscovery()总是安全的(如果你确实想检查,调用 isDiscovering())。

当用完 BluetoothSocket,一定要调用 close()来完成清理工作。这么做会立即关闭掉已连接的socket并且清理所有中间资源。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值