文章目录

  • 一、项目实现功能
  • 二、项目核心代码
    • 1.简要实现设备蓝牙通信
    • 2.模拟Client 和Server端实现简单的通信。
  • 三、实验项目截图
  • 四、源代码

一、项目实现功能

1.两台设备可以通过蓝牙进行通信
2.模拟Client 和Server端实现简单的通信。

二、项目核心代码

1.简要实现设备蓝牙通信

如果想让应用启动设备发现或操纵蓝牙设置,则除了 BLUETOOTH 权限以外,还必须声明 BLUETOOTH_ADMIN 权限。大多数应用只是需利用此权限发现本地蓝牙设备。除非应用是根据用户请求修改蓝牙设置的“超级管理员”,否则不应使用此权限所授予的其他功能。在Manifest.xml中加入以下代码

在这里插入代码片<manifest ... ><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!-- If your app targets Android 9 or lower, you can declareACCESS_COARSE_LOCATION instead. --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />...
</manifest>``

布局文件:
activity.xml主要布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="?attr/colorPrimary"android:minHeight="?attr/actionBarSize"android:theme="?attr/actionBarTheme" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><TextViewandroid:id="@+id/title_left_text"style="?android:attr/windowTitleStyle"android:layout_width="0dp"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:layout_weight="1"android:gravity="left"android:ellipsize="end"android:singleLine="true" /><TextViewandroid:id="@+id/title_right_text"android:layout_width="0dp"android:layout_height="match_parent"android:layout_alignParentRight="true"android:layout_weight="1"android:ellipsize="end"android:gravity="right"android:singleLine="true"android:textColor="#fff" /></LinearLayout><ListView android:id="@+id/in"android:layout_width="match_parent"android:layout_height="match_parent"android:stackFromBottom="true"android:transcriptMode="alwaysScroll"android:layout_weight="1" /><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content" ><EditText android:id="@+id/edit_text_out"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:layout_gravity="bottom" /><Button android:id="@+id/button_send"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/send"/></LinearLayout>
</LinearLayout>

device_list.xm文件

option 布局

java文件:
BluetoothChat.java文件 检测蓝牙是否授权,启动的activity文件

package com.example.bluetooth;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;public class BluetoothChat extends AppCompatActivity{public static final int MESSAGE_STATE_CHANGE = 1;public static final int MESSAGE_READ = 2;public static final int MESSAGE_WRITE = 3;public static final int MESSAGE_DEVICE_NAME = 4;public static final int MESSAGE_TOAST = 5;public static final String DEVICE_NAME = "device_name";public static final String TOAST = "toast";private static final int REQUEST_CONNECT_DEVICE = 1;  //请求连接设备private static final int REQUEST_ENABLE_BT = 2;private TextView title;private ListView conversationView;private EditText outEditText;private Button send;private String connectedDeviceName = null;private ArrayAdapter<String> adapter1;     // conversation array adapterprivate StringBuffer outStringBuffer;private BluetoothAdapter adapter2 = null;private ChatService chatService = null;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);getSupportActionBar().hide();  //隐藏标题栏if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);}}Toolbar toolbar = findViewById(R.id.toolbar);//创建选项菜单toolbar.inflateMenu(R.menu.option_menu);//选项菜单监听toolbar.setOnMenuItemClickListener(new MyMenuItemClickListener());title = findViewById(R.id.title_left_text);title.setText(R.string.app_name);title = findViewById(R.id.title_right_text);// 得到本地蓝牙适配器adapter2 = BluetoothAdapter.getDefaultAdapter();if (adapter2 == null) {Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_LONG).show();finish();return;}if (!adapter2.isEnabled()) { //若当前设备蓝牙功能未开启Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableIntent, REQUEST_ENABLE_BT); //} else {if (chatService == null) {setupChat();  //创建会话}}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if(grantResults.length>0){if(grantResults[0]!=PackageManager.PERMISSION_GRANTED){Toast.makeText(this, "未授权,蓝牙搜索功能将不可用!", Toast.LENGTH_SHORT).show();}}}@Overridepublic synchronized void onResume() {  //synchronized:同步方法实现排队调用super.onResume();if (chatService != null) {if (chatService.getState() == ChatService.STATE_NONE) {chatService.start();}}}private void setupChat() {adapter1 = new ArrayAdapter<String>(this, R.layout.activity_main);conversationView = findViewById(R.id.in);conversationView.setAdapter(adapter1);outEditText = findViewById(R.id.edit_text_out);outEditText.setOnEditorActionListener(mWriteListener);send = findViewById(R.id.button_send);send.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {TextView view = findViewById(R.id.edit_text_out);String message = view.getText().toString();sendMessage(message);}});//创建服务对象chatService = new ChatService(this, mHandler);outStringBuffer = new StringBuffer("");}@Overridepublic void onDestroy() {super.onDestroy();if (chatService != null)chatService.stop();}// 直接使用enable()函数不安全,需要将内容存放在intent中private void ensureDiscoverable() { //修改本机蓝牙设备的可见性//打开手机蓝牙后,能被其它蓝牙设备扫描到的时间不是永久的if (adapter2.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);//设置在300秒内可见(能被扫描)discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);startActivity(discoverableIntent);Toast.makeText(this, "已经设置本机蓝牙设备的可见性,对方可搜索了。", Toast.LENGTH_SHORT).show();}}private void sendMessage(String message) {if (chatService.getState() != ChatService.STATE_CONNECTED) {Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();return;}if (message.length() > 0) {byte[] send = message.getBytes();chatService.write(send);outStringBuffer.setLength(0);outEditText.setText(outStringBuffer);}}private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() {@Overridepublic boolean onEditorAction(TextView view, int actionId, KeyEvent event) {if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {//软键盘里的回车也能发送消息String message = view.getText().toString();sendMessage(message);}return true;}};//使用Handler对象在UI主线程与子线程之间传递消息private final Handler mHandler = new Handler() {   //消息处理@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_STATE_CHANGE:switch (msg.arg1) {case ChatService.STATE_CONNECTED:title.setText(R.string.title_connected_to);title.append(connectedDeviceName);adapter1.clear();break;case ChatService.STATE_CONNECTING:title.setText(R.string.title_connecting);break;case ChatService.STATE_LISTEN:case ChatService.STATE_NONE:title.setText(R.string.title_not_connected);break;}break;case MESSAGE_WRITE:byte[] writeBuf = (byte[]) msg.obj;String writeMessage = new String(writeBuf);adapter1.add("我:  " + writeMessage);break;case MESSAGE_READ:byte[] readBuf = (byte[]) msg.obj;String readMessage = new String(readBuf, 0, msg.arg1);adapter1.add(connectedDeviceName + ":  "+ readMessage);break;case MESSAGE_DEVICE_NAME:connectedDeviceName = msg.getData().getString(DEVICE_NAME);Toast.makeText(getApplicationContext(),"链接到 " + connectedDeviceName, Toast.LENGTH_SHORT).show();break;case MESSAGE_TOAST:Toast.makeText(getApplicationContext(),msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show();break;}}};//返回进入好友列表操作后的数回调方法public void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {case REQUEST_CONNECT_DEVICE:if (resultCode == Activity.RESULT_OK) {String address = data.getExtras().getString(DeviceList.EXTRA_DEVICE_ADDRESS);BluetoothDevice device = adapter2.getRemoteDevice(address);chatService.connect(device);} else if (resultCode == Activity.RESULT_CANCELED) {Toast.makeText(this, "未选择任何好友!", Toast.LENGTH_SHORT).show();}break;case REQUEST_ENABLE_BT:if (resultCode == Activity.RESULT_OK) {setupChat();} else {Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();finish();}}}//内部类,选项菜单的单击事件处理private class MyMenuItemClickListener implements Toolbar.OnMenuItemClickListener {@Overridepublic boolean onMenuItemClick(MenuItem item) {switch (item.getItemId()) {case R.id.scan://启动DeviceList这个ActivityIntent serverIntent = new Intent(BluetoothChat.this, DeviceList.class);startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);return true;case R.id.discoverable:ensureDiscoverable();return true;case R.id.back:finish();System.exit(0);return true;}return false;}}
}

ChatService.java文件,蓝牙服务的会话程序 定义了3个内部类,AcceptThread(接受新连接)、ConnectThread(发出连接)和ConnectedThread (已连接)

package com.example.bluetooth;import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;public class ChatService {//本应用的主Activity组件名称private static final String NAME = "BluetoothChat";// UUID:通用唯一识别码,是一个128位长的数字,一般用十六进制表示//算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成//在创建蓝牙连接private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");private final BluetoothAdapter adapter;private final Handler mHandler;private AcceptThread acceptThread;private ConnectThread connectThread;private ConnectedThread connectedThread;private int state;public static final int STATE_NONE = 0;public static final int STATE_LISTEN = 1;public static final int STATE_CONNECTING = 2;public static final int STATE_CONNECTED = 3;//构造方法,接收UI主线程传递的对象public ChatService(Context context, Handler handler) {//构造方法完成蓝牙对象的创建adapter = BluetoothAdapter.getDefaultAdapter();state = STATE_NONE;mHandler = handler;}private synchronized void setState(int state) {state = state;mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();}public synchronized int getState() {return state;}public synchronized void start() {if (connectThread != null) {connectThread.cancel();connectThread = null;}if (connectedThread != null) {connectedThread.cancel();connectedThread = null;}if (acceptThread == null) {acceptThread = new AcceptThread();acceptThread.start();}setState(STATE_LISTEN);}//取消 CONNECTING 和 CONNECTED 状态下的相关线程,然后运行新的 connectThread 线程public synchronized void connect(BluetoothDevice device) {if (state == STATE_CONNECTING) {if (connectThread != null) {connectThread.cancel();connectThread = null;}}if (connectedThread != null) {connectedThread.cancel();connectedThread = null;}connectThread = new ConnectThread(device);connectThread.start();setState(STATE_CONNECTING);}/*开启一个 ConnectedThread 来管理对应的当前连接。之前先取消任意现存的 connectThread 、connectedThread 、 acceptThread 线程,然后开启新 connectedThread ,传入当前刚刚接受的socket 连接。最后通过 Handler来通知UI连接*/public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {if (connectThread != null) {connectThread.cancel();connectThread = null;}if (connectedThread != null) {connectedThread.cancel();connectedThread = null;}if (acceptThread != null) {acceptThread.cancel();acceptThread = null;}connectedThread = new ConnectedThread(socket);connectedThread.start();Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);Bundle bundle = new Bundle();bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());msg.setData(bundle);mHandler.sendMessage(msg);setState(STATE_CONNECTED);}// 停止所有相关线程,设当前状态为 NONEpublic synchronized void stop() {if (connectThread != null) {connectThread.cancel();connectThread = null;}if (connectedThread != null) {connectedThread.cancel();connectedThread = null;}if (acceptThread != null) {acceptThread.cancel();acceptThread = null;}setState(STATE_NONE);}// 在 STATE_CONNECTED 状态下,调用 connectedThread 里的 write 方法,写入 bytepublic void write(byte[] out) {ConnectedThread r;synchronized (this) {if (state != STATE_CONNECTED)return;r = connectedThread;}r.write(out);}// 连接失败的时候处理,通知 ui ,并设为 STATE_LISTEN 状态private void connectionFailed() {setState(STATE_LISTEN);Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);Bundle bundle = new Bundle();bundle.putString(BluetoothChat.TOAST, "链接不到设备");msg.setData(bundle);mHandler.sendMessage(msg);}// 当连接失去的时候,设为 STATE_LISTEN 状态并通知 uiprivate void connectionLost() {setState(STATE_LISTEN);Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);Bundle bundle = new Bundle();bundle.putString(BluetoothChat.TOAST, "设备链接中断");msg.setData(bundle);mHandler.sendMessage(msg);}// 创建监听线程,准备接受新连接。使用阻塞方式,调用 BluetoothServerSocket.accept()private class AcceptThread extends Thread {private final BluetoothServerSocket mmServerSocket;public AcceptThread() {BluetoothServerSocket tmp = null;try {//使用射频端口(RF comm)监听tmp = adapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);} catch (IOException e) {}mmServerSocket = tmp;}@Overridepublic void run() {setName("AcceptThread");BluetoothSocket socket = null;while (state != STATE_CONNECTED) {try {socket = mmServerSocket.accept();} catch (IOException e) {break;}if (socket != null) {synchronized (ChatService.this) {switch (state) {case STATE_LISTEN:case STATE_CONNECTING:connected(socket, socket.getRemoteDevice());break;case STATE_NONE:case STATE_CONNECTED:try {socket.close();} catch (IOException e) {e.printStackTrace();}break;}}}}}public void cancel() {try {mmServerSocket.close();} catch (IOException e) {e.printStackTrace();}}}/*连接线程,专门用来对外发出连接对方蓝牙的请求和处理流程。构造函数里通过 BluetoothDevice.createRfcommSocketToServiceRecord() ,从待连接的 device 产生 BluetoothSocket. 然后在 run 方法中 connect ,成功后调用 BluetoothChatSevice 的 connected() 方法。定义 cancel() 在关闭线程时能够关闭相关socket 。*/private class ConnectThread extends Thread {private final BluetoothSocket mmSocket;private final BluetoothDevice mmDevice;public ConnectThread(BluetoothDevice device) {mmDevice = device;BluetoothSocket tmp = null;try {tmp = device.createRfcommSocketToServiceRecord(MY_UUID);} catch (IOException e) {e.printStackTrace();}mmSocket = tmp;}@Overridepublic void run() {setName("ConnectThread");adapter.cancelDiscovery();try {mmSocket.connect();} catch (IOException e) {connectionFailed();try {mmSocket.close();} catch (IOException e2) {e.printStackTrace();}ChatService.this.start();return;}synchronized (ChatService.this) {connectThread = null;}connected(mmSocket, mmDevice);}public void cancel() {try {mmSocket.close();} catch (IOException e) {e.printStackTrace();}}}/*双方蓝牙连接后一直运行的线程;构造函数中设置输入输出流。run()方法中使用阻塞模式的 InputStream.read()循环读取输入流,然后发送到 UI 线程中更新聊天消息。本线程也提供了 write() 将聊天消息写入输出流传输至对方,传输成功后回写入 UI 线程。最后使用cancel()关闭连接的 socket*/private class ConnectedThread extends Thread {private final BluetoothSocket mmSocket;private final InputStream mmInStream;private final OutputStream mmOutStream;public ConnectedThread(BluetoothSocket socket) {mmSocket = socket;InputStream tmpIn = null;OutputStream tmpOut = null;try {tmpIn = socket.getInputStream();tmpOut = socket.getOutputStream();} catch (IOException e) {e.printStackTrace();}mmInStream = tmpIn;mmOutStream = tmpOut;}@Overridepublic void run() {byte[] buffer = new byte[1024];int bytes;while (true) {try {bytes = mmInStream.read(buffer);mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer).sendToTarget();} catch (IOException e) {connectionLost();break;}}}public void write(byte[] buffer) {try {mmOutStream.write(buffer);mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();} catch (IOException e) {e.printStackTrace();}}public void cancel() {try {mmSocket.close();} catch (IOException e) {e.printStackTrace();}}}
}

DeviceList.java文件
本程序供菜单项主界面的选项菜单“我的友好”调用,用于:
(1)显示已配对的好友列表;
(2)搜索可配对的好友进行配对
(3)新选择并配对的蓝牙设备将刷新好友列表
注意:发现新的蓝牙设备并请求配对时,需要对应接受
关键技术:动态注册一个广播接收者,处理蓝牙设备扫描的结果

package com.example.bluetooth;import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import java.util.Set;public class DeviceList extends AppCompatActivity {private BluetoothAdapter adapter;private ArrayAdapter<String> adapter1;    // 配对的设备private ArrayAdapter<String> adapter2;       // 新设备public static String EXTRA_DEVICE_ADDRESS = "device_address";  //Mac地址//定义广播接收者,用于处理扫描蓝牙设备后的结果private final BroadcastReceiver receiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (BluetoothDevice.ACTION_FOUND.equals(action)) {BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);if (device.getBondState() != BluetoothDevice.BOND_BONDED) {adapter2.add(device.getName() + "\n" + device.getAddress());}} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {if (adapter2.getCount() == 0) {String noDevices = getResources().getText(R.string.none_found).toString();adapter2.add(noDevices);}}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.device_list);//在被调用活动里,设置返回结果码setResult(Activity.RESULT_CANCELED);init();  //活动界面}private void init() {Button scanButton = findViewById(R.id.button_scan);scanButton.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {Toast.makeText(DeviceList.this, R.string.scanning, Toast.LENGTH_LONG).show();doDiscovery();  //搜索蓝牙设备}});adapter1 = new ArrayAdapter<String>(this, R.layout.activity_main);adapter2 = new ArrayAdapter<String>(this, R.layout.activity_main);//已配对蓝牙设备列表ListView pairedListView =findViewById(R.id.paired_devices);pairedListView.setAdapter(adapter1);pairedListView.setOnItemClickListener(mPaireDeviceClickListener);//未配对蓝牙设备列表ListView newDevicesListView = findViewById(R.id.new_devices);newDevicesListView.setAdapter(adapter2);newDevicesListView.setOnItemClickListener(mNewDeviceClickListener);//动态注册广播接收者IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);registerReceiver(receiver, filter);filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);registerReceiver(receiver, filter);adapter = BluetoothAdapter.getDefaultAdapter();Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();if (pairedDevices.size() > 0) {findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);for (BluetoothDevice device : pairedDevices) {adapter1.add(device.getName() + "\n" + device.getAddress());}} else {String noDevices = getResources().getText(R.string.none_paired).toString();adapter1.add(noDevices);}}@Overrideprotected void onDestroy() {super.onDestroy();if (adapter != null) {adapter.cancelDiscovery();}this.unregisterReceiver(receiver);}private void doDiscovery() {findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);if (adapter.isDiscovering()) {adapter.cancelDiscovery();}adapter.startDiscovery();  //开始搜索蓝牙设备并产生广播//startDiscovery是一个异步方法//找到一个设备时就发送一个BluetoothDevice.ACTION_FOUND的广播}private AdapterView.OnItemClickListener mPaireDeviceClickListener = new AdapterView.OnItemClickListener() {public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {adapter.cancelDiscovery();String info = ((TextView) v).getText().toString();String address = info.substring(info.length() - 17);Intent intent = new Intent();intent.putExtra(EXTRA_DEVICE_ADDRESS, address);  //Mac地址setResult(Activity.RESULT_OK, intent);finish();}};private AdapterView.OnItemClickListener mNewDeviceClickListener = new AdapterView.OnItemClickListener() {public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {adapter.cancelDiscovery();Toast.makeText(DeviceList.this, "请在蓝牙设置界面手动连接设备",Toast.LENGTH_SHORT).show();Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);startActivityForResult(intent,1);}};//回调方法:进入蓝牙配对设置界面返回后执行@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);init();  //刷新好友列表}
}

2.模拟Client 和Server端实现简单的通信。

Client端 布局文件
button用以开关蓝牙以及搜索设备的布局按钮

<?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"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><ListViewandroid:id="@+id/lis1"android:layout_width="match_parent"android:layout_height="match_parent" ><ToggleButtonandroid:id="@+id/bu5"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="ToggleButton" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="本机状态:" /><Buttonandroid:id="@+id/button4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="搜索设备" /><Buttonandroid:id="@+id/button3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="设置" /><Buttonandroid:id="@+id/button2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="打开" /></ListView></LinearLayout>

mainactivity.java

package com.example.gwlblu;import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;import androidx.appcompat.app.AppCompatActivity;import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;public class MainActivity extends AppCompatActivity {//蓝牙通信需要相同的UUID和对方的蓝牙地址,UUID规定是下面的格式,只要格式对,两边的UUID相同,数字可以改变,不影响通信,但一般都是用下面这种static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";Button btnSearch, btnDis;//定义布局中的按钮ToggleButton tbtnSwitch;//显示蓝牙开关状态的双状态按钮ListView lvBTDevices; //搜索到的蓝牙列表ArrayAdapter<String> adtDevices; //将本机的蓝牙地址显示List<String> lstDevices = new ArrayList<String>();//列表中蓝牙的地址BluetoothAdapter btAdapt;  //定义移动设备的本地的蓝牙适配器public static BluetoothSocket btSocket;  //Socket用来接受客户端的要求@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); //加载 布局// Button 设置 通过findViewById的方法来定义//获得所有控件对象btnSearch = (Button) this.findViewById(R.id.button2);btnDis = (Button) this.findViewById(R.id.button3);tbtnSwitch = (ToggleButton) this.findViewById(R.id.bu5);//给所有的控件设置监听器btnDis.setOnClickListener(new ClickEvent());btnSearch.setOnClickListener(new ClickEvent());tbtnSwitch.setOnClickListener(new ClickEvent());// ListView及其数据源 适配器lvBTDevices = (ListView) this.findViewById(R.id.lis1);adtDevices = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1, lstDevices);lvBTDevices.setAdapter(adtDevices);lvBTDevices.setOnItemClickListener(new ItemClickEvent());//设置监听器获取数据btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 读取蓝牙状态并显示于双状态按钮tbtnSwitch.setChecked(false);else if (btAdapt.getState() == BluetoothAdapter.STATE_ON)tbtnSwitch.setChecked(true);// 注册Receiver来获取蓝牙设备相关的结果,onReceive()里取得搜索所得的蓝牙设备信息IntentFilter intent = new IntentFilter();intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);registerReceiver(searchDevices, intent);}private BroadcastReceiver searchDevices = new BroadcastReceiver() {public void onReceive(Context context, Intent intent) {String action = intent.getAction();Bundle b = intent.getExtras();Object[] lstName = b.keySet().toArray();// 显示所有收到的消息及其细节for (int i = 0; i < lstName.length; i++) {String keyName = lstName[i].toString();Log.e(keyName, String.valueOf(b.get(keyName)));}//搜索设备时,取得设备的MAC地址if (BluetoothDevice.ACTION_FOUND.equals(action)) {BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);String str= device.getName() + "|" + device.getAddress();if (lstDevices.indexOf(str) == -1)// 防止重复添加,lstDevices.add(str); // 获取设备名称和mac地址adtDevices.notifyDataSetChanged();//通知Activity刷新数据}}};//本次活动的销毁函数@Overrideprotected void onDestroy() {try {if (btSocket != null)btSocket.close();} catch (IOException e) {e.printStackTrace();}this.unregisterReceiver(searchDevices);super.onDestroy();android.os.Process.killProcess(android.os.Process.myPid());}//对监听器的设置,获取列表中设备名以及蓝牙设备地址class ItemClickEvent implements AdapterView.OnItemClickListener {@Overridepublic void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) {btAdapt.cancelDiscovery();//连接时停止搜索周围蓝牙,否则容易连接失败//取出蓝牙地址String str = lstDevices.get(arg2);String[] values = str.split("\\|");String address=values[1];Log.d("address",values[1]);UUID uuid = UUID.fromString(SPP_UUID);//利用BluetoothDevice衍生出Socket,BluetoothDevice btDev = btAdapt.getRemoteDevice(address);try {btSocket = btDev.createRfcommSocketToServiceRecord(uuid);try {// 连接建立之前的先配对if (btDev.getBondState() == BluetoothDevice.BOND_NONE) {Method creMethod = BluetoothDevice.class.getMethod("createBond");Log.e("TAG", "开始配对");creMethod.invoke(btDev);}} catch (Exception e) {e.printStackTrace();}btSocket.connect();Toast.makeText(MainActivity.this,"connect succeeded",Toast.LENGTH_SHORT).show();//将数据写入输出流OutputStream os = btSocket.getOutputStream();if (os != null) {try {os.write("test".getBytes("UTF-8"));Toast.makeText(MainActivity.this, "send succeed", Toast.LENGTH_LONG).show();} catch (IOException e) {e.printStackTrace();}}} catch (IOException e) {e.printStackTrace();Toast.makeText(MainActivity.this,"connect fail",Toast.LENGTH_LONG).show();}}}//按钮监听器,打开本机蓝牙的设置class ClickEvent implements View.OnClickListener {@Overridepublic void onClick(View v) {if (v == btnSearch)// 搜索蓝牙设备,在BroadcastReceiver显示结果{if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果蓝牙还没开启Toast.makeText(MainActivity.this, "请先打开蓝牙", Toast.LENGTH_LONG).show();return;}setTitle("本机蓝牙地址:" + btAdapt.getAddress());lstDevices.clear();btAdapt.startDiscovery();} else if (v == tbtnSwitch) {// 本机蓝牙启动/关闭if (tbtnSwitch.isChecked() == false)btAdapt.enable();else if (tbtnSwitch.isChecked() == true)btAdapt.disable();} else if (v == btnDis)// 本机可以被搜索{Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);startActivity(discoverableIntent);//本机蓝牙的内部设置}}}
}

Server端

package com.example.gwlblu;import androidx.appcompat.app.AppCompatActivity;import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;import java.io.InputStream;
import java.util.UUID;public class Server extends AppCompatActivity {private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");//Server端和Client端的UUID要一致private BluetoothAdapter bluetoothAdapter;private final String NAME = "BlueTooth_Socket";//名字可以随便写private AcceptThread acceptThread;//后面的accept()会阻塞,所以要新开线程@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_server);bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();acceptThread=new AcceptThread();acceptThread.start();//开启线程}private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {Toast.makeText(Server.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();}};private class AcceptThread extends Thread {private BluetoothServerSocket serverSocket;private BluetoothSocket socket;private InputStream is;public AcceptThread() {try {//监听有无连接serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);} catch (Exception e) {}}@Overridepublic void run() {try {socket = serverSocket.accept();//若有监听到有连接,accept给BluetoothSocketLog.d("tag", "connected");is = socket.getInputStream();while (true) {byte[] buffer = new byte[128];int count = is.read(buffer);//子线程里不能直接Toast,利用handlerMessage msg = new Message();msg.obj = new String(buffer, 0, count, "UTF-8");handler.sendMessage(msg);}} catch (Exception e) {}}}
}

三、实验项目截图

实验1(真机调试):
简单实现蓝牙通信聊天


实验2
实现功能:Client端三个按钮,分别是蓝牙开关、本机可被搜索和搜索设备,点击搜索设备即可搜索周围的蓝牙,点击搜索到的蓝牙即可连接并自动发送test。Server端在接收到消息之后便Toast出来。

四、源代码

gitee源代码1
gitee源代码2

Android Studio 简要实现蓝牙(Bluetooth)通信(附加作业)相关推荐

  1. 蓝牙App设计2:使用Android Studio制作一个蓝牙软件(包含:代码实现等)

    前言:蓝牙聊天App设计全部有三篇文章(一.UI界面设计,二.蓝牙搜索配对连接实现,三.蓝牙连接聊天),这篇文章是:二.蓝牙搜索配对连接实现. 课程1:Android Studio小白安装教程,以及第 ...

  2. android:手机与BLE-CC41-A蓝牙模块通信

    原创地址:http://blog.csdn.net/bigtree_mfc/article/details/53783585 手机蓝牙的开启.搜索就不再多说,和手机之间的蓝牙通信一样. 需要注意的安卓 ...

  3. android studio手机与蓝牙收发数据_QT for Android :蓝牙

    首先要搭建QT for Andorid环境,忘记怎么弄了,基本是要装NDK.JDK.ANDROID STUDIO. 先做测试 QT on Android 编写蓝牙使用HC-05​blog.csdn.n ...

  4. 【Android探索】基于Android Studio平台的蓝牙遥控APP

    前言:好久没更新这博客了,接下来有时间记录下前阵子做的东西,当作是复习之余的回顾往事和时间消遣,哈哈.首先是这个蓝牙遥控APP,这个APP是之前做一个小比赛的时候尝试做来玩玩的,主要内容是使用这个AP ...

  5. Android Studio开发之蓝牙通信

    安卓开发-蓝牙通信 功能需求:在微信程序的第一子项中完成"蓝牙聊天功能" 开发步骤: 配置文件注册 设计界面布局 编写用于蓝牙会话的服务组件ChatService 分别建立供主Ac ...

  6. 【android studio】注册广播监测网络(附加gif和菊花进度条)

    通过注册广播来监测网络的变化,包含wifi和数据网络的监测,添加了gif图片和菊花进度条.本来想上传一个完整测试的gif,但是只能上传小于5M的,哭了.下面上效果图和代码. 效果图 MainActiv ...

  7. Android studio 学生信息录入表(课堂作业)

    任务: 制作学生信息录入表: 开发步骤: 运用线性布局嵌套,按照模型制作activity_main.xml 添加组件TextView.EditText.单选框.复选框.按钮等 编辑MainActivi ...

  8. 解决Android Studio中 Call requires API level 18 (current min is 15)问题

    Android Studio今天用蓝牙的时候出现一个奇怪的问题 final BluetoothManager bluetoothManager = (BluetoothManager) getSyst ...

  9. Android Studio制作手机App:通过手机蓝牙(Bluetooth)与STM32上的低功耗蓝牙(HC-42)连接通信,实现手机端对单片机的控制。

    背景: 本文的内容是针对单片机蓝牙模块(HC-42)开发的手机App.在这之前,我想先声明一点,手机与手机间的蓝牙连接方式"与"手机与HC间的蓝牙连接方式"是不一样的.原 ...

最新文章

  1. bootstrap下拉列表与输入框组结合的样式调整
  2. Android开源框架——内存泄漏检测工具 LeakCanary
  3. toc](boost::type_erasure::is_subconcept相关的测试程序
  4. mysql两张主表person event加上第三张关联表
  5. mysql 5.6 5.7不兼容_同一条sql在mysql5.6和5.7版本遇到的问题。
  6. erlang 程序设计书中的错误
  7. Java生成.jar库
  8. ACL2022奇葩标题大赏
  9. 每秒几十万的大规模网络爬虫是如何炼成的?
  10. AlphaGo实力远超对手
  11. i.MX6ULL终结者Linux RS232/485驱动实验RS232驱动
  12. 阮一峰ES6旅程:入门布局
  13. matlab指派问题论文,数学建模指派问题论文.doc
  14. 员工年会中大奖,老板:这个给不了
  15. linux 清屏函数
  16. 1.5-瑞吉外卖-第五天
  17. Vigenere加密算法
  18. 20211127 1+X 中级实操考试样题(第二套)
  19. 网课搜答案公众号接口系统
  20. 基于javaweb+mysql的就业管理系统设计和实现(java+springboot+ssm)

热门文章

  1. docker制作nginx+nginx-module-vts镜像基于alpine镜像
  2. 针对【BIEB六人行活动】第三季的部分问题一
  3. ExcelUtil 工具类
  4. strok函数的使用
  5. 扬声器的谐振频率 f0 的测量
  6. 模型剪枝学习笔记--Pruning Filters for Efficient Convnets
  7. 新浪微博接口,获取用户粉丝列表
  8. Android FM流程分析
  9. wordpress 主机_好的WordPress主机的10个基本功能
  10. Acwing第 65 场周赛【未完结】