公司用的是android8.1的源码,系统api有改动,改动的地方会稍微标明一下。我是在系统源码上开发的,所以有些类或者api@hide了 在开发工具上会报错,但是可以编译通过。如果是纯应用上层需要利用反射,有一部分功能需要移植代码。
车载蓝牙主要是实现蓝牙电话,蓝牙音乐,同步通讯录。这些功能都是用到蓝牙的配置文件协议。下面简单介绍一下这几个协议。
1.HFP(Hands-free Profile),让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。
2.A2DP(Advanced Audio Distribution Profile)是蓝牙的音频传输协议,典型应用为蓝牙耳机。A2DP协议的音频数据在ACL Link上传输。
3.AVRCP(Audio/Video Remote Control Profile)作用是支持CT控制TG,具体来说如果手机和一个蓝牙音箱设备连接上了,那么音箱可以控制手机播放/暂停/切歌以及获得手机上播放歌曲的信息,如专辑,歌名,歌手,时长等信息。
4.PBAP(Phone Book Access Profile)访问下载通讯录以及通话记录。
以上是简单介绍这些协议的用途,想具体了解可以分别取查资料,这方面资料很多。
一.打开蓝牙,查找蓝牙设备,连接蓝牙协议。
要使用蓝牙必须先在文件清单加权限
检测设备蓝牙是否打开,没有可以自动打开,也可以请求,给用户提示,让用户自己打开。
查找蓝牙之前可以先查看是否有以前绑定过的蓝牙设备
Set devices = mBluetoothAdapter.getBondedDevices();
if (devices != null && devices.size() > 0) {
for (Iterator it = devices.iterator(); it.hasNext();) {
BluetoothDevice device = it.next();
if (device != null) {
addBluetoothDevice(device);
}
}
}
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (!mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.enable();
}
查找蓝牙可以先查找低功耗蓝牙,一般查找时间为8-10秒,然后再查找经典蓝牙。两种查找用的api不同,先说一下查找低功耗蓝牙设备。
拿到BluetoothLeScanner:mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
调用startScan,参数为ScanCallback对象,它里面有各种回调,根据自己的需求实现。
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
BluetoothDevice device = result.getDevice();
Log.i(TAG, "scan succeed device == " +device);
addBluetoothDevice(device);
}
}
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
for (ScanResult result: results){
Log.i(TAG, "scan succeed device == " +result.getDevice());
addBluetoothDevice(result.getDevice());
}
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.i(TAG,"scan fail");
}
};
查看经典蓝牙直接使用BluetoothAdapter的startDiscovery方法,注册BluetoothDevice.ACTION_FOUND广播接收搜索到的蓝牙设备。使用intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)可以得到BluetoothDevice。
接着连接设备,连接设备之前需要绑定设备,就是确认双方设备的存在通过相同秘钥,如果需要访问通讯录,需要勾选权限。
可以先判断BluetoothDevice的状态,如果状态为BluetoothDevice.BOND_NONE,调用bluetoothDevice.createBond()进行绑定。
讲连接之前说一下自己踩过的坑,也不能说是坑,自己能力不足所以花了不少时间。开始我以为连接蓝牙需要socket通讯,查google文档说要 两台设备一台充当客户端一台充当服务端,然后用io流传输数据。我一直在纠结,我只能操作车载应用,用户手机安装不了我开发的应用,那这怎么连接呢。后来看到系统setting有连接蓝牙的效果就果断去setting的Bluetooth模块查看连接蓝牙的代码,终于找到了。在frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth中专门有个类存储管理蓝牙设备CachedBluetoothDevice.java,他连接远程蓝牙设备就是
synchronized void connectInt(LocalBluetoothProfile profile) {
if (!ensurePaired()) {
return;
}
if (profile.connect(mDevice)) {
if (Utils.D) {
Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
}
return;
}
Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
}
LocalBluetoothProfile是一个接口,各种协议的封装者实现了该接口,例如HfpClientProfile它里面封装的是BluetoothHeadsetClient,就是HFP协议。主要看它的connect方法
@Override
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List srcs = getConnectedDevices();
if (srcs != null) {
for (BluetoothDevice src : srcs) {
if (src.equals(device)) {
// Connect to same device, Ignore it
Log.d(TAG,"Ignoring Connect");
return true;
}
}
}
return mService.connect(device);
}
先获取已连接的蓝牙设备,如果蓝牙设备已连接返回true,不在列表中则调用BluetoothHeadsetClient的connect方法。接着看下这个BluetoothHeadsetClient怎么实例化。
mLocalAdapter.getProfileProxy(context, new HfpClientServiceListener(),
BluetoothProfile.HEADSET_CLIENT);
通过BluetoothAdapter的getProfileProxy方法去请求这个协议对象。
private final class HfpClientServiceListener
implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (V) Log.d(TAG,"Bluetooth service connected");
mService = (BluetoothHeadsetClient) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
List deviceList = mService.getConnectedDevices();
while (!deviceList.isEmpty()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if (device == null) {
Log.w(TAG, "HfpClient profile found new device: " + nextDevice);
device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
}
device.onProfileStateChanged(
HfpClientProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
mIsProfileReady=true;
}
@Override
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
监听返回状态,赋值。其实不仅仅是BluetoothHeadsetClient,BluetoothA2dpSink等等请求方式一模一样,参数不同。看到这个之后恍然大悟,车载蓝牙实现的功能不需要进行socket通信,只需要连接这些协议。
好了,整理一下连接过程,先拿到BluetoothAdapter,调用getProfileProxy方法请求协议,第三个参数为协议的种类,都定义在BluetoothProfile中,例如BluetoothProfile.AVRCP_CONTROLLER(蓝牙控制协议),BluetoothProfile.A2DP_SINK(音频协议)。实现BluetoothProfile.ServiceListener接口,监听返回状态。如果返回成功得到对象,就可以调用connect(BluetoothDevice device)连接。
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.AVRCP_CONTROLLER);
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.A2DP_SINK);
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.AVRCP_CONTROLLER) {
isAvrcpProfileReady = true;
mAvrcpCt = (BluetoothAvrcpController)proxy;
Log.d(TAG, "AvrcpController Profile Proxy Connected");
}
if (profile == BluetoothProfile.A2DP_SINK) {
isA2dpProfileReady = true;
mA2dpSink = (BluetoothA2dpSink)proxy;
Log.d(TAG, "BluetoothA2dpSink Profile Proxy Connected");
}
}
@Override
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
isAvrcpProfileReady = false;
mAvrcpCt = null;
Log.d(TAG, "AvrcpController Profile Proxy Disconnected");
}
if (profile == BluetoothProfile.A2DP_SINK) {
isA2dpProfileReady = false;
mA2dpSink = null;
Log.d(TAG, "BluetoothA2dpSink Profile Proxy Disconnected");
}
}
}
public boolean connect(BluetoothDevice device) {
if (null != mA2dpSink) {
return mA2dpSink.connect(device);
}
Log.i(TAG, "mA2dpSink == null");
return false;
}