一、运行效果图,左边是服务端,右边是客户端。 资源下载路径
https://download.csdn.net/download/qq_37858386/87612932
一、服务端app代码
1、MainActivity.java
package com.hypernano.btserver;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
String TAG = "HPBTSERVERMA";
// 获取到蓝牙适配器
private BluetoothAdapter mBluetoothAdapter;
// 用来保存搜索到的设备信息
private List bluetoothDevices = new ArrayList();
// ListView组件
private ListView lvDevices;
// ListView的字符串数组适配器
private ArrayAdapter arrayAdapter;
// UUID,蓝牙建立链接需要的
private final UUID MY_UUID = UUID
.fromString("db764ac8-4b08-7f25-aafe-59d03c27bae3");
// 为其链接创建一个名称
private final String NAME = "Bluetooth_Socket";
// 选中发送数据的蓝牙设备,全局变量,否则连接在方法执行完就结束了
private BluetoothDevice selectDevice;
// 获取到选中设备的客户端串口,全局变量,否则连接在方法执行完就结束了
private BluetoothSocket clientSocket;
// 获取到向设备写的输出流,全局变量,否则连接在方法执行完就结束了
private InputStream is;// 获取到输入流
private OutputStream os;// 获取到输出流
// 服务端利用线程不断接受客户端信息
private AcceptThread thread;
private EditText m_send_data;
private TextView m_connect_status;
public static final int BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE = 1000;
public static final int BLUETOOTH_SERVER_RECEIVED_DATA = 1001;
@SuppressLint("MissingPermission")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m_send_data = (EditText) findViewById(R.id.send_data);;
m_connect_status = (TextView) findViewById(R.id.connect_status);
// 获取到蓝牙默认的适配器
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 获取到ListView组件
lvDevices = (ListView) findViewById(R.id.lvDevices);
// 为listview设置字符换数组适配器
arrayAdapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, android.R.id.text1,
bluetoothDevices);
// 为listView绑定适配器
lvDevices.setAdapter(arrayAdapter);
// 为listView设置item点击事件侦听
Log.e(TAG,"hello");
// 用Set集合保持已绑定的设备
@SuppressLint("MissingPermission")
Set devices = mBluetoothAdapter.getBondedDevices();
if (devices.size() > 0) {
for (BluetoothDevice bluetoothDevice : devices) {
// 保存到arrayList集合中
bluetoothDevices.add(bluetoothDevice.getName() + ":"
+ bluetoothDevice.getAddress() + "\n");
}
}
// 因为蓝牙搜索到设备和完成搜索都是通过广播来告诉其他应用的
// 这里注册找到设备和完成搜索广播
IntentFilter filter = new IntentFilter(
BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
thread = new AcceptThread();
thread.start();
}
@SuppressLint("MissingPermission")
public void onClick_Search(View view) {
// 点击搜索周边设备,如果正在搜索,则暂停搜索
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
public void onClick_send(View view) {
String text = m_send_data.getText().toString();
Log.d(TAG,"onClick_send ready to send " + text);
if (os != null) {
try {
os.write(text.getBytes("UTF-8"));
Log.d(TAG,"successfully onClick_send " + text);
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG,"onClick_send Failed "+e.toString());
}
}else
Log.d(TAG,"OutputStream os is null");
}
// 注册广播接收者
private BroadcastReceiver receiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context arg0, Intent intent) {
// 获取到广播的action
String action = intent.getAction();
// 判断广播是搜索到设备还是搜索完成
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
// 找到设备后获取其设备
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
// 设备没有绑定过,则将其保持到arrayList集合中
bluetoothDevices.add(device.getName() + ":"
+ device.getAddress() + "\n");
// 更新字符串数组适配器,将内容显示在listView中
arrayAdapter.notifyDataSetChanged();
}
} else if (action
.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
Toast.makeText(MainActivity.this, "搜索完成", Toast.LENGTH_SHORT).show();
}
}
};
// 创建handler,因为我们接收是采用线程来接收的,在线程中无法操作UI,所以需要handler
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
// 通过msg传递过来的信息,吐司一下收到的信息
Log.i(TAG,"recevied message:"+msg.obj);
switch (msg.what){
case BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE:
int status = (int) msg.arg1;
if(status == 0){
m_connect_status.setText("客户端已断开");
m_connect_status.setTextColor(Color.RED);}
else if(status == 1){
m_connect_status.setText("等待客户端连接");
m_connect_status.setTextColor(Color.GRAY);
}
else if(status == 2) {
m_connect_status.setText("客户端已连接");
m_connect_status.setTextColor(Color.GREEN);
}
Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
break;
case BLUETOOTH_SERVER_RECEIVED_DATA:
Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
break;
}
}
};
// 服务端接收信息线程
private class AcceptThread extends Thread {
private BluetoothServerSocket serverSocket;// 服务端接口
private BluetoothSocket socket;// 获取到客户端的接口
//private InputStream is;// 获取到输入流
//private OutputStream os;// 获取到输出流
Message msg = new Message();
@SuppressLint("MissingPermission")
public AcceptThread() {
try {
// 通过UUID监听请求,然后获取到对应的服务端接口
serverSocket = mBluetoothAdapter
.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
Log.i(TAG,"listenUsingRfcommWithServiceRecord\n");
} catch (Exception e) {
// TODO: handle exception
}
}
public void run() {
while (true) {
try {
Log.i(TAG, "accept,waiting a client... \n");
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.obj = "there is a client connected";
msg.arg1 = 1;
handler.sendMessage(msg);
socket = serverSocket.accept();
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.obj = "there is a client connected";
msg.arg1 = 2;
handler.sendMessage(msg);
Log.i(TAG, "there is a client connected \n");
// 获取到输入流
is = socket.getInputStream();
// 获取到输出流
os = socket.getOutputStream();
Log.i(TAG, "accept getInputStream getOutputStream \n");
while (true) {
byte[] buffer = new byte[128];
Log.i(TAG, "reading... ");
int count = is.read(buffer);
Log.i(TAG, "read count " + count);
os.write("OK".getBytes("UTF-8"));
// 创建Message类,向handler发送数据
//Message msg = new Message();
msg = handler.obtainMessage();
msg.what = BLUETOOTH_SERVER_RECEIVED_DATA;
msg.obj = new String(buffer, 0, count, "utf-8");
handler.sendMessage(msg);
}
} catch(Exception e){
// TODO: handle exception
e.printStackTrace();
Log.d(TAG, "read Thread " + e.toString());
}
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.obj = "the client is disconnected";
msg.arg1 = 0;
handler.sendMessage(msg);
}
}
}
}
2、activity_main.xml
3、AndroidManifest.xml
二、客户端app代码
1、MainActivity .java
package com.hypernano.hyperbluetooth;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements OnItemClickListener {
String TAG = "HPBMainActivity";
// 获取到蓝牙适配器
private BluetoothAdapter mBluetoothAdapter;
// 用来保存搜索到的设备信息
private List bluetoothDevices = new ArrayList();
// ListView组件
private ListView lvDevices;
// ListView的字符串数组适配器
private ArrayAdapter arrayAdapter;
// UUID,蓝牙建立链接需要的
private final UUID MY_UUID = UUID
.fromString("db764ac8-4b08-7f25-aafe-59d03c27bae3");
// 为其链接创建一个名称
private final String NAME = "Bluetooth_Socket";
// 选中发送数据的蓝牙设备,全局变量,否则连接在方法执行完就结束了
private BluetoothDevice selectDevice;
// 获取到选中设备的客户端串口,全局变量,否则连接在方法执行完就结束了
private BluetoothSocket clientSocket;
// 获取到向设备写的输出流,全局变量,否则连接在方法执行完就结束了
private OutputStream os;
// 服务端利用线程不断接受客户端信息
private EditText m_send_data;
private ReadThread mReadThread;
public static final int BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE = 1000;
public static final int BLUETOOTH_CLIENT_RECEIVED_DATA = 1001;
public int received_data_line = 0;
private TextView m_connect_status , m_received_data;
public StringBuffer sb = new StringBuffer();
@SuppressLint("MissingPermission")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
m_send_data = (EditText) findViewById(R.id.send_data);
m_connect_status = (TextView) findViewById(R.id.connect_status);
m_connect_status.setText("当前状态:"+"断开");
m_received_data = (TextView) findViewById(R.id.received_data);
m_received_data.setMovementMethod(ScrollingMovementMethod.getInstance());
// 获取到蓝牙默认的适配器
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 获取到ListView组件
lvDevices = (ListView) findViewById(R.id.lvDevices);
// 为listview设置字符换数组适配器
arrayAdapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, android.R.id.text1,
bluetoothDevices);
// 为listView绑定适配器
lvDevices.setAdapter(arrayAdapter);
// 为listView设置item点击事件侦听
lvDevices.setOnItemClickListener(this);
// 用Set集合保持已绑定的设备
@SuppressLint("MissingPermission")
Set devices = mBluetoothAdapter.getBondedDevices();
if (devices.size() > 0) {
for (BluetoothDevice bluetoothDevice : devices) {
// 保存到arrayList集合中
bluetoothDevices.add(bluetoothDevice.getName() + ":"
+ bluetoothDevice.getAddress() + "\n");
}
}
// 因为蓝牙搜索到设备和完成搜索都是通过广播来告诉其他应用的
// 这里注册找到设备和完成搜索广播
IntentFilter filter = new IntentFilter(
BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
Log.e(TAG, "Client onCreate");
}
@SuppressLint("MissingPermission")
public void onClick_Search(View view) {
setTitle("正在扫描...");
// 点击搜索周边设备,如果正在搜索,则暂停搜索
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
public void onClick_send(View view) throws IOException {
if (os != null) {
try {
String text = m_send_data.getText().toString();
os.write(text.getBytes("UTF-8"));
Toast.makeText(MainActivity.this, "发送成功", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
clientSocket.close();
clientSocket = null;
os.close();
os = null;
Log.e(TAG, "onClick_send "+ e.toString());
}
}
}
// 注册广播接收者
private BroadcastReceiver receiver = new BroadcastReceiver() {
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context arg0, Intent intent) {
// 获取到广播的action
String action = intent.getAction();
// 判断广播是搜索到设备还是搜索完成
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
// 找到设备后获取其设备
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 判断这个设备是否是之前已经绑定过了,如果是则不需要添加,在程序初始化的时候已经添加了
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
// 设备没有绑定过,则将其保持到arrayList集合中
bluetoothDevices.add(device.getName() + ":"
+ device.getAddress() + "\n");
// 更新字符串数组适配器,将内容显示在listView中
arrayAdapter.notifyDataSetChanged();
}
} else if (action
.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
//setTitle("搜索完成");
Toast.makeText(MainActivity.this, "搜索完成", Toast.LENGTH_SHORT).show();
}
}
};
public class ReadThread extends Thread {
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
InputStream inputStream;
Log.d(TAG, "start ReadThread");
while (true) {
try {
inputStream = clientSocket.getInputStream();
Log.d(TAG, "waiting to reading....");
if ((bytes = inputStream.read(buffer)) > 0) {
byte[] buf_data = new byte[bytes];
for (int i = 0; i < bytes; i++) {
buf_data[i] = buffer[i];
}
String s = new String(buf_data);
Message msg = new Message();
msg.what = BLUETOOTH_CLIENT_RECEIVED_DATA;
msg.obj = s;
handler.sendMessage(msg);
}
} catch (IOException e) {
Message msg = new Message();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.arg1 = 0;
msg.obj = "Failed to connect the server," + e.toString();
handler.sendMessage(msg);
Log.d(TAG, e.toString());
break;
}
}
if (clientSocket != null) {
try {
clientSocket.close();
clientSocket = null;
} catch (IOException e) {
Log.d("TAG", e.toString());
}
}
}
}
// 点击listView中的设备,传送数据
@SuppressLint("MissingPermission")
@Override
public void onItemClick(AdapterView> parent, View view, int position,
long id) {
// 获取到这个设备的信息
String s = arrayAdapter.getItem(position);
// 对其进行分割,获取到这个设备的地址
String address = s.substring(s.indexOf(":") + 1).trim();
Message msg = new Message();
Log.d(TAG, "address:" + address);
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
// 如果选择设备为空则代表还没有选择设备
if (selectDevice == null) {
//通过地址获取到该设备
selectDevice = mBluetoothAdapter.getRemoteDevice(address);
}
try {
if (clientSocket == null) {
// 获取到客户端接口
clientSocket = selectDevice
.createRfcommSocketToServiceRecord(MY_UUID);
// 向服务端发送连接
//Toast.makeText(this, "connecting to the server,mac address " + address, Toast.LENGTH_SHORT).show();
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.obj = "connecting to the server,mac address " + address;
msg.arg1 = 1;
handler.sendMessage(msg);
clientSocket.connect();
os = clientSocket.getOutputStream();
if (os != null) {
String text = "hello server,i am a client.";
os.write(text.getBytes("UTF-8"));
}
mReadThread = new ReadThread();
mReadThread.start();
Log.d(TAG, "connected to server!");
// Toast.makeText(this, "connected to server!", Toast.LENGTH_SHORT).show();
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.obj = "connected to the server successfully!";
msg.arg1 = 3;
handler.sendMessage(msg);
}
} catch (IOException e) {
// TODO Auto-generated catch block
clientSocket = null;
e.printStackTrace();
Log.d(TAG, "failed to connect server," + e.toString());
msg = handler.obtainMessage();
msg.what = BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE;
msg.arg1 = 0;
msg.obj = "Failed to connect the server," + e.toString();
handler.sendMessage(msg);
}
}
void refresh_received_data(String msg)
{
m_received_data.append(msg);
int offset = m_received_data.getLineCount() * m_received_data.getLineHeight();
if(offset > m_received_data.getHeight()){
m_received_data.scrollTo(0,offset- m_received_data.getHeight());
}
}
// 创建handler,因为我们接收是采用线程来接收的,在线程中无法操作UI,所以需要handler
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what){
case BLUETOOTH_CLIENT_CONNECT_INFO_MESSAGE:
int sts = (int) msg.arg1;
if(sts == 0){
m_connect_status.setText("当前状态:" + "断开");
m_connect_status.setTextColor(Color.GRAY);
Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
}else if(sts == 1){
m_connect_status.setText("当前状态:" + "正在连接...");
}else if(sts == 3){
m_connect_status.setText("当前状态:"+"已经连接到服务端");
m_connect_status.setTextColor(Color.GREEN);
//sb.delete(0,sb.length());
received_data_line = 0;
m_received_data.scrollTo(0,0);
m_received_data.setText("\n");
refresh_received_data("接收到的数据:");
}
break;
case BLUETOOTH_CLIENT_RECEIVED_DATA:
String str = String.format("%05d",received_data_line);
// sb.append( str +":" + msg.obj+ "\n");
received_data_line++;
refresh_received_data("\n" + str +":" + msg.obj );
break;
default:
//Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
break;
}
Log.i(TAG, "recevied message:" + msg.obj);
}
};
@SuppressLint("MissingPermission")
@Override
public void onDestroy() {
super.onDestroy();
try {
mBluetoothAdapter.cancelDiscovery();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
// mBluetoothAdapter.disable();
}
}
2、activity_main.xml
3、AndroidManifest.xml
四、代码主要参考文章
Android蓝牙搜索设备,向其发送数据并接收-刘宇_android 蓝牙数据传输_Brycen Liu的博客-CSDN博客
五、其他参考文章
用C写蓝牙通讯程序:扫描、读取、发送_#include Android 使用Socket实现服务器与手机客户端的长连接一:一对一聊天_12506227的技术博客_51CTO博客 android蓝牙简单开发示例教程_Android_脚本之家