Android 跟蓝牙耳机建立连接有两种方式。 

1. Android 主动跟蓝牙耳机连BluetoothSettings 中和蓝牙耳机配对上之后, BluetoothHeadsetService 会收到BONDING_CREATED_ACTION,这个时候BluetoothHeadsetService 会主动去和蓝牙耳机建立RFCOMM 连接。
if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
if (mState == BluetoothHeadset.STATE_DISCONNECTED) {
// Lets try and initiate an RFCOMM connection
try {
mBinder.connectHeadset(address, null);
} catch (RemoteException e) {}
}
} RFCOMM 连接的真正实现是在ConnectionThread 中,它分两步,第一步先通过SDPClient 查询蓝牙设备时候支持Headset 和Handsfree profile。
// 1) SDP query
SDPClient client = SDPClient.getSDPClient(address);
if (DBG) log("Connecting to SDP server (" + address + ")...");
if (!client.connectSDPAsync()) {
Log.e(TAG, "Failed to start SDP connection to " + address);
mConnectingStatusHandler.obtainMessage(SDP_ERROR).sendToTarget();
client.disconnectSDP();
return;
}
if (isInterrupted()) {
client.disconnectSDP();
return;
}
if (!client.waitForSDPAsyncConnect(20000)) { // 20 secs
if (DBG) log("Failed to make SDP connection to " + address);
mConnectingStatusHandler.obtainMessage(SDP_ERROR).sendToTarget();
client.disconnectSDP();
return;
}
if (DBG) log("SDP server connected (" + address + ")");
int headsetChannel = client.isHeadset();
if (DBG) log("headset channel = " + headsetChannel);
int handsfreeChannel = client.isHandsfree();
if (DBG) log("handsfree channel = " + handsfreeChannel);
client.disconnectSDP();
第2步才是去真正建立RFCOMM 连接。
// 2) RFCOMM connect mHeadset = new HeadsetBase(mBluetooth, address, channel);
if (isInterrupted()) {
return;
}
int result = mHeadset.waitForAsyncConnect(20000, // 20 secs
mConnectedStatusHandler);
if (DBG) log("Headset RFCOMM connection attempt took " +(System.currentTimeMillis() - timestamp) + " ms");
if (isInterrupted()) {
return;
}
if (result < 0) {
Log.e(TAG, "mHeadset.waitForAsyncConnect() error: " + result);
mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
return;
} else if (result == 0) {
Log.e(TAG, "mHeadset.waitForAsyncConnect() error: " + result +"(timeout)");
mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
return;
} else {
if (DBG) log("mHeadset.waitForAsyncConnect() success");
mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED).sendToTarget();
}
当RFCOMM连接成功建立后,BluetoothHeadsetDevice 会收到RFCOMM_CONNECTED消息,它会调用BluetoothHandsfree 来建立SCO 连接,广播通知Headset状态变化的Intent
(PhoneApp 和BluetoothSettings 会接收这个Intent)。
case RFCOMM_CONNECTED:
// success
if (DBG) log("Rfcomm connected");
if (mConnectThread != null) {
try {
mConnectThread.join();
} catch (InterruptedException e) {
Log.w(TAG, "Connect attempt cancelled, ignoring
RFCOMM_CONNECTED", e);
return;
}
mConnectThread = null;
}
setState(BluetoothHeadset.STATE_CONNECTED,BluetoothHeadset.RESULT_SUCCESS);
mBtHandsfree.connectHeadset(mHeadset, mHeadsetType);
break;
BluetoothHandsfree 会先做一些初始化工作,比如根据是Headset 还是Handsfree 初始化不同的ATParser,并且启动一个接收线程从已建立的RFCOMM上接收蓝牙耳机过来的控制命令(也就是AT 命令),接着判断如果是在打电话过程中,才去建立SCO 连接来打通数据通道。
/* package */
void connectHeadset(HeadsetBase headset, int headsetType) {
mHeadset = headset;
mHeadsetType = headsetType;
if (mHeadsetType == TYPE_HEADSET) {
initializeHeadsetAtParser();
} else {
initializeHandsfreeAtParser();
}
headset.startEventThread();
configAudioParameters();
if (inDebug()) {
startDebug();
}
if (isIncallAudio()) {
audioOn();
}
}
建立SCO 连接是通过SCOSocket 实现的
/** Request to establish SCO (audio) connection to bluetooth
* headset/handsfree, if one is connected. Does not block.
* Returns false if the user has requested audio off, or if there
* is some other immediate problem that will prevent BT audio.
*/
/* package */
synchronized boolean audioOn() {
mOutgoingSco = createScoSocket();
if (!mOutgoingSco.connect(mHeadset.getAddress())) {
mOutgoingSco = null;
}
return true;
}
当SCO 连接成功建立后,BluetoothHandsfree 会收到SCO_CONNECTED 消息,它就会去调用AudioManager 的setBluetoothScoOn函数,从而通知音频系统有个蓝牙耳机可用了。
到此,Android 完成了和蓝牙耳机的全部连接。
case SCO_CONNECTED:
if (msg.arg1 == ScoSocket.STATE_CONNECTED && isHeadsetConnected()&&mConnectedSco == null) {
if (DBG) log("Routing audio for outgoing SCO conection");
mConnectedSco = (ScoSocket)msg.obj;
mAudioManager.setBluetoothScoOn(true);
} else if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
if (DBG) log("Rejecting new connected outgoing SCO socket");
((ScoSocket)msg.obj).close();
mOutgoingSco.close();
}
mOutgoingSco = null;
break;
2. 蓝牙耳机主动跟Android 连首先BluetoothAudioGateway 会在一个线程中收到来自蓝牙耳机的RFCOMM 连接,然后发送消息给BluetoothHeadsetService。
mConnectingHeadsetRfcommChannel = -1;
mConnectingHandsfreeRfcommChannel = -1;
if(waitForHandsfreeConnectNative(SELECT_WAIT_TIMEOUT) == false) {
if (mTimeoutRemainingMs > 0) {
try {
Log.i(tag, "select thread timed out, but " +
mTimeoutRemainingMs + "ms of
waiting remain.");
Thread.sleep(mTimeoutRemainingMs);
} catch (InterruptedException e) {
Log.i(tag, "select thread was interrupted (2),
exiting");
mInterrupted = true;
}
}
} BluetoothHeadsetService 会根据当前的状态来处理消息,分3 种情况,第一是当前状态是非连接状态,会发送RFCOMM_CONNECTED 消息,后续处理请参见前面的分析。
case BluetoothHeadset.STATE_DISCONNECTED:
// headset connecting us, lets join
setState(BluetoothHeadset.STATE_CONNECTING);
mHeadsetAddress = info.mAddress;
mHeadset = new HeadsetBase(mBluetooth, mHeadsetAddress,info.mSocketFd,info.mRfcommChan,mConnectedStatusHandler);
mHeadsetType = type;
mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED).sendToTarget();
break;
如果当前是正在连接状态, 则先停掉已经存在的ConnectThread,并直接调用BluetoothHandsfree 去建立SCO 连接。
case BluetoothHeadset.STATE_CONNECTING:
// If we are here, we are in danger of a race condition
// incoming rfcomm connection, but we are also attempting an
// outgoing connection. Lets try and interrupt the outgoing
// connection.
mConnectThread.interrupt();
// Now continue with new connection, including calling callback
mHeadset = new HeadsetBase(mBluetooth,mHeadsetAddress,info.mSocketFd,info.mRfcommChan,mConnectedStatusHandler);
mHeadsetType = type;
setState(BluetoothHeadset.STATE_CONNECTED,BluetoothHeadset.RESULT_SUCCESS);
mBtHandsfree.connectHeadset(mHeadset,mHeadsetType);
// Make sure that old outgoing connect thread is dead.
break;
如果当前是已连接的状态,这种情况是一种错误case,所以直接断掉所有连接。
case BluetoothHeadset.STATE_CONNECTED:
if (DBG) log("Already connected to " + mHeadsetAddress + ",disconnecting" +info.mAddress);
mBluetooth.disconnectRemoteDeviceAcl(info.mAddress);
break;
蓝牙耳机也可能会主动发起SCO 连接, BluetoothHandsfree 会接收到一个SCO_ACCEPTED消息,它会去调用AudioManager 的setBluetoothScoOn 函数,从而通知音频系统有个蓝牙耳机可用了。到此,蓝牙耳机完成了和Android 的全部连接。
case SCO_ACCEPTED:
if (msg.arg1 == ScoSocket.STATE_CONNECTED) {
if (isHeadsetConnected() && mAudioPossible && mConnectedSco ==null) {
Log.i(TAG, "Routing audio for incoming SCO connection");
mConnectedSco = (ScoSocket)msg.obj;
mAudioManager.setBluetoothScoOn(true);
} else {
Log.i(TAG, "Rejecting incoming SCO connection");
((ScoSocket)msg.obj).close();
}
} // else error trying to accept, try again
mIncomingSco = createScoSocket();
mIncomingSco.accept();
break;

Android跟蓝牙耳机建立连接有两种方式的更多相关文章

  1. 【Android】adb connect 手机的两种方式

    adb支持两种连接Android系统的方式,USB方式及网络方式.一般android手机及android平板默认会设置为USB方式(直接插数据线的方式). 下边介绍两种方式的切换方式. 1. 背景知识 ...

  2. Android Activity返回键控制的两种方式

    Android Activity返回键监听的两种方式 1.覆写Activity的OnBackPressed方法 官方解释: Called when the activity has detected ...

  3. Android提交数据到服务器的两种方式四种方法

    本帖最后由 yanghe123 于 2012-6-7 09:58 编辑 Android应用开发中,会经常要提交数据到服务器和从服务器得到数据,本文主要是给出了利用http协议采用HttpClient方 ...

  4. web.config中配置数据库(多数据)连接的两种方式

    这是我的第一篇文章,既然是第一篇了,那就从最基础的只是说起--web.config中配置数据库连接. 网上有很多这方面的资料,但发现并没有一篇从头到位很清楚明了说完的,今天就把我的整理写在这里吧. 在 ...

  5. erl0007 - erlang 远程节点连接的两种方式

    启动连接:erl -setcookie abc -name xxx@192.168.x.x -remsh xxx@192.168.x.y 退出:ctrl + g,q 参考:http://www.cnb ...

  6. android studio学习---签名打包的两种方式

    注:给我们自己开发的app签名,就代表着我自己的版权,以后要进行升级,也必须要使用相同的签名才行.签名就代表着自己的身份(即keystore),多个app可以使用同一个签名. 如果不知道签名是啥意思, ...

  7. Android 监听wifi广播的两种方式

    1.XML中声明 <receiver android:name=".NetworkConnectChangedReceiver" >             <i ...

  8. Android之发送短信的两种方式

    SMS涉及的主要类SmsManager 实现SMS主要用到SmsManager类,该类继承自java.lang.Object类,下面我们介绍一下该类的主要成员. 公有方法: ArrayList< ...

  9. Android更新主线程UI的两种方式handler与runOnUiThread()

    在android开发过程中,耗时操作我们会放在子线程中去执行,而更新UI是要主线程(也叫做:UI线程)来更新的,自然会遇到如何更新主线程UI的问题.如果在主线程之外的线程中直接更新页面显示常会报错.抛 ...

随机推荐

  1. [转载]Android 知识图谱

    from: http://blog.csdn.net/xyz_lmn/article/details/41411355

  2. android TDD平台插入双卡时,查看允许返回发送报告的选项,去掉勾选,不起作用

    请在MultiSimPreferenceActivity.java 下修改 修改1: 函数 isChecked()     private boolean isChecked(String prefe ...

  3. 全栈JavaScript之路(七)学习 Comment 类型节点.

    凝视 在DOM中,用 Comment 类型 节点表示, 构造器函数为:  function Comment(){[native code]}. comment 节点的特征: nodeType:8 no ...

  4. UVAlive 2326 Moving Tables(贪心 + 区间问题)

    The famous ACM (Advanced Computer Maker) Company has rented a floor of a building whose shape is in ...

  5. 键盘码、ASCII码表

    转载原文:http://www.cnblogs.com/knowledgesea/archive/2012/05/19/2508683.html ASCII码表 ASCII值 控制字符 ASCII值 ...

  6. C#中泛型、程序集一些基本运用(Fifteenth Day)

    今天主要在学习了泛型和程序集以及一些细碎的知识的运用.下面我就把今天所学的总结一下. 理论: 泛型: * 英文名字是Generic,可以让多个类型共享一组代码,泛型允许我们声明类型参数化,可以用不同的 ...

  7. 第10季asp.net基础

    什么是ASP.Net: ASP.Net是一种动态网页技术,在服务器端运行.Net代码,动态生成HTML.可以使用javascript.Dom在浏览器端完成很多工作,但是有很多工作无法在浏览器端完成,比 ...

  8. [译]Stairway to Integration Services Level 4 - 增量更新数据

    在本文中, 我们说下增量更新数据:即将数据源中更新了的数据替换掉目标表中对应的数据. 更新代码 操作之前我们先把目标表e (dbo.Contact). 的数据改掉 Use AdventureWorks ...

  9. BZOJ 3293 分金币

    整体来说,这道题与之前做的1045题目完全一样,出了说法不一样外,思路及做法可以照搬,因此在这里便不再详解.        程序如下:(如有疑问请参看我的博客http://www.cnblogs.co ...

  10. NSArray的4种遍历方式

    前言:NSArray对应的是java的List,不同的是其元素不能更改,不过其派生类NSMutableArray可以更改,遍历的方式跟java的List基本一样 一.  for循环 Student * ...