Android——使用BluetoothSocket实现跨设备通讯

    科技2025-01-14  12

    1 前言

    使用Socket实现跨设备通讯 中介绍了使用 WiFi 通道实现跨设备通讯,本文将介绍使用 Bluetooth 通道实现跨进程通讯。

    本文全部代码见→使用BluetoothSocket实现跨设备通讯

    1.1 蓝牙通讯核心类

    在蓝牙通讯中,主要用到 BluetoothAdapter、BluetoothDevice、BluetoothServerSocket、BluetoothSocket。

    (1)BluetoothAdapter(管理本机蓝牙设备)

    public static synchronized BluetoothAdapter getDefaultAdapter() //获取默认的蓝牙适配器对象 public boolean isEnabled() //判断蓝牙功能是否打开 public boolean enable() //打开蓝牙功能(不会弹出提示) public boolean disable() //关闭蓝牙功能 public boolean isDiscovering() //判断当前是否正在搜索设备 public boolean startDiscovery() //搜索周围的蓝牙设备,结果通过广播返回 public boolean cancelDiscovery() //取消搜索 public Set<BluetoothDevice> getBondedDevices() //获取本机已绑定的设备列表 public BluetoothDevice getRemoteDevice(String address) //根据蓝牙地址获取远程的蓝牙设备 public boolean setName(String name) // 设置本机蓝牙名称 public String getName() // 获取本机蓝牙名称 public String getAddress() //获取本机蓝牙地址 // 创建并返回 BluetoothServerSocket public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) //不安全的 public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) //安全的

    (2)BluetoothDevice(管理对端蓝牙设备)

    public String getName() //获取远程设备名称 public String getAddress() //获取远程设备地址 //创建并返回 BluetoothSocket public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) //非安全的 public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) //安全的

    (3)BluetoothServerSocket

    public BluetoothSocket accept() //服务端监听外部的蓝牙连接请求(会阻塞当前线程,建议在子线程中执行) public void close() //关闭服务端的蓝牙监听

    (4)BluetoothSocket

    public void connect() //建立蓝牙 Socket 连接(客户端调用) public void close() //关闭蓝牙 Socket 连接 public BluetoothDevice getRemoteDevice() //获取远程蓝牙设备 public InputStream getInputStream() //获取 Socket 连接的输入流对象 public OutputStream getOutputStream()//获取 Socket 连接的输出流对象

    1.2 蓝牙通讯基本流程

    (1)权限申请

    <!-- 蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <!-- 位置访问权限 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    (2)服务端接口调用

    //获取 BluetoothSocket mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("server_name", UUID.fromString(UUIDString)) mSocket = mServerSocket.accept() //调用此方法会阻塞当前线程,建议在子线程中执行 // 读取对端数据 in = mSocket.getInputStream() byte[] bt = new byte[50] in.read(bt) // 向对端发送数据 out = new PrintWriter(mSocket.getOutputStream()) out.print(content) out.flush()

    (3)客户端接口调用

    // 发现设备 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() mBluetoothAdapter.startDiscovery() receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case BluetoothDevice.ACTION_FOUND: //接收搜索到的蓝牙设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); list.add(device); break; } } } //获取 BluetoothSocket,并建立蓝牙 Socket 连接 remoteDevice = mBluetoothAdapter.getRemoteDevice(remoteAddress); mSocket = remoteDevice.createRfcommSocketToServiceRecord(UUID.fromString(UUIDString)); mSocket.connect() //调用此方法会阻塞当前线程,建议在子线程中执行 // 读取对端数据 in = mSocket.getInputStream() byte[] bt = new byte[50] in.read(bt) // 向对端发送数据 out = new PrintWriter(mSocket.getOutputStream()) out.print(content) out.flush()

    2 项目结构

    说明:客户端(bluetooth_c )中 discover 包里的类用于搜索蓝牙设备。

    3 服务端(bluetooth_S)

    BluetoothService.java

    package com.zhyan8.bluetooth_s; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.os.Handler; import android.os.Message; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.UUID; public class BluetoothService { final String UUIDString = "00001101-0000-1000-8000-00805F9B34FB"; private Handler mHandler; private BluetoothAdapter mBluetoothAdapter; private BluetoothServerSocket mServerSocket; private BluetoothSocket mSocket; private InputStream in; private PrintWriter out; private Object lock = new Object(); BluetoothService(Handler handler) { mHandler = handler; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); try { mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("server_name", UUID.fromString(UUIDString)); } catch (IOException e) { e.printStackTrace(); } } private void connect(){ new Thread() { @Override public void run() { try { synchronized (lock) { if (mSocket!=null) { return; } mSocket = mServerSocket.accept(); // 会阻塞线程,建议在子线程中进行 in = mSocket.getInputStream(); out = new PrintWriter(mSocket.getOutputStream()); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void begin_listen() { while (mSocket==null) { connect(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } new Thread() { @Override public void run() { try { while (mSocket.isConnected()) { byte[] bt = new byte[50]; in.read(bt); String content = new String (bt, "UTF-8" ); if (content!=null && !content.equals("")) { Message msg = new Message(); msg.obj = content; mHandler.sendMessage(msg); } } } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void send_msg(final String content) { new Thread() { @Override public void run() { out.print(content); out.flush (); } }.start(); } }

    ChatActivity.java

    package com.zhyan8.bluetooth_s; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class ChatActivity extends AppCompatActivity { private BluetoothService service; private EditText et_msg; private TextView tv_msg; public static final String[] permissions = { "android.permission.BLUETOOTH", "android.permission.BLUETOOTH_ADMIN", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.BLUETOOTH_PRIVILEGED" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); if(Build.VERSION.SDK_INT>=23){ requestPermissions(permissions,1); } init(); } private void init() { et_msg = (EditText) findViewById(R.id.et_msg); tv_msg = (TextView) findViewById(R.id.tv_msg); service = new BluetoothService(mHandler); service.begin_listen(); } public void onClick(View v) { //单击发送按钮 String content = et_msg.getText().toString().trim(); if (content!=null && !content.equals("")) { service.send_msg(content); } } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { String content = (String) msg.obj; tv_msg.setText(content); } }; }

    activity_chat.xml

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context="com.zhyan8.bluetooth_s.ChatActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="100dp" android:orientation="horizontal" android:gravity="center_vertical"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:text="接收的消息:" android:textSize="20sp" android:gravity="center_vertical"/> <TextView android:id="@+id/tv_msg" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="20sp" android:gravity="center_vertical"/> </LinearLayout> <EditText android:id="@+id/et_msg" android:layout_width="match_parent" android:layout_height="100dp" android:textSize="30sp" android:background="#ffcc66"/> <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:layout_height="80dp" android:text="发送" android:textSize="30sp" android:layout_marginTop="30dp" android:onClick="onClick"/> </LinearLayout>

    界面如下: 

     4 客户端(bluetooth_C)

    4.1 搜索设备

    ScanDevices.java

    package com.zhyan8.bluetooth_c.discover; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; public class ScanDevices { private BluetoothAdapter mBluetoothAdapter; ScanDevices() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } public ArrayList<BluetoothDevice> getBondedDevices() { //获取已绑定设备列表 Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices(); ArrayList bondedDevices = new ArrayList(); Iterator<BluetoothDevice> iterator = devices.iterator(); while (iterator.hasNext()) { bondedDevices.add(iterator.next()); } return bondedDevices; } public void startDiscovery() { //搜索周围蓝牙设备,并通过广播返回 if (!mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.enable(); // 打开蓝牙功能 return; } mBluetoothAdapter.startDiscovery(); } }

    DeviceAdapter.java

    package com.zhyan8.bluetooth_c.discover; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import com.zhyan8.bluetooth_c.R; import java.util.ArrayList; public class DevicesAdapter extends BaseAdapter { private ArrayList<BluetoothDevice> list; private Context mContext; public DevicesAdapter(Context context, ArrayList<BluetoothDevice> list){ mContext = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(mContext).inflate(R.layout.device_list, null); ViewHolder viewHolder = new ViewHolder(convertView); BluetoothDevice device = list.get(position); viewHolder.name.setText(device.getName()); viewHolder.mac.setText(device.getAddress()); return convertView; } public class ViewHolder{ private TextView name; private TextView mac; public ViewHolder(View view){ name = (TextView) view.findViewById(R.id.device_name); mac = (TextView) view.findViewById(R.id.device_mac); } } }

    device_list.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"> <TextView android:id="@+id/device_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="10dp" android:textColor="#FF0000FF"/> <TextView android:id="@+id/device_mac" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingBottom="10dp" android:textColor="#808080"/> </LinearLayout>

    MainActivity.java

    package com.zhyan8.bluetooth_c.discover; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import com.zhyan8.bluetooth_c.ChatActivity; import com.zhyan8.bluetooth_c.R; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private ListView listView; private DevicesAdapter adapter; private ArrayList<BluetoothDevice> list; private ScanDevices mScanDevices; public static final String[] permissions = { "android.permission.BLUETOOTH", "android.permission.BLUETOOTH_ADMIN", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.BLUETOOTH_PRIVILEGED" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(Build.VERSION.SDK_INT>=23){ requestPermissions(permissions,1); } init(); } private void init() { listView = (ListView) findViewById(R.id.listview); list = new ArrayList<>(); adapter = new DevicesAdapter(this, list); listView.setAdapter(adapter); listView.setOnItemClickListener(icl); mScanDevices = new ScanDevices(); initReceiver(); } private void initReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_FOUND); registerReceiver(receiver, filter); } public void onClick(View view){ //单击扫描按钮 list.clear(); list.addAll(mScanDevices.getBondedDevices()); //添加已绑定的设备列表 adapter.notifyDataSetChanged(); mScanDevices.startDiscovery(); //搜索周围蓝牙设备,并通过广播返回 } BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case BluetoothDevice.ACTION_FOUND: //接收搜索到的蓝牙设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String address = device.getAddress(); for (int i = 0; i < list.size(); i++) { //避免接收重复的设备 if (address == null || address.equals(list.get(i).getAddress())) { return; } } list.add(device); adapter.notifyDataSetChanged(); break; } } }; AdapterView.OnItemClickListener icl = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String address = list.get(position).getAddress(); startChat(address); } }; private void startChat(String remoteAddress) { Intent intent = new Intent(this, ChatActivity.class); intent.putExtra("remoteAddress", remoteAddress); startActivity(intent); } @Override protected void onDestroy() { unregisterReceiver(receiver); super.onDestroy(); } }

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".discover.MainActivity"> <Button android:id="@+id/scan_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="onClick" android:text="扫描"/> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>

    4.2 设备通讯

    BluetoothClient.java

    package com.zhyan8.bluetooth_c; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.os.Handler; import android.os.Message; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.UUID; public class BluetoothClient { final String UUIDString = "00001101-0000-1000-8000-00805F9B34FB"; private Handler mHandler; private BluetoothAdapter mBluetoothAdapter; private BluetoothSocket mSocket; private InputStream in; private PrintWriter out; private Object lock = new Object(); BluetoothClient(Handler handler, String remoteAddress) { mHandler = handler; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(remoteAddress); try { mSocket = remoteDevice.createRfcommSocketToServiceRecord(UUID.fromString(UUIDString)); } catch (IOException e) { e.printStackTrace(); } } private void connect(){ new Thread() { @Override public void run() { try { synchronized (lock) { if (mSocket.isConnected()) { return; } mSocket.connect(); // 会阻塞线程,建议在子线程中进行 in = mSocket.getInputStream(); out = new PrintWriter(mSocket.getOutputStream()); } } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void begin_listen() { while (!mSocket.isConnected()) { connect(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } new Thread() { @Override public void run() { try { while (mSocket.isConnected()) { byte[] bt = new byte[50]; in.read(bt); String content = new String (bt, "UTF-8" ); if (content!=null && !content.equals("")) { Message msg = new Message(); msg.obj = content; mHandler.sendMessage(msg); } } } catch (IOException e) { e.printStackTrace(); } } }.start(); } public void send_msg(final String content) { new Thread() { @Override public void run() { out.print(content); out.flush (); } }.start(); } }

    ChatActivity.java

    package com.zhyan8.bluetooth_c; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class ChatActivity extends AppCompatActivity { private BluetoothClient client; private EditText et_msg; private TextView tv_msg; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); init(); } private void init() { et_msg = (EditText) findViewById(R.id.et_msg); tv_msg = (TextView) findViewById(R.id.tv_msg); Intent intent = getIntent(); String remoteAddress = intent.getStringExtra("remoteAddress"); client = new BluetoothClient(mHandler, remoteAddress); client.begin_listen(); } public void onClick(View v) { //单击发送按钮 String content = et_msg.getText().toString().trim(); if (content!=null && !content.equals("")) { client.send_msg(content); } } private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { String content = (String) msg.obj; tv_msg.setText(content); } }; }

    activity_chat.xml

    同第3节。

    5 效果展示

    客户端:

    点击第一个设备,进入会话界面:

    服务端:

    Processed: 0.022, SQL: 8