Android蓝牙开发介绍

蓝牙(BlueTooth)是一种无线技术标准,是当今移动终端最流行的三种数据传输方案之一,其余两种是WiFi和NFC(由于红外传输只能是直线传输,故更多地用于遥控器等设备,不适合数据传输)。蓝牙的传输特点是传输距离短(≤10m),速度适中,为24Mbps(比WiFi(802.11ac 1.3Gbps)慢,比NFC(≤400Kbit/s)快),功耗低(最新4.1版本)。
本文将介绍在Android移动设备上蓝牙技术的使用。

添加蓝牙权限

调用系统蓝牙技术方案实现设备间的数据传输,涉及到侵犯用户的隐私,需添加如下权限:

//程序中使用了蓝牙技术需要添加的权限
<uses-permission android:name="android.permission.BLUETOOTH" />

//当本程序需要和其他程序绑定时添加的权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

检测设备是否支持蓝牙

创建BlueToothAdapter对象,判断该对象是否为空,以确定当前设备是否支持蓝牙:

//获得BluetoothAdapter对象
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
//通过判断BluetoothAdapter对象是否为空,来确定设备是否支持蓝牙功能
 if (mAdapter != null) {
            //设备支持蓝牙功能
        } else {
            //设备不支持蓝牙功能
        }

判断当前设备的蓝牙是否处于开启状态

//断言当前设备具备蓝牙功能
assert(mAdapter != null);
//若BluetoothAdapter.isEnabled()返回true,则当前设备处于开启状态,否则处于关闭状态
if(mAdapter.isEnabled())
{
    //当前设备的蓝牙处于开启状态
}
else
{
    //当前设备的蓝牙处于关闭状态
}

开启和关闭蓝牙功能

通过隐式intent开启蓝牙功能。可以在onActivityResult()方法中获取开启成功与否的消息;也可以使用广播接收器(BroadcastReceiver)接收系统发出的关于设备蓝牙状态的广播消息。

//使用隐式intent开启蓝牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//启动蓝牙
startActivityForResult(intent, 0);

//在onActivityResult()方法中得知蓝牙是否开启
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
     if(requestCode == 0)
     {
      if(resultCode == RESULT_OK)
      {
        //打开成功
      }
      else
      {
        //打开失败
      }
     }
}

//通过BroadcastReceiver接收系统发出的关于设备蓝牙状态的广播消息
//在onCreate方法中动态注册BroadcastReceiver
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //表示程序希望接收到蓝牙状态发生改变时,系统发出的广播
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(receiver, filter);
}
//定义BroadcastReceiver截获广播
private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //截获的广播附带有BluetoothAdapter.EXTRA_STATE信息,通过该键对应的值可以判断系统发出的是什么广播,从而确定蓝牙处于何种状态
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
            switch (state) {
                //蓝牙处于关闭状态
                case BluetoothAdapter.STATE_OFF:
                    //"STATE_OFF"状态
                    break;
                //蓝牙处于开启状态
                case BluetoothAdapter.STATE_ON:
                    //"STATE_ON"状态
                    break;
                //蓝牙处于正在打开状态(开启蓝牙一个异步操作)
                case BluetoothAdapter.STATE_TURNING_ON:
                    //"STATE_TURNING_ON"状态
                    break;
                //蓝牙处于正在关闭状态
                case BluetoothAdapter.STATE_TURNING_OFF:
                    //"STATE_TURNING_OFF"状态
                    break;
                default:
                    break;
            }
        }
    };

设置设备的可见性

蓝牙设备为了安全起见,可设置为不可见,其他设备无法搜索到蓝牙处于不可见状态的设备;反之,若需要被其他设备搜索到,需要将蓝牙设为可见:

//通过隐式intent设置蓝牙的可见性
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置蓝牙的可见性为300秒(5分钟)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

当设备变为可被搜索或变为不可被搜索时,系统会发出一条广播,注册一个广播接收器可获得系统发出的广播:

IntentFilter filter = new IntentFilter();
//监听 “设备变为可被搜索时”(也可以监听“设备变为不可见时”),系统发出的广播
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
//注册该广播
registerReceiver(mReceiver, filter);

在onReceive方法中,接受广播:

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        //接收“当蓝牙可见状态发生改变时”系统发出的广播
if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
                //获取该广播所附带的BluetoothAdapter.EXTRA_SCAN_MODE键对应的值
                int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 0);
                //若值为BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,表示蓝牙变为可见状态
                if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
                    setProgressBarIndeterminateVisibility(true);
                } 
                //否则蓝牙变为不可见状态
                else {
                    setProgressBarIndeterminateVisibility(false);
                }
            }
}

查找并绑定设备

开启蓝牙后,可以通过下面方法查找周围已开启蓝牙且蓝牙可见的设备:

BlueToothAdapter.startDiscovery();

当程序开始查找设备、结束查找设备、发现一个可见的设备,系统都会发送一条广播,程序需要通过广播接收器接受响应状态发生时系统发出的广播,当接收到的广播所附带的action为BluetoothAdapter.ACTION_DISCOVERY_STARTED时,表示程序开始查找设备;action为BluetoothAdapter.ACTION_DISCOVERY_FINISHED时,表示程序结束查找,action为BluetoothDevice.ACTION_ FOUND时,intent附带的键BluetoothDevice.EXTRA_DEVICE所对应的值,就是这个搜索到的蓝牙设备:

//查找设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);

//接收搜索到的设备
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                setProgressBarIndeterminateVisibility(true);
                //初始化数据列表
                mDeviceList.clear();
                mAdapter.notifyDataSetChanged();
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                setProgressBarIndeterminateVisibility(false);
            }
        //搜索到一个设备
        else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //将搜索到的设备添加到ListView中
                mDeviceList.add(device);
                //Adapter通知刷新ListView
                mAdapter.notifyDataSetChanged();
        }

查找到设备后,可通过createBond()方法对设备进行绑定。createBond()方法在API 19 (Android 4.4) 及以上被引入,低于API 19的版本不支持蓝牙绑定:

mListView.setOnItemClickListener(bindDeviceClick);
private AdapterView.OnItemClickListener bindDeviceClick = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            BluetoothDevice device = mDeviceList.get(i);
            //绑定选中的设备(需API级别≥19),同时需要权限<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
            device.createBond();

当绑定或解绑设备时,系统也会发出广播,在onReceive()方法中捕获该广播:

//绑定状态发生改变
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //没有绑定的设备
                if (remoteDevice == null) {
                    //未绑定任何设备
                    return;
                }
                //绑定设备处于何种状态
                int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0);
                //已绑定设备
                if (status == BluetoothDevice.BOND_BONDED) {
                    //"Bonded " + remoteDevice.getName())
                } 
                //正在绑定设备
                else if (status == BluetoothDevice.BOND_BONDING) {
                    //"Bonding " + remoteDevice.getName())
                } 
                //结束绑定设备
                else if (status == BluetoothDevice.BOND_NONE) {
                    //"Not bond " + remoteDevice.getName())
                }
            }

蓝牙传输数据

Android蓝牙开发介绍_第1张图片

在TCP/IP通信中,可使用Socket交换数据,客户端(Client)和服务器端(Server)的通信流程如上图所示。在客户端,首先建立一个socket对象,然后建立一个connect连接服务,在connect方法中传入需要要换数据的服务器端IP地址和端口号(在蓝牙传输中,这里有所不同),接着利用IO流读写数据,最后关闭连接服务;在服务器端,同样需要建立一个socket连接,并绑定端口号,接着客户端将等待client端的socket的连接请求,此时会阻塞当前线程,所以需要将该操作放到子线程中执行,当有client请求连接时,服务器端accept该请求,并建立一个新的socket对象与之通信,原有socket继续等待,当新建socket与客户端通信结束后,断开连接。(一般情况下由客户端中断连接)
在蓝牙的socket通信中,服务器端通信流程不包含上图所示的bind()和listen()步骤,也就是说,socket创建后直接到达accept,阻塞也是发生在accept;客户端的流程就是上图所示,但参数与TCP/IP通信时不一样。
总结起来,Android蓝牙的server端的建立步骤如下:

  1. 通过listenUsingRfcommWithServiceRecord创建BluetoothServerSocket对象;
  2. 监听网络accept;(阻塞线程)
  3. 处理网络socket;
  4. 关闭连接。

client端的建立步骤如下:

  1. 通过createRfcommSocketToServiceRecord创建BlueToothSocket对象;
  2. 建立服务器端connect;(阻塞线程)
  3. 处理数据;
  4. 关闭连接。

服务端

当触发监听时,程序进入监听状态,此时设备可看成分服务器端,用于等待其他设备绑定:

//创建一个新线程,用于处理会发生阻塞的监听网络的方法accept
private AcceptThread mAcceptThread;
//获取BlueToothAdapter对象
private BlueToothController mController = new BlueToothController();
//创建Handler对象,用于用于在子线程中通知主线程(UI线程)更新UI
private Handler mUIHandler = new MyHandler();
private class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                //开始监听时,接收由子线程发来的消息
                case Constant.MSG_START_LISTENING:
                //更新UI 
                setProgressBarIndeterminateVisibility(true);
                    break;
                //结束监听时,接收由子线程发来的消息
                case Constant.MSG_FINISH_LISTENING:
                //更新UI
                setProgressBarIndeterminateVisibility(false);
                    break;
                    //接收到数据后,接收由子线程发来的消息及其附加信息
                case Constant.MSG_GOT_DATA:
                //在UI线程中显示
                    showToast("data: "+String.valueOf(msg.obj));
                    break;
                    //当发生错误时,接收由子线程发来的消息
                case Constant.MSG_ERROR:
                //显示错误原因
                    showToast("error: "+String.valueOf(msg.obj));
                    break;
                    //当程序作为客户端连接到服务器后,接收接收由子线程发来的消息
                case Constant.MSG_CONNECTED_TO_SERVER:
                //通知用户连接成功
                    showToast("Connected to Server");
                    break;
                    //当程序作为服务器端绑定了一个客户端后,接收接收由子线程发来的消息
                case Constant.MSG_GOT_A_CLINET:
                //通知用户绑定成功
                    showToast("Got a Client");
                    break;
            }
        }
    }
//触发监听状态
@Override
onClick(View v)
{
    if( mAcceptThread != null) {
                mAcceptThread.cancel();
            }
            //创建一个新线程,传入BlueToothAdapter对象实例和Handler对象实例
            mAcceptThread = new AcceptThread(mController.getAdapter(), mUIHandler);
            //启动该线程
            mAcceptThread.start();
}

子线程AcceptThread如下所示:

public class AcceptThread extends Thread {
    private static final String NAME = "BlueToothClass";
    //为了保证蓝牙连接,必须使用如下代码的唯一的UUID:
    public static final String CONNECTTION_UUID = "00001101-0000-1000-8000-00805F9B34FB";
    private static final UUID MY_UUID = UUID.fromString(CONNECTTION_UUID);

    private final BluetoothServerSocket mmServerSocket;
    private final BluetoothAdapter mBluetoothAdapter;
    private final Handler mHandler;
    private ConnectedThread mConnectedThread;

    public AcceptThread(BluetoothAdapter adapter, Handler handler) {

        mBluetoothAdapter = adapter;
        mHandler = handler;
        // 构造方法中创建一个临时的BluetoothServerSocket对象的引用
        BluetoothServerSocket tmp = null;
        try {

            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }

//子线程中运行的代码
    public void run() {
        BluetoothSocket socket = null;
        // 不断监听来自客户端的连接请求
        while (true) {
            try {
//通知UI线程:“服务器端开始监听客户端的请求”
mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
//accept方法会阻塞线程
                socket = mmServerSocket.accept();
            } catch (IOException e) {
//若出现异常,退出监听
                mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
                break;
            }
            // 连接成功
            if (socket != null) {
                // 另开启一个子线程,新建一个socket用于传输数据
                manageConnectedSocket(socket);
                try {
                //强制关闭监听
                    mmServerSocket.close();
                    mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    private void manageConnectedSocket(BluetoothSocket socket) {
        //只支持同时处理一个连接
        if( mConnectedThread != null) {
            mConnectedThread.cancel();
        }
        //通知UI线程连接到一个蓝牙设备(客户端)
        mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
        //另开启的新线程,用于传输数据,传入BluetoothSocket对象引用和Handler对象引用
        mConnectedThread = new ConnectedThread(socket, mHandler);
        //启动线程
        mConnectedThread.start();
    }

    //主动停止监听
    public void cancel() {
        try {
            mmServerSocket.close();
//通知UI线程:“已停止监听”
            mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
        } catch (IOException e) { }
    }
//向客户端写入数据
    public void sendData(byte[] data) {
        if( mConnectedThread!=null){
            mConnectedThread.write(data);
        }
    }
}

另创建的用于与绑定的设备进行数据通信的线程:

public class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    private final Handler mHandler;

    public ConnectedThread(BluetoothSocket socket, Handler handler) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        mHandler = handler;
        //获得输入输出流
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

//在子线程中运行的代码
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()

        // 不断循环以读取数据
        while (true) {
            try {

                // 读取数据
                bytes = mmInStream.read(buffer);
                // 将读取的数据信息发送至UI线程并显示数据
                if( bytes >0) {
                    Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA, new String(buffer, 0, bytes, "utf-8"));
                    mHandler.sendMessage(message);
                }
                Log.d("GOTMSG", "message size" + bytes);
            } catch (IOException e) {
                mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
                break;
            }
        }
    }

    //写入数据
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    //取消连接
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

客户端

当设备作为客户端与其他设备(服务端)交换数据时,需保证设备之间已绑定:

//作为客户端连接,设备之间处于绑定状态
mListView.setOnItemClickListener(bindedDeviceClick);
private AdapterView.OnItemClickListener bindedDeviceClick = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
            BluetoothDevice device = mBondedDeviceList.get(i);
            if( mConnectThread != null) {
                mConnectThread.cancel();
            }
            //创建子线程
            mConnectThread = new ConnectThread(device, mController.getAdapter(), mUIHandler);
            mConnectThread.start();
        }
    };

客户端的线程与服务器端的子线程类似,代码如下:

public class ConnectThread extends Thread {
//UUID必须与之前的UUID一致
    private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    private BluetoothAdapter mBluetoothAdapter;
    private final Handler mHandler;
    private ConnectedThread mConnectedThread;

    public ConnectThread(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) {

        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
        mBluetoothAdapter = adapter;
        mHandler = handler;

        try {

            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }

    public void run() {
        // 首先使设备不再查找其他设备,以提高效率
        mBluetoothAdapter.cancelDiscovery();

        try {
            //connect方法会阻塞线程
            mmSocket.connect();
        } catch (Exception connectException) {
            mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, connectException));
            // 连接错误,结束线程并抛出异常
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }

        // 与设备交换数据需定义在一个新的线程中(新建一个socket专门用于交互数据)
        manageConnectedSocket(mmSocket);
    }

    private void manageConnectedSocket(BluetoothSocket mmSocket) {
        mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
        mConnectedThread = new ConnectedThread(mmSocket, mHandler);
        mConnectedThread.start();
    }


    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }

    public void sendData(byte[] data) {
        if( mConnectedThread!=null){
            mConnectedThread.write(data);
        }
    }
}

蓝牙规范简介

蓝牙规范(BlueTooth Profile)又叫配置文件,是一种协议。蓝牙规范定义了一种基于蓝牙的应用(如蓝牙耳机、车载蓝牙设备、蓝牙鼠标),每各蓝牙规范主要包括针对开发者的接口、消息的格式和标准,以及如何使用蓝牙协议,使用蓝牙协议通信的蓝牙设备必须使用同一UUID,否则无法配对通信。
常用的蓝牙规范:

  • A2DP(Advanced Audio Distribution Profile)蓝牙音频传输模型
    –应用方式:蓝牙立体声耳机+移动终端
    –用途:听音乐等

  • BlueTooth HeadSet 带打电话功能的蓝牙耳机
    –用途:蓝牙耳机+移动终端、车载蓝牙+移动终端

下列代码以HeadSet为例,演示了使用蓝牙耳机与手机连接的蓝牙协议:

public abstract class BluetoothHeadsetUtils {
    private Context mContext;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothHeadset mBluetoothHeadset;
    private BluetoothDevice mConnectedHeadset;

    private AudioManager mAudioManager;

    private boolean mIsOnHeadsetSco;
    private boolean mIsStarted;

    /** * Constructor * * @param context */
    public BluetoothHeadsetUtils(Context context) {
        mContext = context;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    }

    /** * Call this to start BluetoothHeadsetUtils functionalities. * * @return The return value of startBluetooth() or startBluetooth11() */
    public boolean start() {
        if (mBluetoothAdapter.isEnabled() == false){
            mIsStarted = false;
            return mIsStarted;
        }
        if (!mIsStarted) {
            mIsStarted = true;

            mIsStarted = startBluetooth();
        }
        return mIsStarted;
    }

    /** * Should call this on onResume or onDestroy. Unregister broadcast receivers * and stop Sco audio connection and cancel count down. */
    public void stop() {
        if (mIsStarted) {
            mIsStarted = false;

            stopBluetooth();

        }
    }

    /** * * @return true if audio is connected through headset. */
    public boolean isOnHeadsetSco() {
        return mIsOnHeadsetSco;
    }

    public abstract void onHeadsetDisconnected();

    public abstract void onHeadsetConnected();

    public abstract void onScoAudioDisconnected();

    public abstract void onScoAudioConnected();


    /** * Register a headset profile listener * * @return false if device does not support bluetooth or current platform * does not supports use of SCO for off call or error in getting * profile proxy. */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private boolean startBluetooth() {

        // Device support bluetooth
        if (mBluetoothAdapter != null) {
            if (mAudioManager.isBluetoothScoAvailableOffCall()) {
                // All the detection and audio connection are done in
                // mHeadsetProfileListener
                if (mBluetoothAdapter.getProfileProxy(mContext, mHeadsetProfileListener, BluetoothProfile.HEADSET)) {
                    return true;
                }
            }
        }

        return false;
    }

    /** * API >= 11 Unregister broadcast receivers and stop Sco audio connection * and cancel count down. */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    protected void stopBluetooth() {

        if (mBluetoothHeadset != null) {
            // Need to call stopVoiceRecognition here when the app
            // change orientation or close with headset still turns on.
            mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
            mContext.unregisterReceiver(mHeadsetBroadcastReceiver);
            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
            mBluetoothHeadset = null;
        }
    }





    /** * API >= 11 Check for already connected headset and if so start audio * connection. Register for broadcast of headset and Sco audio connection * states. */
    private BluetoothProfile.ServiceListener mHeadsetProfileListener = new BluetoothProfile.ServiceListener() {

        /** * This method is never called, even when we closeProfileProxy on * onPause. When or will it ever be called??? */
        @Override
        public void onServiceDisconnected(int profile) {
            stopBluetooth();
        }

        @SuppressWarnings("synthetic-access")
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {

            // mBluetoothHeadset is just a headset profile,
            // it does not represent a headset device.
            mBluetoothHeadset = (BluetoothHeadset) proxy;

            // If a headset is connected before this application starts,
            // ACTION_CONNECTION_STATE_CHANGED will not be broadcast.
            // So we need to check for already connected headset.
            List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
            if (devices.size() > 0) {
                // Only one headset can be connected at a time,
                // so the connected headset is at index 0.
                mConnectedHeadset = devices.get(0);

                onHeadsetConnected();

            }

            // During the active life time of the app, a user may turn on and
            // off the headset.
            // So register for broadcast of connection states.
            mContext.registerReceiver(mHeadsetBroadcastReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
            // Calling startVoiceRecognition does not result in immediate audio
            // connection.
            // So register for broadcast of audio connection states. This
            // broadcast will
            // only be sent if startVoiceRecognition returns true.
            mContext.registerReceiver(mHeadsetBroadcastReceiver, new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
        }
    };

    /** * API >= 11 Handle headset and Sco audio connection states. */
    private BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver() {

        @SuppressWarnings("synthetic-access")
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            int state;
            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
                if (state == BluetoothHeadset.STATE_CONNECTED) {
                    mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                    // Calling startVoiceRecognition always returns false here,
                    // that why a count down timer is implemented to call
                    // startVoiceRecognition in the onTick.


                    // override this if you want to do other thing when the
                    // device is connected.
                    onHeadsetConnected();

                } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {

                    mConnectedHeadset = null;

                    // override this if you want to do other thing when the
                    // device is disconnected.
                    onHeadsetDisconnected();

                }
            } else // audio
            {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
                    // override this if you want to do other thing when headset
                    // audio is connected.
                    onScoAudioConnected();
                } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                    mIsOnHeadsetSco = false;

                    // override this if you want to do other thing when headset
                    // audio is disconnected.
                    onScoAudioDisconnected();

                }
            }
        }
    };
}
public class BluetoothHelper extends BluetoothHeadsetUtils {
    private final static String TAG = BluetoothHelper.class.getSimpleName();
    Context mContext;
    int mCallvol;
// int mMediaVol;
    AudioManager mAudioManager;
    public BluetoothHelper(Context context) {
        super(context);
        mContext = context;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        mCallvol = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
// mMediaVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    }

    @Override
    public void onHeadsetDisconnected() {
        mAudioManager.setBluetoothScoOn(false);
    }

    @Override
    public void onHeadsetConnected() {
        mAudioManager.setBluetoothScoOn(true); // 打开SCO
// mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
    }

    @Override
    public void onScoAudioDisconnected() {
        mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, mCallvol, 0);
// mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mMediaVol, 0);
    }

    @Override
    public void onScoAudioConnected() {
        mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL), 0);
        // mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
    }

}

有关蓝牙协议的部分,由于Google官方并未提供更多API支持,故了解即可。

你可能感兴趣的:(android,socket,蓝牙,BlueTooth)