蓝牙是一种无线技术标准,可实现设备间短距离数据交换。 蓝牙可以以一定的周期发送广播,手机端接收到广播后,解析广播包,可做设备识别、配对,事件通知以及指令控制等。低精度定位根据设备的信号强度,可以估算出大概方位和距离。
蓝牙通信
前置条件:
首先,要操作蓝牙,先要在AndroidManifest.xml里加入权限
除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:
通信流程:
发现设备->配对/绑定设备->建立连接->数据通信
发现设备
经典蓝牙设备发现其它经典蓝牙设备的方式是调用BluetoothAdapter的startDiscovery()方法,这个方法只能够发现经典蓝牙设备。
低功耗蓝牙中则有一个主设备(Central)和从设备(Peripheral,也叫外围设备)的概念。主设备作为发现方(一般为手机)调用发现设备的方法,通过BluetoothAdapter的startLeScan()方法实现。从设备则作为被发现方(穿戴设备,便携设备等),发出广播,以供发现。同样,这个startLeScan()方法也仅能够发现低功耗蓝牙从设备,在整个搜索过程中,功耗是比较大的,应尽快结束搜索。
发现设备比较重要,实践中有2个比较重要的点:
1 、经典蓝牙配对前,如果没有扫描过程,通过蓝牙地址直接配对,配对对话框可能不出现,以消息通知栏的方式呈现。如果扫描过,则会弹出配对对话框。
2 、ble连接前,如果没有扫描过程,通过蓝牙地址直接连接,可能连不上。如果扫描并发现外围设备,则会连接成功。
配对/绑定
配对指的是BLE设备的配对。配对的作用在于和设备做相互确认,一方面是确定要操作的设备,另一方面是考虑到安全因素。 经典蓝牙可通过createRfcommSocketToServiceRecord 和 Method creMethod = BluetoothDevice.class.getMethod("createBond"); creMethod.invoke(mBluetoothDevice);
ble蓝牙在连接的过程中 自动配对,但不弹出配对对话框。
建立连接
在建立连接的方式上,两者就千差万别了。
蓝牙小知识
在蓝牙设备中,存在着物理地址,我们也叫作蓝牙的MAC地址,这个地址是唯一的,就像咱们网络上的IP地址。同时还存在着一个叫做UUID的东西,可以把它理解为是IP地址中的端口号。正如知道了IP地址和端口号,就知道了怎么链接到目标网络服务器位置,知道了蓝牙设备的MAC地址和UUID也就能够确定到具体是哪一台蓝牙设备了,这两者合起来就是蓝牙的唯一身份标识。BLE扫描的过程,设备会生成随机地址。根据随机地址也能进行连接。
经典蓝牙建立连接的方式实际上就是Socket的连接的建立。只不过这里不是直接用Socket,而是BluetoothSocket。获取BluetoothSocket的方式也很简单,利用搜索找到的BluetoothDevice,调用其方法createRfcommSocketToServiceRecord(UUID)。最后,使用获取到的BluetoothDevice调用其方法connect()就建立了经典蓝牙设备之间的连接通道。
蓝牙连接耳机播放音乐
A2dp的连接过程,在蓝牙搜索结果列表连接一个蓝牙耳机,既然是从设备列表开始,所以起步代码自然是这个了
DevicePickerFragment.java (settings\src\com\android\settings\bluetooth) 3884 2013-6-26
void onClicked() {
int bondState = mCachedDevice.getBondState();
if (mCachedDevice.isConnected()) {
askDisconnect();
} else if (bondState == BluetoothDevice.BOND_BONDED) {
mCachedDevice.connect(true);
} .......
}
void connect(boolean connectAllProfiles) {
if (!ensurePaired()) { //要先确保配对
return;
}
mConnectAttempted = SystemClock.elapsedRealtime();
connectWithoutResettingTimer(connectAllProfiles);//没别的了,只能看到这里
}
代码路径这里packages/apps/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java,具体代码看下面
// Try to initialize the profiles if they were not.
...........
// Reset the only-show-one-error-dialog tracking variable
mIsConnectingErrorPossible = true;
int preferredProfiles = 0;
for (LocalBluetoothProfile profile : mProfiles) {
if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
if (profile.isPreferred(mDevice)) {
++preferredProfiles;
connectInt(profile);//连接在这里,
}
}
}
.............
connectInt的实现很简单,直接跳过看里面的profile.connect(mDevice),这里的profile是指A2dpProfile,所以connet()方法的具体实现在
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List sinks = getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
mService.disconnect(sink);
}}
return mService.connect(device);
}
下面是 BluetoothA2dp.java (frameworks\base\core\java\android\bluetooth) ,为什么是这样看下这个private BluetoothA2dp mService;就知道了
public boolean connect(BluetoothDevice device) {
if (mService != null && isEnabled() &&
isValidDevice(device)) {
try {
return mService.connect(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
}
}...........
return false;
Binder跳转
public boolean connect(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return false;
return service.connect(device);
}
}
之后的跳转和第一部分蓝牙接听电话跳转过程类似,就不重复了,最后会来到packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp的connectA2dpNative,同样到下面的代码,我们能看到的开放的代码也就是这些,再下面要看vendor的具体实现了。
static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {
jbyte *addr;
bt_bdaddr_t * btAddr;
bt_status_t status;
ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);
if (!sBluetoothA2dpInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
btAddr = (bt_bdaddr_t *) addr;
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
ALOGE("Failed HF connection, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
本文主要讲了车载中的蓝牙通信基础,如何连接蓝牙耳机代码实现。车载除了这有许多要学习的。车载学习资料可以点击获取方式;有BYD车载高级工程师拟制。初步学习感觉内容讲的很细致。这里推荐一下。
比喻:
- 串口
- DLNA
- Automotive
- 车载进程通信
- CarLauncher
- 车载多媒体
- 车载空调系统
- 车载系统开发
等等。。。
Android车载是未来10年的黄金赛道,把握住这次机会就能稳住程序员的饭碗,所以学习是必要的。