Android(安卓)蓝牙( Bluetooth)耳机连接分析及实现
16lz
2022-01-14
- Android实现了对Headset和Handsfree两种profile的支持。其实现核心是BluetoothHeadsetService,在PhoneApp创建的时候会启动它。
- if(getSystemService(Context.BLUETOOTH_SERVICE)!=null){
- mBtHandsfree=newBluetoothHandsfree(this,phone);
- startService(newIntent(this,BluetoothHeadsetService.class));
- }else{
- //Deviceisnotbluetoothcapable
- mBtHandsfree=null;
- }
- BluetoothHeadsetService通过接收ENABLED_ACTION、BONDING_CREATED_ACTION、DISABLED_ACTION和REMOTE_DEVICE_DISCONNECT_REQUESTEDACTION来改变状态,它也会监听Phone的状态变化。
- IntentFilterfilter=newIntentFilter(BluetoothIntent.BONDING_CREATED_ACTION);
- filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
- filter.addAction(BluetoothIntent.ENABLED_ACTION);
- filter.addAction(BluetoothIntent.DISABLED_ACTION);
- registerReceiver(mBluetoothIntentReceiver,filter);
- mPhone.registerForPhoneStateChanged(mStateChangeHandler,PHONE_STATE_CHANGED,null);
- BluetoothHeadsetService收到ENABLED_ACTION时,会先向BlueZ注册Headset和Handsfree两种profile(通过执行sdptool来实现的,均作为AudioGateway),然后让BluetoothAudioGateway接收RFCOMM连接,让BluetoothHandsfree接收SCO连接(这些操作都是为了让蓝牙耳机能主动连上Android)。
- if(action.equals(BluetoothIntent.ENABLED_ACTION)){
- //SDPservermaynotbeready,sowait3secondsbefore
- //registeringrecords.
- //TODO:UseadifferentmechanismtoregisterSDPrecords,
- //thatactuallyACK’sonsuccess,sothatwecanretryrather
- //thanhardcodinga3secondguess.
- mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS),3000);
- mAg.start(mIncomingConnectionHandler);
- mBtHandsfree.onBluetoothEnabled();
- }
- BluetoothHeadsetService收到DISABLED_ACTION时,会停止BluetoothAudioGateway和BluetoothHandsfree。
- if(action.equals(BluetoothIntent.DISABLED_ACTION)){
- mBtHandsfree.onBluetoothDisabled();
- mAg.stop();
- }
- Android跟蓝牙耳机建立连接有两种方式。
- 1.Android主动跟蓝牙耳机连BluetoothSettings中和蓝牙耳机配对上之后,BluetoothHeadsetService会收到BONDING_CREATED_ACTION,这个时候BluetoothHeadsetService会主动去和蓝牙耳机建立RFCOMM连接。
- if(action.equals(BluetoothIntent.BONDING_CREATED_ACTION)){
- if(mState==BluetoothHeadset.STATE_DISCONNECTED){
- //LetstryandinitiateanRFCOMMconnection
- try{
- mBinder.connectHeadset(address,null);
- }catch(RemoteExceptione){}
- }
- }
- RFCOMM连接的真正实现是在ConnectionThread中,它分两步,第一步先通过SDPClient查询蓝牙设备时候支持Headset和Handsfreeprofile。
- //1)SDPquery
- SDPClientclient=SDPClient.getSDPClient(address);
- if(DBG)log(”ConnectingtoSDPserver(”+address+“)…”);
- if(!client.connectSDPAsync()){
- Log.e(TAG,“FailedtostartSDPconnectionto”+address);
- mConnectingStatusHandler.obtainMessage(SDP_ERROR).sendToTarget();
- client.disconnectSDP();
- return;
- }
- if(isInterrupted()){
- client.disconnectSDP();
- return;
- }
- if(!client.waitForSDPAsyncConnect(20000)){//20secs
- if(DBG)log(”FailedtomakeSDPconnectionto”+address);
- mConnectingStatusHandler.obtainMessage(SDP_ERROR).sendToTarget();
- client.disconnectSDP();
- return;
- }
- if(DBG)log(”SDPserverconnected(”+address+“)”);
- intheadsetChannel=client.isHeadset();
- if(DBG)log(”headsetchannel=”+headsetChannel);
- inthandsfreeChannel=client.isHandsfree();
- if(DBG)log(”handsfreechannel=”+handsfreeChannel);
- client.disconnectSDP();
- 第2步才是去真正建立RFCOMM连接。
- //2)RFCOMMconnect
- mHeadset=newHeadsetBase(mBluetooth,address,channel);
- if(isInterrupted()){
- return;
- }
- intresult=mHeadset.waitForAsyncConnect(20000,//20secs
- mConnectedStatusHandler);
- if(DBG)log(”HeadsetRFCOMMconnectionattempttook”+(System.currentTimeMillis()–timestamp)+”ms”);
- if(isInterrupted()){
- return;
- }
- if(result<0){
- Log.e(TAG,“mHeadset.waitForAsyncConnect()error:”+result);
- mConnectingStatusHandler.obtainMessage(RFCOMM_ERROR).sendToTarget();
- return;
- }elseif(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)。
- caseRFCOMM_CONNECTED:
- //success
- if(DBG)log(”Rfcommconnected”);
- if(mConnectThread!=null){
- try{
- mConnectThread.join();
- }catch(InterruptedExceptione){
- Log.w(TAG,“Connectattemptcancelled,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*/
- voidconnectHeadset(HeadsetBaseheadset,intheadsetType){
- mHeadset=headset;
- mHeadsetType=headsetType;
- if(mHeadsetType==TYPE_HEADSET){
- initializeHeadsetAtParser();
- }else{
- initializeHandsfreeAtParser();
- }
- headset.startEventThread();
- configAudioParameters();
- if(inDebug()){
- startDebug();
- }
- if(isIncallAudio()){
- audioOn();
- }
- }
- 建立SCO连接是通过SCOSocket实现的
- /**RequesttoestablishSCO(audio)connectiontobluetooth
- *headset/handsfree,ifoneisconnected.Doesnotblock.
- *Returnsfalseiftheuserhasrequestedaudiooff,orifthere
- *issomeotherimmediateproblemthatwillpreventBTaudio.
- */
- /*package*/
- synchronizedbooleanaudioOn(){
- mOutgoingSco=createScoSocket();
- if(!mOutgoingSco.connect(mHeadset.getAddress())){
- mOutgoingSco=null;
- }
- returntrue;
- }
- 当SCO连接成功建立后,BluetoothHandsfree会收到SCO_CONNECTED消息,它就会去调用AudioManager的setBluetoothScoOn函数,从而通知音频系统有个蓝牙耳机可用了。
- 到此,Android完成了和蓝牙耳机的全部连接。
- caseSCO_CONNECTED:
- if(msg.arg1==ScoSocket.STATE_CONNECTED&&isHeadsetConnected()&&mConnectedSco==null){
- if(DBG)log(”RoutingaudioforoutgoingSCOconection”);
- mConnectedSco=(ScoSocket)msg.obj;
- mAudioManager.setBluetoothScoOn(true);
- }elseif(msg.arg1==ScoSocket.STATE_CONNECTED){
- if(DBG)log(”RejectingnewconnectedoutgoingSCOsocket”);
- ((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,“selectthreadtimedout,but”+
- mTimeoutRemainingMs+“msof
- waitingremain.”);
- Thread.sleep(mTimeoutRemainingMs);
- }catch(InterruptedExceptione){
- Log.i(tag,“selectthreadwasinterrupted(2),
- exiting”);
- mInterrupted=true;
- }
- }
- }
- BluetoothHeadsetService会根据当前的状态来处理消息,分3种情况,第一是当前状态是非连接状态,会发送RFCOMM_CONNECTED消息,后续处理请参见前面的分析。
- caseBluetoothHeadset.STATE_DISCONNECTED:
- //headsetconnectingus,letsjoin
- setState(BluetoothHeadset.STATE_CONNECTING);
- mHeadsetAddress=info.mAddress;
- mHeadset=newHeadsetBase(mBluetooth,mHeadsetAddress,info.mSocketFd,info.mRfcommChan,mConnectedStatusHandler);
- mHeadsetType=type;
- mConnectingStatusHandler.obtainMessage(RFCOMM_CONNECTED).sendToTarget();
- break;
- 如果当前是正在连接状态,则先停掉已经存在的ConnectThread,并直接调用BluetoothHandsfree去建立SCO连接。
- caseBluetoothHeadset.STATE_CONNECTING:
- //Ifwearehere,weareindangerofaracecondition
- //incomingrfcommconnection,butwearealsoattemptingan
- //outgoingconnection.Letstryandinterrupttheoutgoing
- //connection.
- mConnectThread.interrupt();
- //Nowcontinuewithnewconnection,includingcallingcallback
- mHeadset=newHeadsetBase(mBluetooth,mHeadsetAddress,info.mSocketFd,info.mRfcommChan,mConnectedStatusHandler);
- mHeadsetType=type;
- setState(BluetoothHeadset.STATE_CONNECTED,BluetoothHeadset.RESULT_SUCCESS);
- mBtHandsfree.connectHeadset(mHeadset,mHeadsetType);
- //Makesurethatoldoutgoingconnectthreadisdead.
- break;
- 如果当前是已连接的状态,这种情况是一种错误case,所以直接断掉所有连接。
- caseBluetoothHeadset.STATE_CONNECTED:
- if(DBG)log(”Alreadyconnectedto”+mHeadsetAddress+“,disconnecting”+info.mAddress);
- mBluetooth.disconnectRemoteDeviceAcl(info.mAddress);
- break;
- 蓝牙耳机也可能会主动发起SCO连接,BluetoothHandsfree会接收到一个SCO_ACCEPTED消息,它会去调用AudioManager的setBluetoothScoOn函数,从而通知音频系统有个蓝牙耳机可用了。到此,蓝牙耳机完成了和Android的全部连接。
- caseSCO_ACCEPTED:
- if(msg.arg1==ScoSocket.STATE_CONNECTED){
- if(isHeadsetConnected()&&mAudioPossible&&mConnectedSco==null){
- Log.i(TAG,“RoutingaudioforincomingSCOconnection”);
- mConnectedSco=(ScoSocket)msg.obj;
- mAudioManager.setBluetoothScoOn(true);
- }else{
- Log.i(TAG,“RejectingincomingSCOconnection”);
- ((ScoSocket)msg.obj).close();
- }
- }//elseerrortryingtoaccept,tryagain
- mIncomingSco=createScoSocket();
- mIncomingSco.accept();
- break;
更多相关文章
- android SIM卡状态
- android时序图 以及UML中时序图、流程图、状态图、协作图之间的
- Android(安卓)电源管理
- android开源框架源码分析:Okhttp
- Android(安卓)Process 优先级
- android sim卡 TelephonyManager类:Android手机及Sim卡状态的获取
- android检查网络连接状态的变化,无网络时跳转到设置界面
- 聊聊Android切图
- Android(安卓)shape使用