IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 9.0 蓝牙通讯录 BluetoothPbapClient -> 正文阅读

[移动开发]Android 9.0 蓝牙通讯录 BluetoothPbapClient

蓝牙通讯录主要包含联系人和通话记录
一、BluetoothPbapClient功能介绍
1、主要实现电话簿下载
2、电话号码簿访问协议(Phonebook Access Profile)
二、BluetoothPbapClient的使用

public void getProfileProxy() {
   boolean isPbapService = mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.PBAP_CLIENT);
    Log.i(TAG, "getProfileProxy"+isPbapService);
}
private final class ProxyServiceListener implements BluetoothProfile.ServiceListener{

   @Override
   public void onServiceConnected(int profile, BluetoothProfile proxy) {
      Log.d(TAG,"Bluetooth service connected profile == " + profile);
      if (profile == BluetoothProfile.PBAP_CLIENT) {
         mPbapClient = (BluetoothPbapClient) proxy;
         isPbapProfileReady = true;
      }
   }

   @Override
   public void onServiceDisconnected(int profile) {
      Log.d(TAG, "BluetoothPbapClient Profile Proxy Disconnected");
      if (profile == BluetoothProfile.PBAP_CLIENT) {
         isPbapProfileReady = false;
         mPbapClient = null;
      }
   }
}

2、BluetoothPbapClient连接

// 连接
public boolean connect(BluetoothDevice device) {
   if (null != mPbapClient) {
      return mPbapClient.connect(device);
   }
   Log.i(TAG, "mPbapClient == null");
   return false;
}
//断连
public boolean disconnect(BluetoothDevice device) {
   if (null != mPbapClient) {
      return mPbapClient.disconnect(device);
   }
   Log.i(TAG, "mPbapClient == null");
   return false;
}
//判断连接状态
public int getConnectionState() {
   if (null != mPbapClient) {
      List<BluetoothDevice> deviceList = mPbapClient.getConnectedDevices();
      if (deviceList.isEmpty()) {
         return BluetoothProfile.STATE_DISCONNECTED;
      } else {
         return mPbapClient.getConnectionState(deviceList.remove(0));
      }
   }
   return BluetoothProfile.STATE_DISCONNECTED;
}

三、源码分析
代码路径:

android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientService.java

1、在PbapClientStateMachine.java文件中内部类Connecting类中,当我们连接蓝牙后就会执行Connecting类中enter()方法,并且也会注册该广播android.bluetooth.device.action.SDP_RECORD

class Connecting extends State {
    private SDPBroadcastReceiver mSdpReceiver;

    @Override
    public void enter() {
        if (DBG) {
            Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
        }
        onConnectionStateChanged(mCurrentDevice, mMostRecentState,
                BluetoothProfile.STATE_CONNECTING);
        mSdpReceiver = new SDPBroadcastReceiver();
        mSdpReceiver.register();
        mCurrentDevice.sdpSearch(BluetoothUuid.PBAP_PSE);
        mMostRecentState = BluetoothProfile.STATE_CONNECTING;

        // Create a separate handler instance and thread for performing
        // connect/download/disconnect operations as they may be time consuming and error prone.
        mHandlerThread =
                new HandlerThread("PBAP PCE handler", Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();
        mConnectionHandler =
                new PbapClientConnectionHandler.Builder().setLooper(mHandlerThread.getLooper())
                        .setContext(mService)
                        .setClientSM(PbapClientStateMachine.this)
                        .setRemoteDevice(mCurrentDevice)
                        .build();

        sendMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT);
    }

    @Override
    public boolean processMessage(Message message) {
        if (DBG) {
            Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
        }
        switch (message.what) {
            case MSG_DISCONNECT:
                if (message.obj instanceof BluetoothDevice && message.obj.equals(
                        mCurrentDevice)) {
                    removeMessages(MSG_CONNECT_TIMEOUT);
                    transitionTo(mDisconnecting);
                }
                break;

            case MSG_CONNECTION_COMPLETE:
                removeMessages(MSG_CONNECT_TIMEOUT);
                transitionTo(mConnected);
                break;

            case MSG_CONNECTION_FAILED:
            case MSG_CONNECT_TIMEOUT:
                removeMessages(MSG_CONNECT_TIMEOUT);
                transitionTo(mDisconnecting);
                break;

            case MSG_SDP_COMPLETE:
                mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_CONNECT,
                        message.obj).sendToTarget();
                break;

            default:
                Log.w(TAG, "Received unexpected message while Connecting");
                return NOT_HANDLED;
        }
        return HANDLED;
    }

    @Override
    public void exit() {
        mSdpReceiver.unregister();
        mSdpReceiver = null;
    }

    private class SDPBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DBG) {
                Log.v(TAG, "onReceive" + action);
            }
            if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
                BluetoothDevice device =
                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (!device.equals(getDevice())) {
                    Log.w(TAG, "SDP Record fetched for different device - Ignore");
                    return;
                }
                ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                if (DBG) {
                    Log.v(TAG, "Received UUID: " + uuid.toString());
                }
                if (DBG) {
                    Log.v(TAG, "expected UUID: " + BluetoothUuid.PBAP_PSE.toString());
                }
                if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
                    sendMessage(MSG_SDP_COMPLETE,
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD));
                }
            }
        }

        public void register() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
            mService.registerReceiver(this, filter);
        }

        public void unregister() {
            mService.unregisterReceiver(this);
        }
    }
}

2、蓝牙连接后会先通过socket去连接UUID

private boolean connectSocket() {
    try {
        /* Use BluetoothSocket to connect */
        if (mPseRec == null) {
            // BackWardCompatability: Fall back to create RFCOMM through UUID.
            Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
            mSocket =
                    mDevice.createRfcommSocketToServiceRecord(BluetoothUuid.PBAP_PSE.getUuid());
        } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
            Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
            mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
        } else {
            Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
            mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
        }

        if (mSocket != null) {
            mSocket.connect();
            return true;
        } else {
            Log.w(TAG, "Could not create socket");
        }
    } catch (IOException e) {
        Log.e(TAG, "Error while connecting socket", e);
    }
    return false;
}
3、蓝牙socket连接成功后就会去Obex Client Session
case MSG_CONNECT:
    mPseRec = (SdpPseRecord) msg.obj;
    /* To establish a connection, first open a socket and then create an OBEX session */
    if (connectSocket()) {
        if (DBG) {
            Log.d(TAG, "Socket connected");
        }
    } else {
        Log.w(TAG, "Socket CONNECT Failure ");
        mPbapClientStateMachine.obtainMessage(
                PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
        return;
    }

    if (connectObexSession()) {
        mPbapClientStateMachine.obtainMessage(
                PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
    } else {
        mPbapClientStateMachine.obtainMessage(
                PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
    }
    break;
    
    

4、Obex Client Session连接

private boolean connectObexSession() {
    boolean connectionSuccessful = false;

    try {
        if (DBG) {
            Log.v(TAG, "Start Obex Client Session");
        }
        BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
        mObexSession = new ClientSession(transport);
        mObexSession.setAuthenticator(mAuth);

        HeaderSet connectionRequest = new HeaderSet();
        connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);

        if (mPseRec != null) {
            if (DBG) {
                Log.d(TAG, "Remote PbapSupportedFeatures " + mPseRec.getSupportedFeatures());
            }

            ObexAppParameters oap = new ObexAppParameters();

            if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
                oap.add(BluetoothPbapRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES,
                        PBAP_SUPPORTED_FEATURE);
            }

            oap.addToHeaderSet(connectionRequest);
        }
        HeaderSet connectionResponse = mObexSession.connect(connectionRequest);

        connectionSuccessful =
                (connectionResponse.getResponseCode() == ResponseCodes.OBEX_HTTP_OK);
        if (DBG) {
            Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
        }
    } catch (IOException e) {
        Log.w(TAG, "CONNECT Failure " + e.toString());
        closeSocket();
    }
    return connectionSuccessful;
}

5、Obex Client Session连接成功后,就可以去下载手机端的联系人和通话记录

case MSG_DOWNLOAD:
    try {
        mAccountCreated = addAccount(mAccount);
        if (!mAccountCreated) {
            Log.e(TAG, "Account creation failed.");
            return;
        }
        // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
        BluetoothPbapRequestPullPhoneBook request =
                new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
                        PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
        request.execute(mObexSession);
        PhonebookPullRequest processor =
                new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
                        mAccount);
        processor.setResults(request.getList());
        processor.onPullComplete();
        HashMap<String, Integer> callCounter = new HashMap<>();
        downloadCallLog(MCH_PATH, callCounter);
        downloadCallLog(ICH_PATH, callCounter);
        downloadCallLog(OCH_PATH, callCounter);
    } catch (IOException e) {
        Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
    }
    break;

总结:
1、蓝牙连接后会先通过socket去连接UUID
2、socket连接上后会连接OBEX协议
3、OBEX协议连接上之后就可以去下载联系人和通话记录
连接上蓝牙后下载通讯录的日志

08-10 22:00:57.986  2629  4617 D PbapClientStateMachine: Enter Connecting: -2
08-10 22:00:57.987  2629  4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 0->1
08-10 22:01:00.372  2629  2629 V PbapClientStateMachine: onReceiveandroid.bluetooth.device.action.SDP_RECORD
08-10 22:01:00.372  2629  2629 V PbapClientStateMachine: Received UUID: 0000112f-0000-1000-8000-00805f9b34fb
08-10 22:01:00.373  2629  2629 V PbapClientStateMachine: expected UUID: 0000112f-0000-1000-8000-00805f9b34fb
08-10 22:01:00.373  2629  4617 D PbapClientStateMachine: Processing MSG 9 from Connecting
08-10 22:01:00.373  2629  4618 D PbapClientConnectionHandler: Handling Message = 1
08-10 22:01:00.374  2629  4618 V PbapClientConnectionHandler: connectSocket: channel: 19
08-10 22:01:00.674  2629  4618 D PbapClientConnectionHandler: Socket connected
08-10 22:01:00.674  2629  4618 V PbapClientConnectionHandler: Start Obex Client Session
08-10 22:01:00.677  2629  4618 D PbapClientConnectionHandler: Remote PbapSupportedFeatures 3
08-10 22:01:00.742  2629  4618 D PbapClientConnectionHandler: Success = true
08-10 22:01:00.742  2629  4617 D PbapClientStateMachine: Processing MSG 5 from Connecting
08-10 22:01:00.744  2629  4617 D PbapClientStateMachine: Enter Connected: 5
08-10 22:01:00.745  2629  4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 1->2
08-10 22:01:00.751  2629  4618 D PbapClientConnectionHandler: Handling Message = 3
08-10 22:01:00.798  2629  4618 D PbapClientConnectionHandler: Added account Account {name=54:92:09:12:BF:E9, type=com.android.bluetooth.pbapsink}

断开蓝牙后日志

08-10 22:42:09.088  2629  3393 D PbapClientStateMachine: Disconnect Request 54:92:09:12:BF:E9
08-10 22:42:09.088  2629  4617 D PbapClientStateMachine: Processing MSG 2 from Connected
08-10 22:42:09.088  2629  4617 D PbapClientStateMachine: Enter Disconnecting: 2
08-10 22:42:09.088  2629  4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 2->3
08-10 22:42:09.104  2629  4618 D PbapClientConnectionHandler: Handling Message = 2
08-10 22:42:09.104  2629  4618 D PbapClientConnectionHandler: Starting Disconnect
08-10 22:42:09.104  2629  4618 D PbapClientConnectionHandler: obexSessionDisconnectjavax.obex.ClientSession@c0dce50
08-10 22:42:09.679  2629  4618 D PbapClientConnectionHandler: Closing Socket
08-10 22:42:09.679  2629  4618 D PbapClientConnectionHandler: Closing socketandroid.bluetooth.BluetoothSocket@89d6849
08-10 22:42:09.680  2629  4618 D PbapClientConnectionHandler: Completing Disconnect
08-10 22:42:09.768  2629  4618 D PbapClientConnectionHandler: Removed account Account {name=54:92:09:12:BF:E9, type=com.android.bluetooth.pbapsink}
08-10 22:42:10.096  2629  4617 D PbapClientStateMachine: Processing MSG 7 from Disconnecting
08-10 22:42:10.097  2629  4617 D PbapClientStateMachine: Enter Disconnected: 7
08-10 22:42:10.097  2629  4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 3->0

遇到问题点:
1、当我们使用BluetoothPbapClient来访问连接状态时候,mPbapClient.connect一直返回是false,返回false的原因是因为:getPriority(device) <= BluetoothProfile.PRIORITY_OFF,该行代码返回false,返回false就导致不能同步手机端的联系人数据。
数据存放的路径:/data/user/0/com.android.providers.contacts/databases

//客户端
public boolean connect(BluetoothDevice device) {
   if (null != mPbapClient) {
      return mPbapClient.connect(device);
   }
   Log.i(TAG, "mPbapClient == null");
   return false;
}
//服务端
public boolean connect(BluetoothDevice device) {
    if (device == null) {
        throw new IllegalArgumentException("Null device");
    }
    enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
    Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
    Log.d(TAG, "Received request to ConnectPBAPPhonebook   getPriority(device) " + getPriority(device));
    Log.d(TAG, "Received request to ConnectPBAPPhonebook  BluetoothProfile.PRIORITY_OFF  " + BluetoothProfile.PRIORITY_OFF);
    if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
       // return false;
    }
    synchronized (mPbapClientStateMachineMap) {
        PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
        if (pbapClientStateMachine == null
                && mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) {
            pbapClientStateMachine = new PbapClientStateMachine(this, device);
            pbapClientStateMachine.start();
            mPbapClientStateMachineMap.put(device, pbapClientStateMachine);
            return true;
        } else {
            Log.w(TAG, "Received connect request while already connecting/connected.");
            return false;
        }
    }
}
//把该行代码注释掉就可以解决connect返回false的问题,这样就可以正常同步手机端的联系人
 if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
       // return false;
    }
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-13 12:13:24  更:2021-08-13 12:15:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 2:02:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码