Android蓝牙 - 经典蓝牙BT

一、蓝牙发展历程

蓝牙(Bluetooth):是一种无线技术标准,可实现设备间短距离数据交换。 蓝牙可以以一定的周期发送广播,手机端接收到广播后,解析广播包,可做设备识别、配对,事件通知以及指令控制等。低精度定位根据设备的信号强度,可以估算出大概方位和距离。

蓝牙发展至今经历了多个版本的更新,其中,将1.x~3.0之间的版本称之为经典蓝牙,4.x开始的蓝牙称之为低功耗蓝牙,也就是蓝牙BLE。

根据应用、协议类型等,可以对蓝牙进行以下分类:
image.png

二、经典蓝牙API

1、BluetoothAdapter:代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作, 例如 : 启动设备发现,获取已配对设备,通过mac蓝牙地址获取蓝牙设备等。

  • enable():打开蓝牙,需要 BLUETOOTH_ADMIN权限。
  • disable():关闭蓝牙,需要 BLUETOOTH_ADMIN权限。
  • checkBluetoothAddress(String address):验证蓝牙设备MAC地址是否有效。
  • getAddress():获取本地蓝牙适配器的硬件地址(MAC地址)
  • getBondedDevices():获取与本机蓝牙所有绑定的远程蓝牙信息。
  • getName():获取本地蓝牙适配器的蓝牙名称。
  • setName(String name):设置本地蓝牙适配器的蓝牙名称。
  • isEnabled():判断当前蓝牙适配器是否打开。
  • isDiscovering():判断蓝牙适配器是否正在处于扫描过程中。
  • startDiscovery():开始扫描周边蓝牙设备。
  • cancelDiscovery():取消蓝牙搜索操作。
  • getScanMode():获取本地蓝牙适配器的当前蓝牙扫描模式。

蓝牙扫描模式:
SCAN_MODE_NONE: 该设备不能扫描以及被扫描。
SCAN_MODE_CONNECTABLE:该设备可以扫描其他蓝牙设备。
SCAN_MODE_CONNECTABLE_DISCOVERABLE:该设备既可以扫描其他设备,也可以被其他设备扫描发现。

  • getState():获取本地蓝牙适配器的当前状态。

蓝牙适配器状态:
STATE_OFF:表示本地蓝牙适配器已关闭。
STATE_TURNING_ON:表示本地蓝牙适配器正在打开。
STATE_ON:表示本地蓝牙适配器已开启,并可供使用。
STATE_TURNING_OFF:表示本地蓝牙适配器正在关闭。

  • getRemoteDevice(String address):获取给定蓝牙硬件地址的BluetoothDevice对象。

如果address中的MAC无效无效,将抛出IllegalArgumentException异常。

  • listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid):创建不安全的蓝牙服务套接字。
  • listenUsingRfcommWithServiceRecord(String name, UUID uuid):创建一个正在监听的安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。

2、BluetoothDevice:代表了一个远程的蓝牙设备, 通过这个类可以查询远程设备的物理地址, 名称, 连接状态等信息。这个类实际上只是一个蓝牙硬件地址的简单包装,这个类的对象是不可变的。对这个类的操作, 会执行在远程蓝牙设备的硬件上。

  • getName():获取远程蓝牙设备的蓝牙名称。
  • getAddress():获取远程蓝牙设备的硬件地址。
  • createBond():开始与远程蓝牙设备的绑定过程。
  • getBondState():获取远程蓝牙设备的绑定状态。

蓝牙绑定状态:
BOND_NONE:远程设备未绑定。
BOND_BONDING:正在与远程设备进行绑定。
BOND_BONDED:远程设备已绑定。

  • createInsecureRfcommSocketToServiceRecord(UUID uuid):创建不安全的蓝牙套接字。
  • createRfcommSocketToServiceRecord(UUID uuid):创建安全的蓝牙套接字。

3、BluetoothServerSocket:侦听蓝牙服务套接字。使用BluetoothServerSocket可以创建一个监听服务端口, 使用accept方法阻塞, 当该方法监测到连接的时候, 就会返回一个BluetoothSocket对象来管理这个连接。BluetoothServerSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭蓝牙服务套接字。需要BLUETOOTH权限。

  • accept():阻塞直到建立连接,成功连接时连接的BluetoothSocket对象。
  • accept(int timeout):阻塞直到建立连接或超时,成功连接时连接的BluetoothSocket对象。
  • close():关闭该监听服务端口,并释放所有关联的资源。

4、BluetoothSocket:蓝牙套接口。在服务器端,使用BluetoothServerSocket创建侦听服务器套接字。当连接被BluetoothServerSocket接受时,它将返回一个新的BluetoothSocket来管理连接。 在客户端,使用单个BluetoothSocket来启动连接并管理连接。BluetoothSocket是线程安全的,close方法始终会立即中止正在进行的操作并关闭套接字。需要BLUETOOTH权限。

  • connect():尝试连接到远程蓝牙服务器。
  • isConnected():获取此套接字的连接状态,即是否与远程蓝牙服务连接。
  • getRemoteDevice():获取此套接字连接的远程蓝牙设备。
  • getInputStream():获取与此套接字关联的输入流。

即使套接字尚未连接,输入流也会返回,但对该流的操作将抛出IOException异常,直到关联的套接字连接。

  • getOutputStream():获取与此套接字关联的输出流。

即使套接字尚未连接,输出流也会返回,但对该流的操作将抛出IOException异常,直到关联的套接字连接。

  • close():关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法不起作用。

三、经典蓝牙开发

1、在工程清单文件AndroidManifest.xml中添加权限:

<!--如果使用了BLUETOOTH_ADMIN权限,那么必须使用BLUETOOTH权限-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<!--android6.0后需要搜索周边蓝牙设备,需要添加以下两个权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<!--要求设备硬件必须支持蓝牙-->
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>

2、获取本地蓝牙适配器
BluetoothAdapter有两种方式获取,方式二要求Android4.3以上才可以用,建议使用方式一,比较通用。

//第一种方式
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

//第二种方式
BluetoothManager manager = (BluetoothManager) Context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();

3、打开蓝牙
方式一:通过Intent来向用户弹框请求打开蓝牙,可以重写onActivityResult来监听打开蓝牙的请求结果.

public void openBluetooth(){
    if (mBluetoothAdapter==null) {
		//自定义方法,用来往TextView上添加提示信息
        showTip("当前设备不支持蓝牙功能!");
        return;
    }

    if (mBluetoothAdapter.isEnabled()) {
        showTip("蓝牙已打开");
        return;
    }

    Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(intent,GlobalDef.REQ_CODE_OPEN_BT);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode==GlobalDef.REQ_CODE_OPEN_BT) {
        if (resultCode == Activity.RESULT_OK) {
            showTip("蓝牙打开成功");
        } else {
            showTip("蓝牙打开失败");
        }
    }
}

方式二:通过enable方法静默打开蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示,向用户请求打开蓝牙)

mBluetoothAdapter.enable();

4、关闭蓝牙
关闭蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示)

mBluetoothAdapter.disable();

5、允许蓝牙可被发现
如果开发的外围设备如音箱,需要被中心设备发现扫描到,需要该功能。有两种实现方式:
方式一:通过Intent方式向用户请求允许蓝牙被搜索。如果蓝牙没有开启,用户点击确定后,会首先开启蓝牙,继而设置蓝牙能被扫描。

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置蓝牙可见性的时间,默认持续时间为120秒,每个请求的最长持续时间上限为300秒
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
startActivity(intent);

方式二:通过反射的方式来设置蓝牙可见性,且不会出现弹框,如果蓝牙没有开启,通过此方式并不会直接打开蓝牙。

/**
 * 设置蓝牙可见
 * @param adapter
 * @param timeout 超时为0时,永久可见
 */
public static void setDiscoverableTimeout(BluetoothAdapter adapter, int timeout) {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    try {
        Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
        setDiscoverableTimeout.setAccessible(true);
        Method setScanMode = BluetoothAdapter.class.getMethod("setScanMode", int.class, int.class);
        setScanMode.setAccessible(true);

        setDiscoverableTimeout.invoke(adapter, timeout);
        setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

6、注册蓝牙广播

mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//扫描到设备会通过该广播获取到
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//设备的绑定状态
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙状态
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//发起扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//结束扫描
mContext.registerReceiver(mBluetoothBroadcastReceiver,filter);

广播类:

class BluetoothBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG,"Action received is "+action);
        //蓝牙搜索
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if(scanDevice == null || scanDevice.getName() == null){
                return;
            }

            int btType = scanDevice.getType();
            if(btType == BluetoothDevice.DEVICE_TYPE_LE || btType==BluetoothDevice.DEVICE_TYPE_UNKNOWN){
                return;
            }

            Log.d(TAG, "bt name=" + scanDevice.getName() + " address=" + scanDevice.getAddress());
			//将搜索到的蓝牙设备加入列表
            deviceList.add(scanDevice);
            short rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
            rssiList.add(rssi);
            listAdapter.notifyDataSetChanged();//通知ListView适配器更新
        }
        //蓝牙配对
        else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if(mCurDevice!=null && btDevice.getAddress().equals(mCurDevice.getAddress())){
                int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
                if (state == BluetoothDevice.BOND_NONE) {                    
                    Log.i(TAG,"已取消与设备" + btDevice.getName() + "的配对");         
                } else if (state == BluetoothDevice.BOND_BONDED) {
                    Log.i(TAG,"与设备" + btDevice.getName() + "配对成功");                   
                }
            }
        }  else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
            switch (blueState) {
                case BluetoothAdapter.STATE_TURNING_ON:
                    Log.i(TAG,"STATE_TURNING_ON 手机蓝牙正在开启");
                    break;
                case BluetoothAdapter.STATE_ON:
                    Log.i(TAG,"STATE_ON 手机蓝牙开启");                  
                    break;
                case BluetoothAdapter.STATE_TURNING_OFF:
                    Log.i(TAG,"STATE_TURNING_OFF 手机蓝牙正在关闭");
                    break;
                case BluetoothAdapter.STATE_OFF:
                    Log.i(TAG,"STATE_OFF 手机蓝牙关闭");
                    break;
            }
        }
    }
}

7、发起扫描

if (mBluetoothAdapter.isDiscovering()) {
    mBluetoothAdapter.cancelDiscovery();
}
//搜索到的蓝牙设备通过广播接收
mBluetoothAdapter.startDiscovery();

8、与扫描到的设备进行配对

private void pair(BluetoothDevice device) {
    //在配对之前,停止搜索
    if (mBluetoothAdapter.isDiscovering()) {
        mBluetoothAdapter.cancelDiscovery();
    }
    if (device.getBondState() != BluetoothDevice.BOND_BONDED) {//没配对才配对
        try {
            Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
            Boolean returnValue = (Boolean) createBondMethod.invoke(device);
            if (returnValue){
                Log.d(TAG, "配对成功...");
            }
         } catch (NoSuchMethodException e) {
                e.printStackTrace();
         } catch (IllegalAccessException e) {
                e.printStackTrace();
         } catch (InvocationTargetException e) {
                e.printStackTrace();
         }
    }
}


public void unpair(BluetoothDevice device){
    Log.d(TAG, "attemp to cancel bond:" + device.getName());
    try {
        Method removeBondMethod = device.getClass().getMethod("removeBond");
        Boolean returnValue = (Boolean) removeBondMethod.invoke(device);
        if (returnValue){
            Log.d(TAG, "解配对成功...");
        }
    } catch (Exception e) {           
        e.printStackTrace();
        Log.e(TAG, "attemp to cancel bond fail!");
    }
}

9、蓝牙连接
经典蓝牙连接相当于socket连接,是个非常耗时的操作,所以应该放到子线程中去完成。
9.1 新建ConnectBlueTask

public class ConnectBlueTask extends AsyncTask<BluetoothDevice, Integer, BluetoothSocket> {
    private static final String TAG = ConnectBlueTask.class.getName();
    private BluetoothDevice bluetoothDevice;
    private ConnectBlueCallBack callBack;
 
    public ConnectBlueTask(ConnectBlueCallBack callBack){
        this.callBack = callBack;
    }
 
    @Override
    protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) {
        bluetoothDevice = bluetoothDevices[0];
        BluetoothSocket socket = null;
        try{
            Log.d(TAG,"开始连接socket,uuid:" + ClassicsBluetooth.UUID);
            socket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(ClassicsBluetooth.UUID));
            if (socket != null && !socket.isConnected()){
                socket.connect();
            }
        }catch (IOException e){
            Log.e(TAG,"socket连接失败");
            try {
                socket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
                Log.e(TAG,"socket关闭失败");
            }
        }
        return socket;
    }
 
    @Override
    protected void onPreExecute() {
        Log.d(TAG,"开始连接");
        if (callBack != null) callBack.onStartConnect();
    }
 
    @Override
    protected void onPostExecute(BluetoothSocket bluetoothSocket) {
        if (bluetoothSocket != null && bluetoothSocket.isConnected()){
            Log.d(TAG,"连接成功");
            if (callBack != null) callBack.onConnectSuccess(bluetoothDevice, bluetoothSocket);
        }else {
            Log.d(TAG,"连接失败");
            if (callBack != null) callBack.onConnectFail(bluetoothDevice, "连接失败");
        }
    }
}

9.2 启动连接线程

/**
 * 连接 (在配对之后调用)
 * @param device
 */
public void connect(BluetoothDevice device, ConnectBlueCallBack callBack){
    if (device == null){
        Log.d(TAG, "bond device null");
        return;
    }
    if (!isBlueEnable()){
        Log.e(TAG, "Bluetooth not enable!");
        return;
    }
    //连接之前把扫描关闭
    if (mBluetoothAdapter.isDiscovering()){
        mBluetoothAdapter.cancelDiscovery();
    }
    new ConnectBlueTask(callBack).execute(device);
}

9.3 判断是否连接成功

/**
 * 蓝牙是否连接
 * @return
 */
public boolean isConnectBlue(){
    return mBluetoothSocket != null && mBluetoothSocket.isConnected();
}

9.4 断开连接

/**
 * 断开连接
 * @return
 */
public boolean cancelConnect(){
    if (mBluetoothSocket != null && mBluetoothSocket.isConnected()){
        try {
            mBluetoothSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
    mBluetoothSocket = null;
    return true;
}

9.5 MAC地址连接

/**
 * 输入mac地址进行自动配对
 * 前提是系统保存了该地址的对象
 * @param address
 * @param callBack
 */
public void connectMAC(String address, ConnectBlueCallBack callBack) {
    if (!isBlueEnable()){
        return ;
    }
    BluetoothDevice btDev = mBluetoothAdapter.getRemoteDevice(address);
    connect(btDev, callBack);
}

10、蓝牙通信
10.1 读取数据线程

public class ReadTask extends AsyncTask<String, Integer, String> {
    private static final String TAG = ReadTask.class.getName();
    private ReadCallBack callBack;
    private BluetoothSocket socket;
 
    public ReadTask(ReadCallBack callBack, BluetoothSocket socket){
        this.callBack = callBack;
        this.socket = socket;
    }
    @Override
    protected String doInBackground(String... strings) {
        BufferedInputStream in = null;
        try {
            StringBuffer sb = new StringBuffer();
            in = new BufferedInputStream(socket.getInputStream());
 
            int length = 0;
            byte[] buf = new byte[1024];
            while ((length = in.read()) != -1) {
                sb.append(new String(buf,0,length));
            }
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "读取失败";
    }
 
    @Override
    protected void onPreExecute() {
        Log.d(TAG,"开始读取数据");
        if (callBack != null) callBack.onStarted();
    }
 
    @Override
    protected void onPostExecute(String s) {
        Log.d(TAG,"完成读取数据");
        if (callBack != null){
            if ("读取失败".equals(s)){
                callBack.onFinished(false, s);
            }else {
                callBack.onFinished(true, s);
            }
        }
    }
}

10.2 写入数据线程

public class WriteTask extends AsyncTask<String, Integer, String>{
    private static final String TAG = WriteTask.class.getName();
    private WriteCallBack callBack;
    private BluetoothSocket socket;
 
    public WriteTask(WriteCallBack callBack, BluetoothSocket socket){
        this.callBack = callBack;
        this.socket = socket;
    }
    @Override
    protected String doInBackground(String... strings) {
        String string = strings[0];
        OutputStream outputStream = null;
        try{
            outputStream = socket.getOutputStream();
 
            outputStream.write(string.getBytes());
        } catch (IOException e) {
            Log.e("error", "ON RESUME: Exception during write.", e);
            return "发送失败";
        }finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "发送成功";
 
 
    }
 
    @Override
    protected void onPreExecute() {
        if (callBack != null) callBack.onStarted();
    }
 
    @Override
    protected void onPostExecute(String s) {
        if (callBack != null){
            if ("发送成功".equals(s)){
                callBack.onFinished(true, s);
            }else {
                callBack.onFinished(false, s);
            }
 
        }
    }
}

参考文章:
Android经典蓝牙开发全流程
Android蓝牙开发—经典蓝牙详细开发流程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值