前言:藍牙聊天App設計全部有三篇文章(一、UI界面設計,二、藍牙搜索配對連接實現(xiàn),三、藍牙連接聊天),這篇文章是:三、藍牙連接聊天。
課程1:Android Studio小白安裝教程,以及第一個Android項目案例“Hello World”的調(diào)試運行
課程2:藍牙聊天App設計1:Android Studio制作藍牙聊天通訊軟件(UI界面設計)
課程3:藍牙聊天App設計2:Android Studio制作藍牙聊天通訊軟件(藍牙搜索配對連接)
課程4:藍牙聊天App設計3:Android Studio制作藍牙聊天通訊軟件(完結,藍牙連接聊天,結合生活情景進行藍牙通信的通俗講解,以及代碼功能實現(xiàn),內(nèi)容詳細,講解通俗易懂)
本次任務:
本次項目的任務是制作一個基于藍牙通信協(xié)議的藍牙聊天軟件。具體內(nèi)容就是用戶雙方在手機上安裝本軟件后,通過藍牙連接后進行“微信聊天”。文章末尾附項目下載鏈接共享。
效果圖與視頻:
用戶1效果圖
用戶2效果圖
效果視頻
基于Android的藍牙通信聊天軟件(效果視頻)
文章說明:
本文將結合生活情景,講解藍牙通信原理。具體講解代碼實現(xiàn)過程并詳細解析代碼含義。本文的前提是你已經(jīng)能夠掌握藍牙的搜索展示,連接配對功能,如果還未掌握,建議先觀看前面文章,一步一個腳印來。
在明確任務和目標效果后,下面開始本項目的內(nèi)容講解:
上圖是藍牙通信的原理圖,建議看完,清楚整個功能流程后,再針對每個環(huán)節(jié)進行開發(fā)。為了進一步加深理解,下面舉一個情景例子。
周末,你在家里一個人呆著無聊,就通過發(fā)朋友圈(開啟BluetoothServerSocket)的方式,邀請一位朋友來家里做客(由于你家太小,只能待兩個人)。你的朋友老王看到你的朋友圈(BluetoothServerSocket)后,立刻就私信你,詢問自己能否去你家里做客聊天(發(fā)起訪問通道請求)。你答應了,給了老王你家(BluetoothSocket)的詳細地址,并立刻刪除剛才的朋友圈(關閉BluetoothServerSocket),防止其他朋友看到后也要來做客。接下來,你就一直待在家里,等待老王上門(Accept)。老王到了你家門口后,就一直按門鈴(connect),直到你開門邀請進去。到了這個時候,其實你們就已經(jīng)處在同個聊天場所(BluetoothSocket)了。坐下聊天過程中,你和老王輪流說話(write),但是無論你們是否在說話,耳朵卻一直保持傾聽(read)的狀態(tài)。到了中午時,由于老王下午還有事要處理,便與你告別,離開你家(斷開BluetoothSocket)。老王離開后,你就重新發(fā)了朋友圈(開啟BluetoothServerSocket),等待下一位朋友的私信做客。
概念的提前解釋
下面的內(nèi)容是編程過程中會出現(xiàn)的常見詞,在這里先提前通俗解析一下,起碼得在心中有個概念,這是干什么用的,有什么區(qū)別?
1.Socket(套接字)
用于進程間的通信,創(chuàng)建一個Socket,其實就是相當于創(chuàng)建一個聊天通道(BluetoothServerSocket和BluetoothSocket),也就是聊天場所的建立。
2.Handler
用于線程間的通信。與前面的Binder/Socket用于進程間通信不同,Handler用于同進程的線程間通信。用最簡單的話描述: handler其實就是子線程運行并生成Message后,Looper獲取message并傳遞給Handler。本質(zhì)是“消息池”、“快遞中轉站”,實現(xiàn)不同線程間的信息交互。
例如:在聊天場景中,將聊天線程中需更新UI的操作信息傳遞到UI主線程,從而實現(xiàn)聊天線程對UI的更新處理,最終實現(xiàn)異步消息的處理
3.Handler中obtainMessage與new Message的區(qū)別:
(1)obtainmessage() 相當于向快遞中轉站(Handler)拿一個空的舊盒子(Message),不需要另開辟空間,obtianmessage指循環(huán)利用舊盒子
(2)new Message() 相當于向快遞中轉站(Handler)拿一個空的新盒子(Message),需要重新申請,效率低
4.Message
message其實相當于一個快遞盒子,是線程間的傳遞媒介。聊天子線程把需要更新的文字信息包裝在message這個盒子里,然后把message盒子送到Handler這個“物流中心”,Handler又把需要更新的內(nèi)容準確傳遞到主線程手里,然后更新UI。message的結構主要包括what字段和obj字段,what用來標明信息類型(int型),obj用來寄放信息內(nèi)容(String型),例如:MSG_GOT_DATA代表告訴Handler,我已經(jīng)正常收到了信息,信息的內(nèi)容是“我想問一下你現(xiàn)在吃飯沒?”,Message的結構如下所示:
what | obj |
---|---|
MSG_GOT_DATA | 我想問一下你現(xiàn)在吃飯沒? |
5.sendMessage和sendEmptyMessage的區(qū)別
二者的作用都是線程向Handler發(fā)送信息message(也就是寄快遞)
(1)sendEmptyMessage(int what) 與sendMessage(Message msg) 相比,只有盒子的what空間能使用,性能上比sendMessage快,但局限性很明顯
(2)sendMessage(Message msg) 相當于向快遞中轉站(Handler)寄一整個盒子,其中盒子的what空間和obj空間都能使用
what內(nèi)容的獲取 | obj內(nèi)容的獲取 |
---|---|
int what = message.what | String obj = message.obj |
what = MSG_GOT_DATA | obj = “我想問一下你現(xiàn)在吃飯沒?” |
Constant.java是用來預先定義一些下面可能需要用到的常量(其實就是對sendEmptyMessage(int what)中“what”內(nèi)容的規(guī)定)
package com.example.wyb.btw3.connect;
/**
* Created by WYB on 2023/4/24.
*/
public class Constant {
public static final String CONNECTTION_UUID = "00001101-0000-1000-8000-00805F9B34FB";
/**
* 開始監(jiān)聽
*/
public static final int MSG_START_LISTENING = 1;
/**
* 結束監(jiān)聽
*/
public static final int MSG_FINISH_LISTENING = 2;
/**
* 有客戶端連接
*/
public static final int MSG_GOT_A_CLINET = 3;
/**
* 連接到服務器
*/
public static final int MSG_CONNECTED_TO_SERVER = 4;
/**
* 獲取到數(shù)據(jù)
*/
public static final int MSG_GOT_DATA = 5;
/**
* 出錯
*/
public static final int MSG_ERROR = -1;
}
//明確快遞中轉站(Handler)收到what類型后,向UI界面?zhèn)鬟f什么更新內(nèi)容
private class MyHandler extends Handler {
public void handleMessage(Message message) {
super.handleMessage(message);
switch (message.what) {
case Constant.MSG_GOT_DATA: //這些代表什么,文章前面的定義有說明
showToast("data:" + String.valueOf(message.obj));
break;
case Constant.MSG_ERROR:
showToast("error:" + String.valueOf(message.obj));
Log.e("提示","error:" + String.valueOf(message.obj));
break;
case Constant.MSG_CONNECTED_TO_SERVER:
showToast("連接到服務端");
break;
case Constant.MSG_GOT_A_CLINET:
showToast("找到客戶端");
break;
}
}
}
功能實現(xiàn)
一、創(chuàng)建請求訪問通道并開啟端口監(jiān)聽(服務端):
請求訪問通道的建立:需要通過創(chuàng)建一個AcceptThread線程來實現(xiàn)。之所以需要創(chuàng)建線程,是因為Accept()會處于阻塞狀態(tài),一直監(jiān)聽,等待連接請求。所以需要創(chuàng)建一個線程來專門干這事。
mAcceptThread = new AcceptThread(mblueToothController.getAdapter(),mUIHandler);
mAcceptThread.start();
AcceptThread.java的完整代碼如下(每段代碼都有詳細功能解析)
package com.example.wyb.btw3.connect;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import java.io.IOException;
import java.util.UUID;
/**
* Created by WYB on 2023/4/24.
*/
public class AcceptThread extends Thread {
private static final String NAME = "BlueToothClass";
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
private final BluetoothServerSocket mmServerSocket;
private final BluetoothAdapter mBluetoothAdapter;
private final Handler mHandler;
private ConnectedThread mConnectedThread;
public AcceptThread(BluetoothAdapter adapter, Handler handler) {
mBluetoothAdapter = adapter;
mHandler = handler;
BluetoothServerSocket tmp = null;
try {
// MY_UUID是應用程序的UUID,客戶端代碼使用相同的UUID,服務端開啟端口并返回一個BluetoothServerSocket,等待客戶端請求
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
//run函數(shù)是線程執(zhí)行start()后執(zhí)行的函數(shù)
public void run() {
BluetoothSocket socket;
//持續(xù)監(jiān)聽,直到出現(xiàn)異?;蚍祷豷ocket
while (true) {
try {
mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
socket = mmServerSocket.accept();//accept處于阻塞狀態(tài),直到收到客戶端請求,并返回給客戶端一個BluetoothSocket對象,也就是聊天通道的建立
} catch (IOException e) {
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
break;
}
// 如果一個連接被接受
if (socket != null) {
// 在單獨的線程中完成管理連接的工作
manageConnectedSocket(socket);
try {
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
private void manageConnectedSocket(BluetoothSocket socket) {
//只支持同時處理一個連接,因為多個連接的話涉及內(nèi)容復雜,所以本項目只接受一個設備連接。如果已經(jīng)存在一個在運行的聊天線程,則關閉掉
if( mConnectedThread != null) {
mConnectedThread.cancel();
}
mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
//創(chuàng)建一個聊天線程(ConnectedThread,具體實現(xiàn)文章后面會詳細解讀)
mConnectedThread = new ConnectedThread(socket, mHandler);
mConnectedThread.start();
}
/**
* 取消監(jiān)聽socket,使此線程關閉
*/
public void cancel() {
try {
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) { }
}
//發(fā)送信息的實現(xiàn)
public void sendData(byte[] data) {
if( mConnectedThread!=null){
mConnectedThread.write(data);
}
}
}
二、發(fā)起聊天請求并建立通信通道(客戶端):
device指服務端的藍牙設備,mblueToothController.getAdapter()指本客戶端的藍牙適配器。創(chuàng)建線程是因為connect()這個函數(shù)會發(fā)生阻塞。
mConnectThread = new ConnectThread(device,mblueToothController.getAdapter(),mUIHandler);
mConnectThread.start();
ConnectThread.java的完整代碼如下(每段代碼都有詳細功能解析)
package com.example.wyb.btw3.connect;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* Created by WYB on 2023/4/24.
*/
public class ConnectThread extends Thread {
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private BluetoothAdapter mBluetoothAdapter;
private final Handler mHandler;
private ConnectedThread mConnectedThread;
public ConnectThread(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) {
BluetoothSocket tmp = null;
mmDevice = device;
mBluetoothAdapter = adapter;
mHandler = handler;
// 用BluetoothSocket連接到給定的藍牙設備
try {
//通過同一個UUID向服務端device發(fā)起連接請求,服務端接收請求后會建立并返回一個BluetoothSocket聊天通道
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
//由于請求過程處于阻塞狀態(tài),所以整個請求過程得用線程
public void run() {
// 關閉藍牙的搜索功能,避免請求過程出現(xiàn)數(shù)據(jù)錯誤
mBluetoothAdapter.cancelDiscovery();
try {
// 通過BluetoothSocket通道發(fā)起設備連接請求,阻塞運行直到成功或拋出異常
mmSocket.connect();
} catch (Exception connectException) {
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, connectException));
// 如果無法連接則關閉socket并退出
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// 在單獨的線程中完成管理連接的工作
manageConnectedSocket(mmSocket);
}
//開門成功后就開始下一個任務
private void manageConnectedSocket(BluetoothSocket mmSocket) {
//發(fā)送連接成功提示文字
mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
//創(chuàng)建連接后的處理線程,即通信線程
mConnectedThread = new ConnectedThread(mmSocket, mHandler);
mConnectedThread.start();
}
/**
* 取消正在進行的連接并關閉socket
*/
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
/**
* 發(fā)送數(shù)據(jù)
*/
public void sendData(byte[] data) {
if( mConnectedThread!=null){
mConnectedThread.write(data);
}
}
}
三、數(shù)據(jù)交互與斷開(服務端與客戶端的聊天實現(xiàn))
數(shù)據(jù)交互階段的原理其實特別簡單,就是服務端和客戶端都分別開啟一個聊天線程,聊天線程的主要功能其實就一個——一直執(zhí)行read()。也就是處于監(jiān)聽狀態(tài),等待另一方write()。
ConnectedThread.java的完整代碼如下(每段代碼都有詳細功能解析)
package com.example.wyb.btw3.connect;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Created by WYB on 2023/4/24.
*/
public class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
mHandler = handler;
try {
tmpIn = socket.getInputStream(); //輸入流
tmpOut = socket.getOutputStream(); //輸出流
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // 用于流的緩沖存儲
int bytes; // 從read()返回bytes
// 持續(xù)監(jiān)聽InputStream(也就是對方發(fā)來的信息),直到出現(xiàn)異常
while (true) {
try {
// 從InputStream讀取數(shù)據(jù),也就是我前面提到的一直保持read()
bytes = mmInStream.read(buffer);
// 將獲得的bytes發(fā)送到UI層activity
if( bytes >0) {
//obtainMessage可以看前面的解析
Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA, new String(buffer, 0, bytes, "utf-8"));
mHandler.sendMessage(message);
}
Log.d("GOTMSG", "message size" + bytes);
} catch (IOException e) {
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
break;
}
}
}
/**
* 在main中調(diào)用此函數(shù),將數(shù)據(jù)發(fā)送到遠端設備中
*/
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/**
* 在main中調(diào)用此函數(shù),斷開連接
*/
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
四、bluetoothChat項目功能demo分享:
鏈接:https://pan.baidu.com/s/1z8tW3aA7a5knKxiwlE3BFw 提取碼:3d53文章來源:http://www.zghlxwxcb.cn/news/detail-528241.html
五、bluetoothChat最終App分享:
鏈接:https://pan.baidu.com/s/1XOpO62n8e2SfdyJzWf5Mwg 提取碼:12pf文章來源地址http://www.zghlxwxcb.cn/news/detail-528241.html
到了這里,關于藍牙聊天App設計3:Android Studio制作藍牙聊天通訊軟件(完結,藍牙連接聊天,結合生活情景進行藍牙通信的通俗講解,以及代碼功能實現(xiàn),內(nèi)容詳細,講解通俗易懂)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!