前言
??在上一篇文章中已經(jīng)了解了數(shù)據(jù)操作的方式,而數(shù)據(jù)交互的字節(jié)長(zhǎng)度取決于我們手機(jī)與藍(lán)牙設(shè)備的最大支持長(zhǎng)度。
目錄
- Ble藍(lán)牙App(一)掃描
- Ble藍(lán)牙App(二)連接與發(fā)現(xiàn)服務(wù)
- Ble藍(lán)牙App(三)特性和屬性
- Ble藍(lán)牙App(四)UI優(yōu)化和描述符
- Ble藍(lán)牙App(五)數(shù)據(jù)操作
- Ble藍(lán)牙App(六)請(qǐng)求MTU與顯示設(shè)備信息
正文
??本文中我們需要請(qǐng)求Mtu,然后做一些利用使用的UI改變,比如增加菜單,和顯示設(shè)備操作信息。
一、請(qǐng)求MTU的概念
??在 Android 的 BLE(Bluetooth Low Energy)開發(fā)中,requestMtu
是一個(gè)用于請(qǐng)求修改 BLE 連接的最大傳輸單元(MTU)的方法。MTU 是指在一個(gè)藍(lán)牙數(shù)據(jù)包中能夠傳輸?shù)淖畲髷?shù)據(jù)量。
??通過調(diào)用 requestMtu
方法,你可以請(qǐng)求增加或減少 BLE 連接中的 MTU 大小。較大的 MTU 可以提高數(shù)據(jù)傳輸效率,因?yàn)槊總€(gè)數(shù)據(jù)包可以攜帶更多的數(shù)據(jù)。而較小的 MTU 可以降低延遲,因?yàn)閿?shù)據(jù)可以更快地分割成較小的包進(jìn)行傳輸。
??獲取MTU,藍(lán)牙一般默認(rèn)支持的MTU長(zhǎng)度是23個(gè)字節(jié),一個(gè)字節(jié)為類型操作碼,兩個(gè)字節(jié)為類型操作句柄,實(shí)際傳輸數(shù)據(jù)就是20字節(jié)。通過gatt.requestMtu(mtu)。會(huì)觸發(fā)onMtuChanged回調(diào)。這里mtu 的范圍在23 ~ 517之間,目前市面上Android版本高的手機(jī)基本上都是247。也就是說即使你mtu = 517,回調(diào)中的mtu可能還是247,為什么呢?因?yàn)槟愕腁ndroid手機(jī)上的藍(lán)牙最大支持247。而在傳輸?shù)臅r(shí)候你還需要-3,也就是244。單次傳輸?shù)淖畲笞止?jié)數(shù)據(jù)為244個(gè)字節(jié)。那么如果你有1000個(gè)字節(jié)需要進(jìn)行傳輸,則需要對(duì)字節(jié)進(jìn)行分包處理,例如一次最大傳輸244個(gè)字節(jié),則需要分成5個(gè)包進(jìn)行傳輸,前4個(gè)包,每個(gè)包為244個(gè)字節(jié),最后一個(gè)包為24個(gè)字節(jié)。注意:在 Android 版本低于 5.0 的設(shè)備上,MTU 大小是固定的,無(wú)法通過此方法進(jìn)行修改。
二、創(chuàng)建使用菜單
??下面我們進(jìn)行實(shí)操環(huán)節(jié),首先我們需要增加一個(gè)請(qǐng)求MTU的入口,而當(dāng)前頁(yè)面上似乎并沒有多余的入口了,那么我們就可以增加一個(gè)菜單了,首先在res
下新建一個(gè)menu
文件夾,在此文件夾下新建一個(gè)menu_main.xml
文件,代碼如下所示:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_request_mtu"
android:orderInCategory="200"
android:title="請(qǐng)求Mtu" />
</menu>
然后去MainActivity中增加菜單,首先在onCreate函數(shù)中增加如下代碼:
//設(shè)置支持ActionBar
setSupportActionBar(binding.toolbar)
??因?yàn)槲覀冊(cè)谥黝}中使用的是NoActionBar,而菜單實(shí)際上就是在ActionBar上的,所以設(shè)置我們的ToolBar支持ActionBar即可,然后在MainActivity中重寫下面兩個(gè)方法:
/**
* 創(chuàng)建選項(xiàng)菜單
*/
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
/**
* 選項(xiàng)菜單Item選中
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (!bleCore.isConnected()) {
showMsg("設(shè)備已斷開連接")
return false
}
when(item.itemId) {
R.id.item_request_mtu -> showRequestMtuDialog()
}
return true
}
??這兩個(gè)方法的意圖很明顯,一個(gè)創(chuàng)造菜單,一個(gè)監(jiān)聽菜單Item選中,在操作之前判斷是否連接,在點(diǎn)擊請(qǐng)求Mtu的菜單Item之后顯示一個(gè)彈窗。
三、請(qǐng)求MTU彈窗
??下面我們來(lái)寫這個(gè)彈窗,首先在layout
下創(chuàng)建一個(gè)dialog_request_mtu.xml
文件,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="請(qǐng)求Mtu" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/data_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:boxStrokeColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:digits="0123456789"
android:hint="MTU"
android:inputType="number"
android:lines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="18dp"
android:layout_weight="1"
android:text="取消"
app:layout_constraintEnd_toStartOf="@+id/btn_positive"
app:layout_constraintTop_toTopOf="@+id/btn_positive" />
<Button
android:id="@+id/btn_positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:text="發(fā)送"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/data_layout"
app:layout_constraintTop_toBottomOf="@+id/data_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
??你會(huì)發(fā)現(xiàn)這個(gè)布局內(nèi)容和寫數(shù)據(jù)彈窗如出一轍,但還是有區(qū)別的,你需要仔細(xì)觀察一下,布局寫好了,下面我們?cè)贛ainActivity中增加一個(gè)顯示彈窗的函數(shù),代碼如下所示:
/**
* 顯示請(qǐng)求Mtu彈窗
*/
private fun showRequestMtuDialog() {
val dialog = BottomSheetDialog(this, R.style.BottomSheetDialogStyle)
val mtuBinding = DialogRequestMtuBinding.inflate(layoutInflater)
mtuBinding.btnPositive.setOnClickListener {
val inputData = mtuBinding.etData.text.toString()
if (inputData.isEmpty()) {
mtuBinding.dataLayout.error = "請(qǐng)輸入MTU"
return@setOnClickListener
}
val mtu = inputData.toInt()
if (mtu !in 23..517) {
mtuBinding.dataLayout.error = "請(qǐng)輸入23 ~ 517之間的數(shù)字"
return@setOnClickListener
}
bleCore.requestMtu(mtu)
dialog.dismiss()
}
mtuBinding.btnNegative.setOnClickListener {
dialog.dismiss()
}
dialog.setContentView(mtuBinding.root)
dialog.show()
}
??這個(gè)函數(shù)中唯一值得說的一點(diǎn)就是關(guān)于這個(gè)有效范圍的判斷,因?yàn)樵诟拍钪形覀冋f過,mtu的范圍在23 ~ 517之間,所以在輸入之后我們做了一個(gè)校驗(yàn),其余的就沒啥好說的,校驗(yàn)通過之后就會(huì)調(diào)用bleCore.requestMtu(mtu)
去請(qǐng)求Mtu,當(dāng)前我們還沒有這個(gè)方法,所以我們?nèi)leCore中增加。
四、請(qǐng)求MTU與回調(diào)
??在BleCore中增加一個(gè)requestMtu()
函數(shù),代碼如下所示:
/**
* 請(qǐng)求Mtu
* @param mtu 23 ~ 517
*/
fun requestMtu(mtu: Int) {
deviceInfo("請(qǐng)求Mtu:$mtu")
mGatt?.requestMtu(mtu)
}
??而調(diào)用了Gatt的requestMtu()
函數(shù),則會(huì)觸發(fā)onMtuChanged()
回調(diào)函數(shù),在BleGattCallback
中增加onMtuChanged()
函數(shù),代碼如下所示:
/**
* 請(qǐng)求Mtu回調(diào)
*/
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) return
deviceInfo("Mtu更改為:$mtu")
}
??現(xiàn)在你運(yùn)行一下應(yīng)該是可以看到菜單的三個(gè)點(diǎn)的,只不過這個(gè)點(diǎn)是黑色的,而我們的標(biāo)題欄背景是橙色的,所以這個(gè)黑色就不是很搭,因此我們需要修改一下這三個(gè)點(diǎn)的顏色,改成白色。
五、修改菜單
首先我們?cè)趖hemes.xml中增加如下代碼:
<style name="MyOverflowButtonStyle" parent="Widget.AppCompat.ActionButton.Overflow">
<item name="android:tint">@color/white</item>
</style>
??這是一個(gè)菜單圖標(biāo)的樣式,android:tint
就是添加一個(gè)顏色,可以說是覆蓋一個(gè)顏色,比如原來(lái)是黑色,那么我再涂成白色。然后在GoodBle主題樣式中增加這一行代碼:
<style name="Theme.GoodBle" parent="Theme.MaterialComponents.DayNight.NoActionBar">
...
<item name="android:actionOverflowButtonStyle">@style/MyOverflowButtonStyle</item>
</style>
如果你還有深色模式的適配的話,建議將深色模式主題下的改動(dòng)同步一下,下面我們運(yùn)行一下看看效果:
??請(qǐng)求Mtu確實(shí)如同我們所想的那么,但是標(biāo)題欄哪里就不太好看了,因?yàn)閿嚅_連接的文字影響了主標(biāo)題的顯示,針對(duì)這種情況,有多種選擇,我們可以將斷開連接的操作方式放到菜單里,這樣就不占標(biāo)題的位置,下面我們操作一下。
首先修改menu_main.xml
,在里面增加一個(gè)item
,代碼如下所示:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_disconnect"
android:orderInCategory="200"
android:visible="false"
android:title="斷開連接" />
<item
android:id="@+id/item_request_mtu"
android:orderInCategory="200"
android:title="請(qǐng)求Mtu" />
</menu>
這里默認(rèn)設(shè)置斷開連接Item不顯示,然后進(jìn)入到activity_main.xml
中將之前Toolbar中的TextView去掉。
再回到MainActivity中,首先聲明一個(gè)變量
private lateinit var mMenu: Menu
然后在onCreateOptionsMenu()
函數(shù)中賦值
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
mMenu = menu
return true
}
修改斷開連接Item的點(diǎn)擊事件,改動(dòng)onOptionsItemSelected()
函數(shù)中的代碼,如下所示:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (!bleCore.isConnected()) {
showMsg("設(shè)備已斷開連接")
return false
}
when(item.itemId) {
R.id.item_disconnect -> bleCore.disconnect()
R.id.item_request_mtu -> showRequestMtuDialog()
}
return true
}
??然后去掉之前所寫好的id為tv_disconnect的TextView控件的點(diǎn)擊事件,同時(shí)修改onConnectionStateChange()
函數(shù)中的代碼:
override fun onConnectionStateChange(state: Boolean) {
runOnUiThread {
if (state) {
//binding.tvDisconnect.visibility = View.VISIBLE
mMenu.findItem(R.id.item_disconnect).isVisible = true
...
} else {
//binding.tvDisconnect.visibility = View.GONE
mMenu.findItem(R.id.item_disconnect).isVisible = false
...
}
}
}
??這里就是把控件的顯示隱藏?fù)Q成Item的顯示和隱藏,下面你其實(shí)就可以運(yùn)行了,不過還有很好的方式,那就是讓我們的斷開連接item在toolbar有空間的時(shí)候顯示在Toolbar上,沒有空間的時(shí)候就在菜單彈窗里面,我們先弄一個(gè)斷開連接的圖標(biāo),在drawable下創(chuàng)建一個(gè)ic_disconnect.xml
,代碼如下所示:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M288.9,527.8c-56.1,56.1 -56.1,148.7 0,204.8s148.7,56.1 204.8,0l80.5,-80.5 -204.8,-204.8 -80.5,80.5zM741.2,280.4c-56.1,-56.1 -148.7,-56.1 -204.8,0l-80.5,80.5 204.8,204.8 80.5,-80.5c57.3,-56.1 57.3,-148.7 0,-204.8zM315.3,340.5l34.5,-34.5 367.2,367.2 -34.5,34.5z"
android:fillColor="#ffffff"/>
</vector>
下面再修改一下item_disconnect的內(nèi)容,代碼如下所示:
<item
android:id="@+id/item_disconnect"
android:icon="@drawable/ic_disconnect"
android:orderInCategory="200"
android:title="斷開連接"
android:visible="false"
app:showAsAction="ifRoom" />
常見的 showAsAction 的取值包括:
- never:表示菜單項(xiàng)將不顯示在工具欄中,而是隱藏在溢出菜單中。
- ifRoom:表示如果有足夠的空間,菜單項(xiàng)將顯示在工具欄中,否則將顯示在溢出菜單中。
- always:表示菜單項(xiàng)始終顯示在工具欄中,即使沒有足夠的空間。它將占據(jù)工具欄中的可用空間,可能會(huì)擠占其他工具欄元素。
- withText:與 always 類似,但會(huì)同時(shí)顯示菜單項(xiàng)的文本標(biāo)簽。
下面你可以再運(yùn)行看一下效果,我就不運(yùn)行了。
六、顯示設(shè)備信息
??先說說為什么要顯示設(shè)備操作信息,因?yàn)檫@可以方便我們測(cè)試一些功能,雖然我們可以在控制臺(tái)看到所有內(nèi)容,不過終究不是時(shí)時(shí)刻刻都是調(diào)試的,也有直接使用的情況,那么針對(duì)這個(gè)需求,我們可以在主頁(yè)面中點(diǎn)擊設(shè)備信息是顯示一個(gè)設(shè)備信息列表彈窗,首先要做的是創(chuàng)建一個(gè)item布局,在layout下創(chuàng)建item_device_info.xml
,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_device_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:ellipsize="end"
android:padding="16dp"
android:singleLine="true"
android:text="設(shè)備操作信息"
android:layout_marginBottom="1dp"
android:textColor="@color/black" />
然后再創(chuàng)建一個(gè)適配器,在adapter下新建一個(gè)InfoAdapter,代碼如下所示:
class InfoAdapter(
private val mLists: List<String>
) : RecyclerView.Adapter<InfoAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemDeviceInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.tvDeviceInfo.text = mLists[position]
}
override fun getItemCount() = mLists.size
class ViewHolder(itemView: ItemDeviceInfoBinding) : RecyclerView.ViewHolder(itemView.root) {
var binding: ItemDeviceInfoBinding
init {
binding = itemView
}
}
}
下面創(chuàng)建彈窗布局,在layout下創(chuàng)建一個(gè)dialog_device_info.xml,代碼如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:background="@color/gray_white"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:navigationIcon="@drawable/ic_close_24"
app:title="設(shè)備操作信息"
app:titleCentered="true" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_device_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp" />
</LinearLayout>
同時(shí)我們修改一下activity_main.xml中的顯示設(shè)備信息的控件,修改后代碼如下所示:
<TextView
android:id="@+id/tv_device_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:foreground="?attr/selectableItemBackground"
android:padding="16dp"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
主要改動(dòng)就是單行顯示,增加點(diǎn)擊效果,同時(shí)多出來(lái)的內(nèi)容省略掉,下面回到MainActivity中增加一個(gè)顯示設(shè)備操作信息彈窗的函數(shù),代碼如下所示:
/**
* 顯示設(shè)備信息彈窗
*/
private fun showDeviceInfoDialog(mInfoList: MutableList<String>) {
val dialog = BottomSheetDialog(this, R.style.BottomSheetDialogStyle)
val infoBinding = DialogDeviceInfoBinding.inflate(layoutInflater)
infoBinding.toolbar.setNavigationOnClickListener { dialog.dismiss() }
infoBinding.rvDeviceInfo.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = InfoAdapter(mInfoList)
}
dialog.setContentView(infoBinding.root)
dialog.show()
}
然后在MainActivity中聲明一個(gè)變量:
private val mInfoList: MutableList<String> = mutableListOf()
然后需要在回調(diào)中添加數(shù)據(jù),在斷連時(shí)清除數(shù)據(jù):
override fun deviceInfo(info: String) {
runOnUiThread {
binding.tvDeviceInfo.text = info
mInfoList.add(info)
}
}
override fun onConnectionStateChange(state: Boolean) {
runOnUiThread {
if (state) {
...
} else {
mMenu.findItem(R.id.item_disconnect).isVisible = false
...
mInfoList.clear() //清除Info列表
}
}
}
最后在onCreate()
函數(shù)中增加設(shè)備信息TextView的點(diǎn)擊監(jiān)聽,代碼如下所示:
override fun onCreate(savedInstanceState: Bundle?) {
...
//設(shè)備信息
binding.tvDeviceInfo.setOnClickListener { if (mInfoList.size > 0) showDeviceInfoDialog(mInfoList) }
}
如果列表有數(shù)據(jù)就顯示彈窗,下面我們運(yùn)行一下看看:
七、源碼
如果對(duì)你有所幫助的話,不妨 Star 或 Fork,山高水長(zhǎng),后會(huì)有期~文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-757583.html
源碼地址:GoodBle文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-757583.html
到了這里,關(guān)于Android Ble藍(lán)牙App(六)請(qǐng)求MTU與顯示設(shè)備信息的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!