最近在作Android蓝牙这部份内容,因此查阅了不少相关资料,在此总结一下。java
Bluetooth是一种短距离(10米)的无线通讯技术标准,蓝牙协议分为4层,即核心协议层、电缆替代协议层、电话控制协议层和采纳的其它协议层。这4种协议中最重要的是核心协议。蓝牙的核心协议包括基带、链路管理、逻辑链路控制和适应协议四部分。其中链路管理(LMP)负责蓝牙组件间链接的创建。逻辑链路控制与适应协议(L2CAP)位于基带协议层上,属于数据链路层,是一个为高层传输和应用层协议屏蔽基带协议的适配协议。android
安卓平台提供对蓝牙的通信栈的支持,容许设别和其余的设备进行无线传输数据。应用程序层经过安卓API来调用蓝牙的相关功能,这些API使程序无线链接到蓝牙设备,并拥有P2P或者多端无线链接的特性。api
packages/apps/Bluetooth/服务器
frameworks/base/core/Java/android/server/网络
framework/base/core/java/android/bluetoothapp
kernel\drivers\bluetoothBluetoothsocket
kernel\net\bluetooth Linux kerneltcp
external\bluetooth\bluedroidide
system\bluetoothBluetooth函数
/frameworks/base/core/java/android/bluetooth/
BluetoothAdapter
表明本地蓝牙适配器(蓝牙发射的装置),是全部蓝牙交互的入口。经过它能够搜索其它蓝牙设备,查询已经配对的设备列表,经过已知的MAC地址建立BluetoothDevice,建立BluetoothServerSocket监听来自其它设备的通讯。BluetoothDevice
表明了一个远端的蓝牙设备, 使用它请求远端蓝牙设备链接或者获取 远端蓝牙设备的名称、地址、种类和绑定状态。 (其信息是封装在 bluetoothsocket 中) 。BluetoothSocket
表明了一个蓝牙套接字的接口(相似于 tcp 中的套接字) ,他是应用程 序经过输入、输出流与其余蓝牙设备通讯的链接点。BluetoothServerSocket
表明打开服务链接来监听可能到来的链接请求 (属于 server 端) , 为了链接两个蓝牙设备必须有一个设备做为服务器打开一个服务套接字。 当远端设备发起连 接链接请求的时候,而且已经链接到了的时候,Blueboothserversocket 类将会返回一个 bluetoothsocket。BluetoothClass
描述了一个设备的特性(profile)或该设备上的蓝牙大体能够提供哪些服务(service),但不可信。好比,设备是一个电话、计算机或手持设备;Blueboothserversocket 设备能够提供audio/telephony服务等。能够用它来进行一些UI上的提示。BluetoothProfile
蓝牙协议BluetoothHeadset
提供手机使用蓝牙耳机的支持。这既包括蓝牙耳机和免提(V1.5)模式。BluetoothA2dp
定义高品质的音频,能够从一个设备传输到另外一个蓝牙链接。 “A2DP的”表明高级音频分配模式。BluetoothHealth
表明了医疗设备配置代理控制的蓝牙服务BluetoothHealthCallback
一个抽象类,使用实现BluetoothHealth回调。你必须扩展这个类并实现回调方法接收更新应用程序的注册状态和蓝牙通道状态的变化。BluetoothHealthAppConfiguration
表明一个应用程序的配置,蓝牙医疗第三方应用注册与远程蓝牙医疗设备交流。BluetoothProfile.ServiceListener
当他们已经链接到或从服务断开时通知BluetoothProfile IPX的客户时一个接口(即运行一个特定的配置文件,内部服务)。\packages\apps\Settings\src\com\android\settings\bluetooth
BluetoothEnabler
界面上蓝牙开启、关闭的开关就是它了,BluetoothSettings
主界面,用于管理配对和链接设备LocalBluetoothManager
提供了蓝牙API上的简单调用接口,这里只是开始。CachedBluetoothDevice
描述蓝牙设备的类,对BluetoothDevice的再封装BluetoothPairingDialog
那个配对提示的对话框/packages/apps/Phone/src/com/android/phone/
BluetoothPhoneService
在phone的目录确定和电话相关了,蓝牙接听挂断电话会用到这个/packages/apps/Bluetooth/src/com/android/bluetooth/
说到这里不能不说4.2蓝牙的目录变了,在4.1及之前的代码中packages层的代码只有opp协议相关应用的代码,也就是文件传输那部分,而4.2的代码应用层的代码则丰富了许多,按具体的蓝牙应用协议来区别,分为如下文件夹(这里一并对蓝牙一些名词做个简单解释)
btservice
这个前面AdapterService.java的描述你们应该能猜到一些,关于蓝牙基本操做的目录,一切由此开始。
a2dp
(Advanced Audio Distribution Profile)高级音频传输模式,蓝牙立体声,和蓝牙耳机听歌有关那些。
avrcp
音频/视频远程控制配置文件,是用来听歌时暂停,上下歌曲选择的。
hdp
(Health Device Profile)蓝牙医疗设备模式,能够建立支持蓝牙的医疗设备,使用蓝牙通讯的应用,例如心率监视器,血液,温度计和秤。
hfp
(Hands-free Profile)让蓝牙设备能够控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。
pbap
(Phonebook Access Profile)电话号码簿访问协议
hid
(The Human Interface Device)人机交互接口,蓝牙鼠标键盘什么的就是这个了。该协议改编自USB HID Protocol。
opp
(Object Push Profile)对象存储规范,最为常见的,文件的传输都是使用此协议。
pan
(Personal Area Network)描述了两个或更多个蓝牙设备如何构成一个即时网络,和网络有关还有串行端口功能(SPP),拨号网络功能(DUN)
android 4.2的蓝牙应用层部分代码更丰富了,虽然有些目录还没具体代码,不过说不许哪一个版本更新就有了,就像4.0添加了hdp医疗那部分同样。另外本来在framework的JNI代码也被移到packages/apps/bluetooth当中。
BluetoothAdapter
(蓝牙本地适配器)
getDefaultAdapter()
获得本地蓝牙适配器setName(String name)
设置蓝牙名称disable()
关闭蓝牙enable()
打开蓝牙isEnabled()
判断蓝牙是否打开getName()
获得本地蓝牙的名称getAddress()
获得本地蓝牙适配器的地址getBondedDevices()
获得已经绑定的蓝牙的设备getRemoteDevice(byte[] address)
获得远程蓝牙设备getRemoteDevice(String address)
获得远程蓝牙设备startDiscovery()
开始搜多附近蓝牙cancelDiscovery()
中止当前搜索蓝牙的 TasklistenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
建立 BluetoothServerSocketBluetoothDevice
(蓝牙设备)
createBond()
蓝牙配对 (低版本不支持,>=api19)createRfcommSocketToServiceRecord(UUID uuid)
建立 BluetoothSocketgetBondState()
获得配对的状态getAddress()
获得远程蓝牙适配器的地址getName()
获得远程蓝牙的名称BluetoothServerSocket
(数据传输服务端)
这个类一共只有三个方法两个重载的。两个重载的区别在于后面的方法指定了过期时间,须要注意的是,执行这两个方法的时候,直到接收到了客户端的请求(或是过时以后),都会阻塞线程,应该放在新线程里运行!
close()
关闭connect()
链接isConnected()
判断当前的链接状态accept()
接收请求accept(int timeout)
接收请求BluetoothSocket
(数据传输客户端)
close()
关闭connect()
链接getInptuStream()
获取输入流getOutputStream()
获取输出流getRemoteDevice()
获取远程设备,这里指的是获取bluetoothSocket指定链接的那个远程蓝牙设备开启蓝牙有两种方法:
1、直接调用系统对话框启动蓝牙:
在AndroidManifest.xml
文件中添加须要的权限,高版本也不须要动态受权:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
而后,在代码中执行:
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
若是不想让用户看到对话框,那么咱们还能够选择第二种方法,进行静默开启蓝牙。
2、静默开启,不会有方法一的对话框:
照样在AndroidManifest.xml
文件中添加须要的权限:
<!-- 已适配Android6.0 --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
因为蓝牙所须要的权限包含Dangerous Permissions,因此咱们须要在Java代码中进行动态受权处理:
private static final int REQUEST_BLUETOOTH_PERMISSION=10; private void requestBluetoothPermission(){ //判断系统版本 if (Build.VERSION.SDK_INT >= 23) { //检测当前app是否拥有某个权限 int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION); //判断这个权限是否已经受权过 if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){ //判断是否须要 向用户解释,为何要申请该权限 if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) Toast.makeText(this,"Need bluetooth permission.", Toast.LENGTH_SHORT).show(); ActivityCompat.requestPermissions(this ,new String[] {Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_BLUETOOTH_PERMISSION); return; }else{ } } else { } }
接下来咱们就能够静默开启蓝牙了:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter.enable(); //开启
关闭蓝牙
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.disable(); }
搜索分为主动搜索和被动搜索:
1、被动搜索
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); // 设置被发现时间,最大值是3600秒,0表示设备老是能够被发现的(小于0或者大于3600则会被自动设置为120秒) discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120); activity.startActivity(discoverableIntent); }
2、主动搜索
建立BluetoothAdapter
对象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
咱们先获取并显示一下已经配对的蓝牙设备列表
/* * 已配对设备列表 */ private ListView mBoundDevicesLv; /** * 显示已配对的设备列表 */ private void showBoundDevices() { List<Map<String, String>> mBoundDevicesList = new ArrayList<>(); Set<BluetoothDevice> boundDeviceSet = mBluetoothAdapter.getBondedDevices(); for (BluetoothDevice boundDevices : boundDeviceSet) { Map<String, String> mBoundDevicesMap = new HashMap<>(); mBoundDevicesMap.put("name", boundDevices.getName()); mBoundDevicesMap.put("address", boundDevices.getAddress()); mBoundDevicesList.add(mBoundDevicesMap); } SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, mBoundDevicesList, android.R.layout.simple_list_item_2, new String[]{"name", "address"}, new int[]{android.R.id.text1, android.R.id.text2}); mBoundDevicesLv.setAdapter(mSimpleAdapter); }
开始搜索
if (mBluetoothAdapter == null) { LogUtil.e(TAG, "设备不支持蓝牙"); } // 打开蓝牙 if (!mBluetoothAdapter.isEnabled()) { BluetoothAdapter.enable(); mBluetoothAdapter.cancelDiscovery(); } // 寻找蓝牙设备,android会将查找到的设备以广播形式发出去 while (!mBluetoothAdapter.startDiscovery()) { LogUtil.e(TAG, "尝试失败"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
定义搜索结果的广播接收器
// 设置广播信息过滤 IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一个设备就会发送一个该广播 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//当所有搜索完后发送该广播 filter.setPriority(Integer.MAX_VALUE);//设置优先级 registerReceiver(receiver, filter);// 注册蓝牙搜索广播接收者,接收并处理搜索结果
搜索蓝牙设备的广播接收器以下:
/** * 搜索出的设备集合 */ private List<Map<String, String>> devices = new ArrayList<>(); /** * 发现的设备列表 */ private ListView mDevicesLv; /** * 定义广播接收器 */ private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { ToastUtil.showToast(MainActivity.this, "Showing Devices"); // 从Intent中获取设备对象 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 定义一个装载蓝牙设备名字和地址的Map Map<String, String> deviceMap = new HashMap<>(); // 过滤已配对的和重复的蓝牙设备 if ((device.getBondState() != BluetoothDevice.BOND_BONDED) && isSingleDevice(device)) { deviceMap.put("name", device.getName() == null ? "null" : device.getName()); deviceMap.put("address", device.getAddress()); devices.add(deviceMap); } // 显示发现的蓝牙设备列表 mDevicesLv.setVisibility(View.VISIBLE); // 加载设备 showDevices(); } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //已搜素完成 } } };定义服务端线程类: /** * 判断此设备是否存在 */ private boolean isSingleDevice(BluetoothDevice device) { if (devices == null) { return true; } for (Map<String, String> mDeviceMap : devices) { if ((device.getAddress()).equals(mDeviceMap.get("address"))) { return false; } } return true; } /** * 显示搜索到的设备列表 */ private void showDevices() { SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, devices, android.R.layout.simple_list_item_2, new String[]{"name", "address"}, new int[]{android.R.id.text1, android.R.id.text2}); mDevicesLv.setAdapter(mSimpleAdapter); }
当咱们搜索到了蓝牙的以后,就须要配对,由于只有在配对以后才能链接。
在上面的搜索到的设备列表的点击事件中,进行配对。
BluetoothDevice device = (BluetoothDevice) adapter.getItem(i); if (device.getBondState() == BluetoothDevice.BOND_BONDED) {//是否已配对 connect(device); } else { try { Method boned=device.getClass().getMethod("createBond"); boolean isok= (boolean) boned.invoke(device); if(isOk) { connect(device); } } catch (Exception e) { e.printStackTrace(); } }
这里须要说明的是,这个配对Android在API19
以后对外提供了createBond()
这个方法。可是在API19
之前并无这个方法,因此用反射兼容性比较好。
在进行蓝牙链接以前,先介绍一下一个关键的东西:两个蓝牙设备进行链接时须要使用同一个UUID。但不少读者可能发现,有不少型号的手机(多是非Android系统的手机)之间使用了不一样的程序也可使用蓝牙进行通信。从表面上看,它们之间几乎不可能使用同一个UUID。
UUID的格式以下:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UUID的格式被分红5段,其中中间3段的字符数相同,都是4,第1段是8个字符,最后一段是12个字符。因此UUID其实是一个8-4-4-4-12的字符串。
实际上,UUID和TCP的端口同样,也有一些默认的值。例如,将蓝牙模拟成串口的服务就使用了一个标准的UUID:
00001101-0000-1000-8000-00805F9B34FB
除此以外,还有不少标准的UUID,以下面就是两个标准的UUID:
信息同步服务:00001104-0000-1000-8000-00805F9B34FB
文件传输服务:00001106-0000-1000-8000-00805F9B34FB
蓝牙传输数据与Socket相似。在网络中使用Socket和ServerSocket控制客户端和服务端的数据读写。而蓝牙通信也由客户端和服务端Socket来完成。蓝牙客户端Socket是BluetoothSocket
,蓝牙服务端Socket是BluetoothServerSocket
。这两个类都在android.bluetooth包中。
不管是BluetoothSocket
,仍是BluetoothServerSocket
,都须要一个UUID(全局惟一标识符,Universally Unique Identifier),UUID至关于Socket的端口,而蓝牙地址至关于Socket的IP。
下面,咱们开始进行模拟一个蓝牙数据的传输:
1、首先来看客户端:
定义全局常量变量:
private ListView mDevicesLv; private BluetoothAdapter mBluetoothAdapter; private List<Map<String, String>> devices = new ArrayList<>(); //随便定义一个UUID private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456"); private BluetoothSocket clientSocket; private BluetoothDevice device; private OutputStream os;//输出流
接下来咱们设置设备列表的点击事件
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Map<String, String> s = devices.get(i); String address = s.get("address");//把地址解析出来 //主动链接蓝牙服务端 try { // 若是当前正在搜索,则取消搜索。 if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } try { if (device == null) { //得到远程设备 device = mBluetoothAdapter.getRemoteDevice(address); } if (clientSocket == null) { //建立客户端蓝牙Socket clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID); //开始链接蓝牙,若是没有配对则弹出对话框提示咱们进行配对 clientSocket.connect(); //得到输出流(客户端指向服务端输出文本) os = clientSocket.getOutputStream(); } } catch (Exception e) { } if (os != null) { //往服务端写信息 os.write("蓝牙信息来了".getBytes("utf-8")); } } catch (Exception e) { } }
2、接下来看服务端:
服务端使用的是另外一部手机,接受上面手机经过蓝牙发送过来的信息并显示。
定义全局常量变量:
private BluetoothAdapter mBluetoothAdapter; private AcceptThread acceptThread; // 和客户端相同的UUID private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456"); private final String NAME = "Bluetooth_Socket"; private BluetoothServerSocket serverSocket; private BluetoothSocket socket; private InputStream is;//输入流
定义服务端线程类:
private Handler handler = new Handler() { public void handleMessage(Message msg) { Toast.makeText(getApplicationContext(), String.valueOf(msg.obj), Toast.LENGTH_LONG).show(); super.handleMessage(msg); } }; // 服务端监听客户端的线程类 private class AcceptThread extends Thread { public AcceptThread() { try { serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (Exception e) { } } public void run() { try { socket = serverSocket.accept(); is = socket.getInputStream(); while(true) { byte[] buffer =new byte[1024]; int count = is.read(buffer); Message msg = new Message(); msg.obj = new String(buffer, 0, count, "utf-8"); handler.sendMessage(msg); } } catch (Exception e) { } } }
在onCreate方法中初始化线程类并开启:
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); acceptThread = new AcceptThread(); acceptThread.start();
注意,使用socket.getInputStream接收到的数据是字节流,这样的数据是无法分析的,因此不少状况须要一个byte转十六进制String的函数:
public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); }
从Android 3.0开始,Bluetooth API就包含了对Bluetooth profiles的支持。
Bluetooth profile是基于蓝牙的设备之间通讯的无线接口规范。
你在你的类里能够完成BluetoothProfile接口来支持某一Bluetooth profile。
Android Bluetooth API完成了下面的Bluetooth profile:
Headset profile
提供了移动电话上的Bluetooth耳机支持。Android提供了BluetoothHeadset类,它是一个协议,用来经过IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth Headset profile
也包含Hands-Free profile
,还包括对AT命令
的支持。HFP (Hands-free Profile)
,免提模式,让蓝牙设备能够控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。HDP(Health Device Profile.)
,蓝牙医疗设备模式,能够建立支持蓝牙的医疗设备,使用蓝牙通讯的应用,例如心率监视器,血液,温度计和秤。AVRCP
,音频/视频远程控制配置文件,是用来听歌时暂停,上下歌曲选择的。A2DP(Advanced Audio Distribution Profile)
,高级音频传输模式。Android提供了BluetoothA2dp类,这是一个经过IPC来控制Bluetooth A2DP的协议。HID (The Human Interface Device)
,人机交互接口,蓝牙鼠标键盘什么的就是这个了。该协议改编自USB HID Protocol。OPP (Object Push Profile)
,对象存储规范,最为常见的,文件的传输都是使用此协议。PAN (Personal Area Network)
,描述了两个或更多个蓝牙设备如何构成一个即时网络,和网络有关还有串行端口功能(SPP),拨号网络功能(DUN)。PBAP (Phonebook Access Profile)
,电话号码簿访问协议。下面是使用profile的基本步骤:
例如,下面的代码片断显示如何链接到一个BluetoothHeadset协议对象,用来控制Headset profile:
BluetoothHeadset mBluetoothHeadset; // 获取默认的Bluetooth适配器 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 链接Headset profile mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET); private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener(){ public void onServiceConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { mBluetoothHeadset = (BluetoothHeadset) proxy; } } public void onServiceDisconnected(int profile) { if (profile == BluetoothProfile.HEADSET) { mBluetoothHeadset = null; } } }; // ... 使用 mBluetoothHeadset // 使用以后,关闭Proxy mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)
以上,就先分析到这儿吧