【Android】使用BluetoothSocket实现跨设备通讯
1 前言使用Socket实现跨设备通讯中介绍了使用 WiFi 通道实现跨设备通讯,本文将介绍使用 Bluetooth 通道实现跨进程通讯。1.1 蓝牙通讯核心类在蓝牙通讯中,主要用到 BluetoothAdapter、BluetoothDevice、BluetoothServerSocket、BluetoothSocket。(1)BluetoothAdapterpublic static sync
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 效果展示
客户端:
点击第一个设备,进入会话界面:
服务端:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)