Android 藍牙使用

原文地址: Android 藍牙使用 - Stars-One的雜貨小窩

公司項目需求需要實現監聽藍牙耳機連接,且要獲取藍牙耳機電量功能,翻了不少官方文檔,記錄下技術調研代碼

注:本文沒有研究藍牙配對功能

關於藍牙權限適配

Android12以後,申請藍牙權限需要申請一組,如新增的幾個權限,需要一起申請

參考: 藍牙權限  |  Connectivity  |  Android Developers

val permissionList =  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
   //android12及以上版本,這2個權限申請只會彈出一個對話框
   listOf(Permission.BLUETOOTH_CONNECT, Permission.BLUETOOTH_SCAN)
} else {
	//android12以下版本申請,默認是同意的,不會有權限彈窗
   listOf(Permission.BLUETOOTH_CONNECT)
}

打開藍牙開關

注意,如果是Android12及以上版本,藍牙開關打開操作需要有Bluetooth_Connect權限才能執行操作

效果就是直接打開藍牙開關

val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter

//需要權限android.permission.BLUETOOTH_CONNECT才能執行操作
bluetoothAdapter.enable()

不過Android還是有提供另外的一個方法供我們使用,就是下面的方法

此方法是API 5 就有的方法,和上面一樣,Android12及以上版本,就是需要有Bluetooth_Connect權限才能執行成功,否則會拋出異常

兼容低版本和高版本,此方法兼容,調用此方法,系統會彈出一個是否允許打開藍牙的對話提示框

val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
//這裏寫你對應的Activity,我這裏只是個例子
Activity.startActivityForResult(intent,7777)

至於接收回調,則是在對應的Activity中的onActivityResult()方法中處理返回結果:

  • 返回結果RESULT_OK,藍牙模塊打開成功
  • 返回結果RESULT_CANCELED,藍牙模塊打開失敗

PS: 測試的時候,用的華爲手機,系統爲鴻蒙4,Android Studio顯示爲Android12,但是使用bluetoothAdapter.enable()卻是能夠正常彈出申請藍牙是否打開的對話框

獲取已配對的藍牙設備列表

val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
val bluetoothDevices = bluetoothAdapter.bondedDevices


//獲取已配對的藍牙設備列表
bluetoothDevices.forEach { device->
	val text = when(device.type){
		BluetoothDevice.DEVICE_TYPE_UNKNOWN -> "傳統藍牙"
		BluetoothDevice.DEVICE_TYPE_CLASSIC -> "傳統藍牙"
		BluetoothDevice.DEVICE_TYPE_LE -> "低功耗藍牙"
		BluetoothDevice.DEVICE_TYPE_DUAL -> "傳統/低功耗雙模式藍牙"
		else->"未知類型"
	}
	LogUtils.d("藍牙設備名稱: ${device.name} 藍牙設備地址: ${device.address} 設備類型: $text")

}

獲取藍牙耳機設備列表

fun getEarPhoneDevices(context: Context): List<BluetoothDevice> {
	val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter

	val bluetoothDevices = bluetoothAdapter.bondedDevices
	val types = listOf(
		BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES,
		BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET,
		BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO,
		BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
	)
	return bluetoothDevices.filter { device ->
		types.any { it == device.bluetoothClass.deviceClass }
	}
}

PS: 測試過程中,發現漫步者耳機的類型識別不了爲上述的四個類型...

獲取當前已連接藍牙耳機

一般只能連接一個藍牙耳機

val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
//如果在連接了藍牙耳機的情況,這裏會進入到裏面獲取到數據
bluetoothAdapter.getProfileProxy(this@EarphoneActivity, object : ServiceListener {
	override fun onServiceConnected(p0: Int, p1: BluetoothProfile?) {
		p1?.apply {
			//獲取藍牙耳機的設備列表
			val devices = this.connectedDevices
			devices.forEach { device ->
				val text = when (device.type) {
					BluetoothDevice.DEVICE_TYPE_UNKNOWN -> "傳統藍牙"
					BluetoothDevice.DEVICE_TYPE_CLASSIC -> "傳統藍牙"
					BluetoothDevice.DEVICE_TYPE_LE -> "低功耗藍牙"
					BluetoothDevice.DEVICE_TYPE_DUAL -> "傳統/低功耗雙模式藍牙"
					else -> "未知類型"
				}
				LogUtils.d("藍牙設備名稱: ${device.name} 藍牙設備地址: ${device.address} 設備類型: $text")
			}
		}
		LogUtils.d("設備連接")
	}

	override fun onServiceDisconnected(p0: Int) {

	}
}, BluetoothProfile.HEADSET)
}

獲取藍牙耳機電量

此方法適應市面上大多數藍牙耳機,但如果是AirPods,則無效果,下一章節會講到獲取AirPods電量方法

(雖然參考的文章說這個是AirPods的擴展AT命令,但實際對於正版AirPods無效果,反倒是我同事的華強北AirPods支持...)

通過註冊廣播,來獲取到對應的AT命令,在參數可以取值

val bluetoothIntentFilter = IntentFilter().apply {
	addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
	addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)

+	addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY+"."+BluetoothAssignedNumbers.APPLE)
+	addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)
}
registerReceiver(BlueToothReceiver(), bluetoothIntentFilter)

廣播詳情說明可看此鏈接藍牙耳機 | 安卓開發者

之後在Receiver可以獲取對應的AT命令參數,如下代碼:

//藍牙耳機的廣播監聽
if (BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT == action) {
	Log.d(TAG, "onReceive: 藍牙設備AT命令")

	//藍牙設備
	val blueDevice = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
		intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
	} else {
		intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
	}
	blueDevice?.apply {
		val device = this
		val text = when (device.type) {
			BluetoothDevice.DEVICE_TYPE_UNKNOWN -> "傳統藍牙"
			BluetoothDevice.DEVICE_TYPE_CLASSIC -> "傳統藍牙"
			BluetoothDevice.DEVICE_TYPE_LE -> "低功耗藍牙"
			BluetoothDevice.DEVICE_TYPE_DUAL -> "傳統/低功耗雙模式藍牙"
			else -> "未知類型"
		}
		LogUtils.d("藍牙設備名稱: ${device.name} 藍牙設備地址: ${device.address} 設備類型: $text")
	}

	intent.extras?.apply {
		val cmd = getString(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD,"")
		val cmdType = getInt(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,0)
		//根據命令行類型,會有不同的參數
		val cmdTypeStr = when (cmdType) {
			BluetoothHeadset.AT_CMD_TYPE_ACTION -> {"AT_CMD_TYPE_ACTION"}
			BluetoothHeadset.AT_CMD_TYPE_BASIC -> {"AT_CMD_TYPE_BASIC"}
			BluetoothHeadset.AT_CMD_TYPE_READ -> {"AT_CMD_TYPE_READ"}
			BluetoothHeadset.AT_CMD_TYPE_SET -> {"AT_CMD_TYPE_SET"}
			BluetoothHeadset.AT_CMD_TYPE_TEST -> {"AT_CMD_TYPE_TEST"}
			else -> {""}
		}

		val args = get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS) as Array<Any>

		LogUtils.d("""
	   接收到的AT命令:  AT $cmd $cmdTypeStr ${args.joinToString(",") { it.toString() }}
	""".trimIndent())
	
		if (cmd == "+IPHONEACCEV") {
				//電量等級說明 0:10% 9:100%
				val param = args.map { it.toString().toInt() }
				val level = param.last()
				
				   //電量
			val battery = (level + 1) * 10
		}
	}
	
}

AT+IPHONEACCEV命令
該命令是用來提示藍牙配件的電池狀態,可以提示兩方面:一方面是電池的電量百分比,一當面是藍牙配件的當前的充電狀態。該命令的說明見下方:

格式:AT+IPHONEACCEV=Number of key/value pairs,key1,val1,key2,val2,…

附帶的參數的含義分別是:①鍵值對的數目:接下來的參數文本的數量;②接下來就是鍵值對分別是:鍵值爲1表示的是電量,該鍵所對應的值就是電量百分比,使用字串”0“到”9“表示;鍵值爲2表示的是充電狀態,0表示不在充電,1表示正在充電。

舉例:AT+IPHONEACCEV=1,1,3 該AT指令就說明附帶了一個鍵值對(第一個參數是1);鍵是1,那麼表示的是電量,且電量是40%(因爲使用的是0~9,這裏3就對應的百分比是40%)。

有個疑問,AirPods在電量變化後,會主動發送AT命令嗎?還是說是在連接後纔會發一次,之後便不再發送了?

AT +XAPL AT_CMD_TYPE_SET AB-12-0100,18

AirPods耳機電量

起初一致沒找到方案,最終在github上輸入了AirPods關鍵字,發現了有幾個對於對應的開源庫,測試發現下面這個能夠符合要求(不過測試的時候,電量有些誤差,充電倉在iphone手機上顯示爲8%,而android這邊則顯示爲5%)

app原理則是通過藍牙掃描,獲取到藍牙設備對應的設備廠商數據,並區分型號,然後做對應的處理從而獲取到電量(比如說左耳機,右耳機,耳機倉)

通過藍牙的adapter獲取scanner,調用掃描方法,之後在掃描的回調裏處理返回結果, 從而得到對應的電量數據

藍牙掃描還需要一個獲取定位的權限(在Android12版本之下需要),不然無法掃描

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章