蓝牙(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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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通知刷新ListViewmAdapter.notifyDataSetChanged();}

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

mListView.setOnItemClickListener(bindDeviceClick);
private AdapterView.OnItemClickListener bindDeviceClick = new AdapterView.OnItemClickListener() {@Overridepublic 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())}}

蓝牙传输数据



在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 {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {//开始监听时,接收由子线程发来的消息case Constant.MSG_START_LISTENING://更新UI    setProgressBarIndeterminateVisibility(true);break;//结束监听时,接收由子线程发来的消息case Constant.MSG_FINISH_LISTENING://更新UIsetProgressBarIndeterminateVisibility(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 streamint 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() {@Overridepublic 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 finalBluetoothSocket 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 bluetoothif (mBluetoothAdapter != null) {if (mAudioManager.isBluetoothScoAvailableOffCall()) {// All the detection and audio connection are done in// mHeadsetProfileListenerif (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???*/@Overridepublic void onServiceDisconnected(int profile) {stopBluetooth();}@SuppressWarnings("synthetic-access")@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overridepublic 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)@Overridepublic 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);}@Overridepublic void onHeadsetDisconnected() {mAudioManager.setBluetoothScoOn(false);}@Overridepublic void onHeadsetConnected() {mAudioManager.setBluetoothScoOn(true); // 打开SCO
//      mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);}@Overridepublic void onScoAudioDisconnected() {mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, mCallvol, 0);
//      mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mMediaVol, 0);}@Overridepublic 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蓝牙开发介绍相关推荐

  1. Android 蓝牙开发(一) -- 传统蓝牙聊天室

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 一.蓝牙概览 以下是蓝牙的介绍,来自维基百科: 蓝牙(英语: ...

  2. Android蓝牙开发教程(二)——连接蓝牙设备

    在上一篇中已经介绍如何搜索附近可连接的蓝牙设备,如果你还没阅读过,建议先看看上一篇文章Android蓝牙开发教程(一)--搜索蓝牙设备 获取到设备后就可以开始处理蓝牙设备之间的连接. 在上一篇教程中我 ...

  3. Android蓝牙开发—经典蓝牙详细开发流程

    文章目录 开发流程 权限 核心API BlueToothAdapter getDefaultAdapter():获取BluetoothAdapter对象 判断设备是否支持蓝牙 判断蓝牙是否开启 get ...

  4. Android蓝牙开发BLE-读写数据

    上一篇:Android蓝牙开发BLE-蓝牙连接 上一篇写了BluetoothGattCallback的一个方法,判断了蓝牙是否连上.他还有好几个方法,读写就是在这里面进行,读写操作都在Bluetoot ...

  5. Android蓝牙开发系列文章-蓝牙音箱连接

    经过一段时间的折腾,我的Android Studio终于可以正常工作了,期间遇到的坑记录在了文章<创建Android Studio 3.5第一个工程遇到的坑>. 我们在<Androi ...

  6. Android多媒体开发介绍

    Android多媒体开发介绍 一.       多媒体架构 基于第三方PacketVideo公司的OpenCORE来实现,支持所有通用的音频/视频/静态图像格式,包括:MPEG4.H.264.MP3. ...

  7. Android - 蓝牙开发

    文章目录 科普 SIG 类型 制式 选择 逻辑链路控制适配协议 (L2CAP) L2CAP的功能 蓝牙框架和 RFCOMM 协议 蓝牙安全 白名单机制 编程 蓝牙权限 Classic Bluetoot ...

  8. Android蓝牙开发(一)蓝牙模块及核心API

    本文主要介绍Android蓝牙开发中基础知识:蓝牙模块及核心API. 关于蓝牙的连接及通讯功能实现,欢迎查阅下一篇文章:Android蓝牙开发(二)蓝牙消息传输实现. 蓝牙模块 从蓝牙4.0开始包含两 ...

  9. Android 蓝牙开发,申请打开蓝牙

    申请打开蓝牙 <!-- 蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH" ...

最新文章

  1. ios 绘制不规则 图形
  2. c# webBrowser打开pdf问题
  3. 多少个没收到会收敛_做多少个俯卧撑算是合格?坚持做俯卧撑,会有什么变化?...
  4. reactjs npm start运行报错:Error: ENOSPC: System limit for number of file watchers reached
  5. thinkphp5如何使用layout
  6. 平安 开源 数据库 实践_刻意的实践-成为开源
  7. iOS Social框架
  8. oracle trace 文件名,限制oracle trace 文件大小
  9. json介绍及简单示例
  10. Android快速开发(2)
  11. yousa_team团队项目——兼职平台网站 工作进度
  12. 图书管理系统(Java实现,十个数据表,含源码、ER图,超详细报告解释,2020.7.11更新)...
  13. 【Pyecharts50例】GEO航线图/lines
  14. 网络协议 终章 - GTP 协议:复杂的移动网络
  15. CF#552div3题解
  16. GIS招聘 | 江西省直事业单位(含测绘、地信等专业岗位)
  17. 从fastq生成vcf文件
  18. Baby-Step-Giant-Step算法
  19. 谷歌浏览器windows以及mac系统下设置跨域
  20. markdown文档插入表情符号支持的英文单词大全 官方

热门文章

  1. MonoRail - 简介
  2. sublime运行python只显示时间_sublime3-python 编译运行不显示内容
  3. 如何建立自己的知识体系?
  4. 设计模式精讲(重点难点理解说明)---什么是设计模式?
  5. win10彻底永久关闭自动更新的方法【已验证有效】
  6. 二叉树的基本操作的实现
  7. 技术类编程题汇总 C++ 刷题记录
  8. Win10+vs2017跑yolov3
  9. 同网段能互通,跨网段不通
  10. 多行输入以及结束输入