本文通过AndroidStudio开发手机应用软件,实现蓝牙连接功能,并且能发送消息给HC-05蓝牙,也能接收HC-05回传的消息。本文在【AndroidStudio如何进行手机应用开发?】一文的基础上,进行了一次总结性应用,以下阐述工程建立过程,最终给出应用展示。
activity_main.xml
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn_linkBlueTooth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="linkBlueTooth"
android:text="连接蓝牙"
android:textSize="12sp"/>
<TextView
android:id="@+id/tv_blueToothStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="蓝牙状态:未连接"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="接收消息:"
android:textSize="18sp"/>
<EditText
android:id="@+id/etxt_receiveMessage"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@android:drawable/editbox_background"
android:gravity="top"
android:scrollbars="vertical"
android:singleLine="false"
android:focusable="false"
android:editable="false"
android:text=""
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="发送消息:"
android:textSize="18sp"/>
<EditText
android:id="@+id/etxt_sendMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:text=""
android:textSize="18sp"
tools:ignore="SpeakableTextPresentCheck"/>
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="send"
android:text="发送"
android:textSize="12sp"/>
</LinearLayout>
<uses-permissionandroid:name="android.permission.BLUETOOTH"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permissionandroid:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permissionandroid:name="ohos.permission.DISCOVER_BLUETOOTH"/>
(2)BluetoothChatUtil.java
packagecom.example.blcommunicate;
importandroid.bluetooth.BluetoothAdapter;
importandroid.bluetooth.BluetoothDevice;
importandroid.bluetooth.BluetoothServerSocket;
importandroid.bluetooth.BluetoothSocket;
importandroid.content.Context;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.util.Log;
importjava.io.ByteArrayInputStream;
importjava.io.DataInputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.util.UUID;
/**
*该类的工作:建立和管理蓝牙连接。
*共有三个线程。mAcceptThread线程用来监听socket连接(服务端使用).
*mConnectThread线程用来连接serversocket(客户端使用)。
*mConnectedThread线程用来处理socket发送、接收数据。(客户端和服务端共用)
*/
publicclassBluetoothChatUtil{
privatestaticfinalStringTAG="BluetoothChatClient";
privatestaticfinalbooleanD=true;
//服务名SDP
privatestaticfinalStringSERVICE_NAME="BluetoothChat";
//uuidSDP
privatestaticfinalUUIDSERVICE_UUID=UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
//蓝牙适配器
privatefinalBluetoothAdaptermAdapter;
privateHandlermHandler;
privateAcceptThreadmAcceptThread;
privateConnectThreadmConnectThread;
privateConnectedThreadmConnectedThread;
privateintmState;
privatestaticBluetoothChatUtilmBluetoothChatUtil;
privateBluetoothDevicemConnectedBluetoothDevice;
//常数,指示当前的连接状态
publicstaticfinalintSTATE_NONE=0;//当前没有可用的连接
publicstaticfinalintSTATE_LISTEN=1;//现在侦听传入的连接
publicstaticfinalintSTATE_CONNECTING=2;//现在开始连接
publicstaticfinalintSTATE_CONNECTED=3;//现在连接到远程设备
publicstaticfinalintSTATAE_CONNECT_FAILURE=4;//连接失败
publicstaticfinalintMESSAGE_DISCONNECTED=5;//断开连接
publicstaticfinalintSTATE_CHANGE=6;//连接状态改变
publicstaticfinalintMESSAGE_READ=7;
publicstaticfinalintMESSAGE_WRITE=8;
publicstaticfinalStringDEVICE_NAME="device_name";
publicstaticfinalStringREAD_MSG="read_msg";
/**
*构造函数。准备一个新的bluetoothchat会话。
*@paramcontext
*/
privateBluetoothChatUtil(Contextcontext){
mAdapter=BluetoothAdapter.getDefaultAdapter();
mState=STATE_NONE;
}
publicstaticBluetoothChatUtilgetInstance(Contextc){
if(null==mBluetoothChatUtil){
mBluetoothChatUtil=newBluetoothChatUtil(c);
}
returnmBluetoothChatUtil;
}
publicvoidregisterHandler(Handlerhandler){
mHandler=handler;
}
publicvoidunregisterHandler(){
mHandler=null;
}
/**
*设置当前状态的聊天连接
*@paramstate整数定义当前连接状态
*/
privatesynchronizedvoidsetState(intstate){
if(D)Log.d(TAG,"setState()"+mState+"->"+state);
mState=state;
//给新状态的处理程序,界面活性可以更新
mHandler.obtainMessage(STATE_CHANGE,state,-1).sendToTarget();
}
/**
*返回当前的连接状态。*/
publicsynchronizedintgetState(){
returnmState;
}
publicBluetoothDevicegetConnectedDevice(){
returnmConnectedBluetoothDevice;
}
/**
*开始聊天服务。特别acceptthread开始
*开始服务器模式。*/
publicsynchronizedvoidstartListen(){
if(D)Log.d(TAG,"start");
//取消任何线程正在运行的连接
if(mConnectedThread!=null){
mConnectedThread.cancel();
mConnectedThread=null;
}
//启动线程来监听一个bluetoothserversocket
if(mAcceptThread==null){
mAcceptThread=newAcceptThread();
mAcceptThread.start();
}
setState(STATE_LISTEN);
}
/**
*开始connectthread启动连接到远程设备。
*@paramdevice连接的蓝牙设备
*/
publicsynchronizedvoidconnect(BluetoothDevicedevice){
if(D)Log.d(TAG,"connectto:"+device);
//取消任何线程试图建立连接
if(mState==STATE_CONNECTING){
if(mConnectThread!=null){
mConnectThread.cancel();
mConnectThread=null;
}
}
//取消任何线程正在运行的连接
if(mConnectedThread!=null){
mConnectedThread.cancel();
mConnectedThread=null;
}
//启动线程连接到远程设备
mConnectThread=newConnectThread(device);
mConnectThread.start();
setState(STATE_CONNECTING);
}
/**
*开始ConnectedThread开始管理一个蓝牙连接,传输、接收数据.
*@paramsocketsocket连接
*@paramdevice已连接的蓝牙设备
*/
publicsynchronizedvoidconnected(BluetoothSocketsocket,BluetoothDevicedevice){
if(D)Log.d(TAG,"connected");
//取消任何线程正在运行的连接
if(mConnectedThread!=null){
mConnectedThread.cancel();
mConnectedThread=null;
}
//启动线程管理连接和传输
mConnectedThread=newConnectedThread(socket);
mConnectedThread.start();
//把连接设备的名字传到uiActivity
mConnectedBluetoothDevice=device;
Messagemsg=mHandler.obtainMessage(STATE_CONNECTED);
Bundlebundle=newBundle();
bundle.putString(DEVICE_NAME,device.getName());
msg.setData(bundle);
mHandler.sendMessage(msg);
setState(STATE_CONNECTED);
}
/**
*停止所有的线程
*/
publicsynchronizedvoiddisconnect(){
if(D)Log.d(TAG,"disconnect");
if(mConnectThread!=null){mConnectThread.cancel();mConnectThread=null;}
if(mConnectedThread!=null){mConnectedThread.cancel();mConnectedThread=null;}
if(mAcceptThread!=null){mAcceptThread.cancel();mAcceptThread=null;}
setState(STATE_NONE);
}
/**
*WritetotheConnectedThreadinanunsynchronizedmanner
*@paramoutThebytestowrite
*@seeConnectedThread#write(byte[])
*/
publicvoidwrite(byte[]out){
//创建临时对象
ConnectedThreadr;
//同步副本的connectedthread
synchronized(this){
if(mState!=STATE_CONNECTED)return;
r=mConnectedThread;
}
//执行写同步
r.write(out);
}
/**
*IndicatethattheconnectionattemptfailedandnotifytheUIActivity.
*/
privatevoidconnectionFailed(){
//发送失败的信息带回活动
Messagemsg=mHandler.obtainMessage(STATAE_CONNECT_FAILURE);
mHandler.sendMessage(msg);
mConnectedBluetoothDevice=null;
setState(STATE_NONE);
}
/**
*IndicatethattheconnectionwaslostandnotifytheUIActivity.
*/
publicvoidconnectionLost(){
//发送失败的信息带回Activity
Messagemsg=mHandler.obtainMessage(MESSAGE_DISCONNECTED);
mHandler.sendMessage(msg);
mConnectedBluetoothDevice=null;
setState(STATE_NONE);
}
/**
*本线程侦听传入的连接。
*它运行直到连接被接受(或取消)。
*/
privateclassAcceptThreadextendsThread{
//本地服务器套接字
privatefinalBluetoothServerSocketmServerSocket;
publicAcceptThread(){
BluetoothServerSockettmp=null;
//创建一个新的侦听服务器套接字
try{
tmp=mAdapter.listenUsingRfcommWithServiceRecord(
SERVICE_NAME,SERVICE_UUID);
//tmp=mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME,SERVICE_UUID);
}catch(IOExceptione){
Log.e(TAG,"listen()failed",e);
}
mServerSocket=tmp;
}
publicvoidrun(){
if(D)Log.d(TAG,"BEGINmAcceptThread"+this);
setName("AcceptThread");
BluetoothSocketsocket=null;
//循环,直到连接成功
while(mState!=STATE_CONNECTED){
try{
//这是一个阻塞调用返回成功的连接
//mServerSocket.close()在另一个线程中调用,可以中止该阻塞
socket=mServerSocket.accept();
}catch(IOExceptione){
Log.e(TAG,"accept()failed",e);
break;
}
//如果连接被接受
if(socket!=null){
synchronized(BluetoothChatUtil.this){
switch(mState){
caseSTATE_LISTEN:
caseSTATE_CONNECTING:
//正常情况。启动ConnectedThread。
connected(socket,socket.getRemoteDevice());
break;
caseSTATE_NONE:
caseSTATE_CONNECTED:
//没有准备或已连接。新连接终止。
try{
socket.close();
}catch(IOExceptione){
Log.e(TAG,"Couldnotcloseunwantedsocket",e);
}
break;
}
}
}
}
if(D)Log.i(TAG,"ENDmAcceptThread");
}
publicvoidcancel(){
if(D)Log.d(TAG,"cancel"+this);
try{
mServerSocket.close();
}catch(IOExceptione){
Log.e(TAG,"close()ofserverfailed",e);
}
}
}
/**
*本线程用来连接设备
*
*/
privateclassConnectThreadextendsThread{
privateBluetoothSocketmmSocket;
privatefinalBluetoothDevicemmDevice;
publicConnectThread(BluetoothDevicedevice){
mmDevice=device;
BluetoothSockettmp=null;
//得到一个bluetoothsocket
try{
mmSocket=device.createRfcommSocketToServiceRecord
(SERVICE_UUID);
}catch(IOExceptione){
Log.e(TAG,"create()failed",e);
mmSocket=null;
}
}
publicvoidrun(){
Log.i(TAG,"BEGINmConnectThread");
try{
//socket连接,该调用会阻塞,直到连接成功或失败
mmSocket.connect();
}catch(IOExceptione){
Log.e(TAG,"IOException");
connectionFailed();
try{//关闭这个socket
mmSocket.close();
}catch(IOExceptione2){
e2.printStackTrace();
}
return;
}
//启动连接线程
connected(mmSocket,mmDevice);
}
publicvoidcancel(){
try{
mmSocket.close();
}catch(IOExceptione){
Log.e(TAG,"close()ofconnectsocketfailed",e);
}
}
}
/**
*本线程server和client共用.
*它处理所有传入和传出的数据。
*/
privateclassConnectedThreadextendsThread{
privatefinalBluetoothSocketmmSocket;
privatefinalInputStreammmInStream;
privatefinalOutputStreammmOutStream;
publicConnectedThread(BluetoothSocketsocket){
Log.d(TAG,"createConnectedThread");
mmSocket=socket;
InputStreamtmpIn=null;
OutputStreamtmpOut=null;
//获得bluetoothsocket输入输出流
try{
tmpIn=socket.getInputStream();
tmpOut=socket.getOutputStream();
}catch(IOExceptione){
Log.e(TAG,"没有创建临时sockets",e);
}
mmInStream=tmpIn;
mmOutStream=tmpOut;
}
publicvoidrun(){
//监听输入流
while(true){
try{
byte[]buffer=newbyte[512];
//读取输入流
intbytes=mmInStream.read(buffer);
//发送获得的字节的uiactivity
Messagemsg=mHandler.obtainMessage(MESSAGE_READ);
Bundlebundle=newBundle();
bundle.putByteArray(READ_MSG,buffer);
msg.setData(bundle);
mHandler.sendMessage(msg);
}catch(IOExceptione){
Log.e(TAG,"disconnected",e);
connectionLost();
break;
}
}
}
/**
*向外发送。
*@parambuffer发送的数据
*/
publicvoidwrite(byte[]buffer){
try{
mmOutStream.write(buffer);
//分享发送的信息到Activity
mHandler.obtainMessage(MESSAGE_WRITE,-1,-1,buffer)
.sendToTarget();
}catch(IOExceptione){
Log.e(TAG,"Exceptionduringwrite",e);
}
}
publicvoidcancel(){
try{
mmSocket.close();
}catch(IOExceptione){
Log.e(TAG,"close()ofconnectsocketfailed",e);
}
}
}
publicstaticintByteArrayToInt(byteb[])throwsException{
ByteArrayInputStreambuf=newByteArrayInputStream(b);
DataInputStreamdis=newDataInputStream(buf);
returndis.readInt();
}
}
(3)MainActivity.java
packagecom.example.blcommunicate;
importandroidx.annotation.RequiresApi;
importandroidx.appcompat.app.AppCompatActivity;
importandroidx.core.app.ActivityCompat;
importandroid.Manifest;
importandroid.annotation.SuppressLint;
importandroid.bluetooth.BluetoothAdapter;
importandroid.bluetooth.BluetoothDevice;
importandroid.content.IntentFilter;
importandroid.content.pm.PackageManager;
importandroid.os.Build;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.text.method.ScrollingMovementMethod;
importandroid.util.Log;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.Button;
importandroid.widget.EditText;
importandroid.widget.ListView;
importandroid.widget.TextView;
importandroid.widget.Toast;
importjava.io.File;
importjava.io.IOException;
importjava.text.SimpleDateFormat;
importjava.util.ArrayList;
importjava.util.Date;
importjava.util.Set;
publicclassMainActivityextendsAppCompatActivity{
TextViewtv_blueToothStatus;
Buttonbtn_linkBlueTooth;
Buttonbtn_send;
EditTextetxt_receiveMessage;
EditTextetxt_sendMessage;
privateBluetoothAdapterbluetoothAdapter;
BluetoothChatUtilmBlthChatUtil;
booleanreadOver=true;
StringstrGet="";
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_blueToothStatus=(TextView)findViewById(R.id.tv_blueToothStatus);
btn_linkBlueTooth=(Button)findViewById(R.id.btn_linkBlueTooth);
btn_send=(Button)findViewById(R.id.btn_send);
etxt_receiveMessage=(EditText)findViewById(R.id.etxt_receiveMessage);
etxt_sendMessage=(EditText)findViewById(R.id.etxt_sendMessage);
if(isSupported()){
Toast.makeText(this,"设备支持蓝牙",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this,"设备不支持蓝牙",Toast.LENGTH_SHORT).show();
}
if(bluetoothAdapter.isEnabled()){
//已经打开蓝牙,判断Android版本是否需要添加权限,解决:无法发现蓝牙设备的问题
Toast.makeText(this,"蓝牙已打开",Toast.LENGTH_SHORT).show();
getPermission();
}else{
//开启蓝牙
if(ActivityCompat.checkSelfPermission(this,Manifest.permission.BLUETOOTH_CONNECT)!=PackageManager.PERMISSION_GRANTED){
return;
}
bluetoothAdapter.enable();
//关闭蓝牙:bluetoothAdapter.disable();
}
}
/**
*连接蓝牙
*@paramview
*/
publicvoidlinkBlueTooth(Viewview){
etxt_receiveMessage.setText("");
mBlthChatUtil=BluetoothChatUtil.getInstance(this);
mBlthChatUtil.registerHandler(mHandler);
Set<BluetoothDevice>pairedDevices=bluetoothAdapter.getBondedDevices();
if(pairedDevices.size()>0){
//如果有配对的设备
for(BluetoothDevicedevice:pairedDevices){
//通过arrayadapter在列表中添加设备名称和地址
if(device.getName().equals("HC-05")){
if(bluetoothAdapter.isDiscovering()){
//取消搜索
bluetoothAdapter.cancelDiscovery();
}
if(mBlthChatUtil.getState()==BluetoothChatUtil.STATE_CONNECTED){
showToast("蓝牙已连接");
}else{
mBlthChatUtil.connect(device);
if(mBlthChatUtil.getState()==BluetoothChatUtil.STATE_CONNECTED){
showToast("蓝牙连接成功");
}
}
break;
}
}
}else{
showToast("暂无已配对设备");
}
}
/**
*发送
*@paramview
*/
publicvoidsend(Viewview){
byte[]buffer2=etxt_sendMessage.getText().toString().getBytes();
mBlthChatUtil.write(buffer2);
}
/**
*判断是否设备是否支持蓝牙
*@return是否支持
*/
privatebooleanisSupported(){
//初始化
bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter==null){
returnfalse;
}else{
returntrue;
}
}
/**
*获取权限
*/
@SuppressLint("WrongConstant")
privatevoidgetPermission(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.M){
intpermissionCheck=0;
permissionCheck=this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
permissionCheck+=this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
if(permissionCheck!=PackageManager.PERMISSION_GRANTED){
intACCESS_LOCATION=1;//自定义常量,任意整型
//未获得权限
this.requestPermissions(//请求授权
newString[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION},
ACCESS_LOCATION);
}
}
}
/**
*显示消息
*@paramstr
*/
privatevoidshowToast(Stringstr){
Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
}
/**
*消息句柄
*/
privateHandlermHandler=newHandler(){
@RequiresApi(api=Build.VERSION_CODES.R)
@SuppressLint("HandlerLeak")
publicvoidhandleMessage(Messagemsg){
StringdeviceName=msg.getData().getString(BluetoothChatUtil.DEVICE_NAME);
switch(msg.what){
caseBluetoothChatUtil.STATE_CONNECTED:
showToast("连接成功");
tv_blueToothStatus.setText("蓝牙状态:已连接HC-05");
break;
caseBluetoothChatUtil.STATAE_CONNECT_FAILURE:
showToast("连接失败");
break;
caseBluetoothChatUtil.STATE_CHANGE:
showToast("正在连接设备..");
break;
caseBluetoothChatUtil.MESSAGE_DISCONNECTED:
showToast("与设备断开连接");
break;
//读到另一方传送的消息
caseBluetoothChatUtil.MESSAGE_READ:{
//showToast("接收消息成功");
byte[]buf;
Stringstr;
buf=msg.getData().getByteArray(BluetoothChatUtil.READ_MSG);
str=newString(buf,0,buf.length);
//根据HC-05传来的消息进行相应的处理后显示
if(readOver){
strGet=str.trim();
if(strGet.length()<9){
readOver=false;
return;
}
}
else{
strGet+=str.trim();
readOver=true;
}
etxt_receiveMessage.setText(etxt_receiveMessage.getText()+"\n"+strGet);
break;
}
caseBluetoothChatUtil.MESSAGE_WRITE:{
//showToast("发送消息成功");
break;
}
default:
break;
}
};
};
}
注1:
代码在笔者编译环境中会出现红色的权限检查报错提示,该错误不影响程序运行。
注2:
本工程的代码文件参见:手机应用开发之如何利用蓝牙与HC-05通信?-工程代码;
代码移植方法参见:AndroidStudio如何进行代码移植?。
参考资料:
[1] cacrle. AndroidStudio如何进行手机应用开发?; 2023-04-16 [accessed 2023-04-16].
[2] cacrle. AndroidStudio如何进行代码移植?; 2023-04-16 [accessed 2023-04-16].
[3] suda哇. Android Studio如何进行真机调试; 2020-05-24 [accessed 2023-04-16].
[4] 金胖. Android蓝牙扫描/连接/收发数据; 2019-03-26 [accessed 2023-04-16].
[5] 考研兔萌酱. android蓝牙传输信息,Android 蓝牙(BLE)连接,发送,接收消息; 2021-05-26 [accessed 2023-04-16].
[6] van久. Android-蓝牙通信; 2019-07-01 [accessed 2023-04-16].
[7] 停止的猪头. 编程回忆之Android回忆(蓝牙BluetoothAdapter的搜索和连接); 2014-02-27 [accessed 2023-04-16].
[8] Panner_pan. 蓝牙开发(二)扫描设备; 2018-09-11 [accessed 2023-04-16].
[9] MirkoWu. Android 低功耗蓝牙(BLE)开发(3)-- BluetoothDevice详解; 2016-12-24 [accessed 2023-04-16].
[10] 杨枝甘卢. 安卓开发实现蓝牙通信——两设备相互发消息; 2021-12-13 [accessed 2023-04-16].