最近在作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)
以上,就先分析到這兒吧