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 效果展示

        客户端:

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

        服务端:

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐