android藍牙簡單開發(fā)示例教程
概述
前段時間學習了一些藍牙開發(fā)的知識,記錄一下Android中藍牙的簡單開發(fā)。下面是最重要的兩個類。
BluetoothAdapter : 藍牙適配器,通過getDefaultAdapter ()去獲取一個實例,如果設(shè)備不支持藍牙的話,返回的是一個null對象,通過它,可以打開、關(guān)閉藍牙,掃描設(shè)備、向指定設(shè)備創(chuàng)建socket通道…
BluetoothDevice : 代表一個設(shè)備對象,可以通過它獲取設(shè)備的名字、地址、類型等,也可以創(chuàng)建匹配,建立socket通道等等。
1、權(quán)限申請
<uses-permission android:name="android.permission.BLUETOOTH"/> 使用藍牙所需要的權(quán)限 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 使用掃描和設(shè)置藍牙的權(quán)限(申明這一個權(quán)限必須申明上面一個權(quán)限)
Android6以上版本,掃描其他藍牙還需要位置權(quán)限
// Android 9 以下版本 <user-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> // Android 9 以上 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
2、打開藍牙
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 如果設(shè)備不支持藍牙
if (mBluetoothAdapter == null){
return;
}
// 設(shè)備支持藍牙功能,調(diào)用startActivityForResult去啟動藍牙
if (!mBluetoothAdapter.isEnabled()){
startBlueTooth.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
}
打開藍牙功能是通過startActivity去啟動的,但是startActivity這個函數(shù)已經(jīng)過期了,所以我使用官方推薦的Activity Result替代它
ActivityResultLauncher<Intent> startBlueTooth = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result==null){
Toast.makeText(BlueToothActivity.this, "open failed", Toast.LENGTH_SHORT).show();
}else {
if (result.getResultCode() == RESULT_CANCELED){
Toast.makeText(BlueToothActivity.this,"用戶取消",Toast.LENGTH_SHORT);
}
}
}
});
3、接收藍牙狀態(tài)的改變
通過廣播去接收藍牙狀態(tài)的改變
class BluetoothStateChangeReceiver extends BroadcastReceiver{
public int DEFAULT_VALUE_BLUETOOTH = 1000;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,DEFAULT_VALUE_BLUETOOTH);
switch(state){
case BluetoothAdapter.STATE_ON:
Log.d(TAG, "onReceive: open");
break;
case BluetoothAdapter.STATE_OFF:
Log.d(TAG, "onReceive: off");
break;
case BluetoothAdapter.STATE_TURNING_ON :
Log.d(TAG, "onReceive: 正在打開");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.d(TAG, "onReceive: 正在關(guān)閉");
break;
}
}
}
}
別忘了廣播的注冊和解注冊
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); stateReceiver = new BluetoothStateChangeReceiver() ; registerReceiver(stateReceiver,filter);
4、掃描其他的設(shè)備
同樣通過廣播接收,action是BluetoothDevice.ACTION_FOUND
class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 從intent對象中獲取藍牙設(shè)備的信息
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 當發(fā)現(xiàn)新設(shè)備不存在于配對列表中時添加
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
blueNames.add(device.getName()+"\t"+device.getAddress());
}
blueAdpater.notifyDataSetChanged();
Log.d(TAG, "onReceive: " + device.getName());
}
}
}
動態(tài)注冊廣播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(mReceiver,filter);
開啟掃描
mBluetoothAdapter.startDiscovery();
5、藍牙配對
public class BondReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch(device.getBondState()){
case BluetoothDevice.BOND_BONDED:
Log.d(TAG, "onReceive: 配對完成");
break;
case BluetoothDevice.BOND_BONDING:
Log.d(TAG, "onReceive: 正在配對");
break;
case BluetoothDevice.BOND_NONE:
Log.d(TAG, "onReceive: 取消配對");
break;
}
}
}
}
6、獲取已經(jīng)配對的設(shè)備
已經(jīng)配對的設(shè)備會被存儲起來,通過BluetoothAdpater直接獲取即可
Set<BluetoothDevice> paireDevices = mBluetoothAdapter.getBondedDevices();
if (paireDevices.size()>0){
for (BluetoothDevice pairedDevice : pairedDevices) {
blueNames.add(pairedDevice.getName()+" "+pairedDevice.getAddress());
Log.d(TAG, "onClick: "+pairedDevice.getName());
}
}
7、連接設(shè)備
想要在兩臺設(shè)備之間創(chuàng)建連接,必須實現(xiàn)客戶端和服務端機制,他們之間使用套接字機制進行連接,服務端開放服務器套接字,客戶端通過MAC地址向服務端發(fā)起連接??蛻舳撕头斩艘圆煌姆绞将@得BluetoothSocket,當客戶端和服務端在同一個RFCOMM通道上分別擁有已連接的BluetoothSocket時,將他們視為彼此已經(jīng)連接,于是每臺設(shè)備都獲得輸入和輸出流式傳輸,并開始傳輸數(shù)據(jù)。
連接技術(shù)
一種實現(xiàn)技術(shù)是自動將每臺設(shè)備準備為一個服務器,從而使每臺設(shè)備開放一個服務套接字并偵聽連接,在此情況下,任何一臺設(shè)備都可以發(fā)起與另一臺設(shè)備的連接并稱為客戶端。
服務器
設(shè)置服務器套接字并接受連接,步驟依次如下
1、調(diào)用listenUsingRfcommWithServiceRecord()獲取一個BluetoothServerSocket, 該函數(shù)需要兩個參數(shù),第一個是服務器的名稱,自己取一個即可,第二個是UUID,用來對信息做唯一性標識,我們可以從網(wǎng)上眾多UUID生成器中隨機的生成一個,然后使用UUID.fromString(String)初始化一個UUID。
2、通過accept()函數(shù)開始偵聽連接請求
只有遠程設(shè)備發(fā)送的連接請求中UUID與使用此套接字注冊的UUID相匹配時服務器才會接受請求,accept函數(shù)會返回已連接的BluetoothSocket
3、連接成功后調(diào)用close()關(guān)閉BluetoothSocket
private class AcceptThread extends Thread{
private final BluetoothServerSocket mmServerSocket;
private String mSocketType;
public AcceptThread(boolean secure){
BluetoothServerSocket tmp = null;
mSocketType = secure ? "secure" : "Insercure";
try{
if (secure){
tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,MY_UUID_SECURE);
}else{
tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_INSECURE,MY_UUID_INSECURE);
}
} catch (IOException e) {
Log.e(TAG,"socket type"+ mSocketType + "listen() failed",e);
}
mmServerSocket = tmp;
}
@Override
public void run() {
Log.d(TAG, "Socket Type: " + mSocketType +
"BEGIN mAcceptThread" + this);
setName("AcceptThread"+ mSocketType);
BluetoothSocket socket = null;
Log.d(TAG, "run: 開始監(jiān)聽");
while (true){
try{
socket = mmServerSocket.accept();
Log.d("acceptThread", "run: 連接成功");
connected(socket,socket.getRemoteDevice(),mSocketType);
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
break;
}
}
Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
}
public void cancel() {
Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e);
}
}
}
上面的secure和Insecure只是使用了不同的UUID而已。
客戶端
遠程設(shè)備開啟監(jiān)聽后,我們就發(fā)起向此設(shè)備的連接,首先必須先獲得遠程設(shè)備的BluetoothDevice對象,然后獲取BluetoothSocket發(fā)起連接。
基本步驟如下
1、使用BluetoothDevice通過調(diào)用createRfcommSocketToServiceRecord(UUID) 獲取 BluetoothSocket。
2、通過connect發(fā)起連接
private class ConnectThread extends Thread{
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private String mSocketType;
public ConnectThread(BluetoothDevice device, boolean secure){
mmDevice = device;
BluetoothSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
try {
if (secure){
tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
}else {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID_INSECURE);
}
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
}
mmSocket = tmp;
}
@Override
public void run() {
Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType);
setName("ConnectThred"+mSocketType);
// 總是取消發(fā)現(xiàn),因為它會減慢連接
bluetoothAdapter.cancelDiscovery();
// connect
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
Log.d(TAG, "run: socket連接成功");
} catch (IOException e) {
// Close the socket
Log.d(TAG, "run: 關(guān)閉socket");
try {
mmSocket.close();
} catch (IOException e2) {
Log.e(TAG, "unable to close() " + mSocketType +
" socket during connection failure", e2);
}
return;
}
connected(mmSocket,mmDevice,mSocketType);
}
public void cancel(){
try{
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e);
}
}
}
發(fā)送數(shù)據(jù)
連接成功后,我們就可以通過socket發(fā)送數(shù)據(jù)了,客戶端的Socket對象是BluetoothSocket, 服務端的socket是BluetoothServerSocket,特別注意不要混淆了。使用getInputStream和getOutputStream分別獲取通過套接字處理數(shù)據(jù)傳輸?shù)?code>InputStream和OutputStream。寫數(shù)據(jù)比較簡單,但是讀數(shù)據(jù)就需要一個單獨的線程一直監(jiān)聽才行。
private class ConnectedThread extends Thread{
private final BluetoothSocket mmSocket;
private InputStream mmInStream;
private OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket, String socketType) throws IOException {
Log.d(TAG, "create ConnectedThread: " + socketType);
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try{
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
if (socket != null){
tmpOut.write(new String("hello").getBytes());
Log.d(TAG, "ConnectedThread: socket不是null");
}
} catch (IOException e) {
Log.e(TAG,"temp socket not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
// mmOutStream.write(new String("hello").getBytes());
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
while (true){
try{
bytes = mmInStream.read(buffer);
// send the bytes to the ui Activity
String text = encodeByteToString(buffer,bytes);
Log.d(TAG, "run: 收到消息:"+ text);
chatItems.add(text);
mHandler.sendMessage(mHandler.obtainMessage());
} catch (IOException e) {
Log.d(TAG, "run: 沒有收到消息");
e.printStackTrace();
break;
}
}
}
public String encodeByteToString(byte[] data,int length) {
byte[] temp = new byte[length];
for (int i = 0; i < length; i++) {
temp[i] = data[i];
}
try {
return new String(temp,"utf-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
public void write(byte[] buffer){
try{
mmOutStream.write(buffer);
// mHandler.obtainMessage(Constants.MESSAGE_WRITE,-1,-1,buffer).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
}
}
public void cancel(){
try{
mmSocket.close();
Log.d(TAG, "cancel: connectedThread");
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
上面的例子我主要是學習官網(wǎng)上的藍牙聊天項目寫的代碼,大家也可以直接看官網(wǎng)項目。從上面的例子中可知,接受到的數(shù)據(jù)流都是一些二進制,要用到實際的項目中還需要進行一定的編碼和轉(zhuǎn)換。也就是自己編寫一些協(xié)議,學過socket編程的同學一定都懂,其實藍牙已經(jīng)有很多的好用的協(xié)議了,就比如AVRCP(Audio Video Remote Control Profile),定義了藍牙設(shè)備和audio/video控制功能通信的特點和過程, 結(jié)合MediaSession 可以很容易的實現(xiàn)設(shè)備音視頻控制。
到此這篇關(guān)于android藍牙簡單開發(fā)示例教程的文章就介紹到這了,更多相關(guān)android藍牙開發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中實現(xiàn)水平滑動(橫向滑動)ListView示例
這篇文章主要介紹了Android中實現(xiàn)水平滑動(橫向滑動)ListView示例,本文用自己封裝一個控件的方法解決了這個需求,需要的朋友可以參考下2015-06-06
使用Compose制作抖音快手視頻進度條Loading動畫效果
這篇文章主要為大家介紹了使用Compose制作抖音快手視頻進度條Loading動畫效果,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
Flutter通過Container實現(xiàn)時間軸效果
時間軸是前端UI經(jīng)常用到的效果,本文講解下Flutter如何通過Container實現(xiàn),感興趣的朋友可以了解下2021-05-05
Android仿微信列表滑動刪除 如何實現(xiàn)滑動列表SwipeListView
這篇文章主要為大家詳細介紹了Android仿微信列表滑動刪除,如何實現(xiàn)滑動列表SwipeListView,感興趣的小伙伴們可以參考一下2016-08-08
Android8.1原生系統(tǒng)網(wǎng)絡感嘆號消除的方法
這篇文章主要介紹了Android8.1原生系統(tǒng)網(wǎng)絡感嘆號消除的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05
AccessibilityService實現(xiàn)微信發(fā)紅包功能
這篇文章主要為大家詳細介紹了AccessibilityService實現(xiàn)微信發(fā)紅包功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12
Android ListView實現(xiàn)上拉加載更多和下拉刷新功能
這篇文章主要為大家詳細介紹了Android ListView實現(xiàn)上拉加載更多和下拉刷新功能,介紹了ListView刷新原理及實現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-05-05

