一、蓝牙通信的步骤

  1. 开启蓝牙
  2. 搜索可用设备
  3. 创建蓝牙socket
  4. 获取输入输出流
  5. 断开连接关闭蓝牙

html5搜索蓝牙并连接 html5蓝牙通信_服务端

二、几个重要的类

  • BluetoothAdapter
  • BluetoothGatt
  • BluetoothDevice
  • BluetoothCattService
  • BluetoothCattCharacteristic

第一个是蓝牙设配器,对蓝牙的操作都需要用到它,很重要,BluetoothGatt作为中央来使用和处理数据,使用时有一个回调方法BluetoothGattCallback返回中央的状态和周边提供的数据,BluetoothCattService作为周边来提供数据;BluetoothGattServerCallback返回周边的状态。BluetoothDevice是蓝牙设备,BluetoothCattCharacteristic是蓝牙设备的特征。

看着有点乱,我们来打个比喻:BluetoothDevice为学校,BluetoothGatt为学校到达某一个班级的通道,BluetoothCattService为学校的某一个班级,BluetoothCattCharacteristic为班级中的某一个学生。那么蓝牙连接通信的过程就是这样,BluetoothAdapter先找到学校(就是连接目的设备),再通过通道找到目标班级,最后从班级中找到目标学生,这个学生就是我们设备之间通信的中介,很重要,学校有唯一的MAC地址,班级有唯一的serviceUUID,学生有唯一的charactersticUUID(相当于学号),所以就是在一所学校找一个学生的问题。

三、蓝牙通信的原理

3.1 蓝牙通信原理与socket通信原理相似

html5搜索蓝牙并连接 html5蓝牙通信_android_02

3.2 客户端socket

  1. 创建客户端蓝牙Socket
  2. 创建连接
  3. 读写数据
  4. 关闭

3.3 服务端Socket

  1. 创建服务端蓝牙Socket
  2. 绑定端口号(蓝牙忽略)
  3. 创建监听listen(蓝牙忽略, 蓝牙没有此监听,而是通过while(true)死循环来一直监听的)
  4. 通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)
  5. 读写数据
  6. 关闭

四、客户端代码

public class ConnectThread extends Thread{
    private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
    /** 客户端socket*/
    private final BluetoothSocket mmSoket;
    /** 要连接的设备*/
    private final BluetoothDevice mmDevice;
    private BluetoothAdapter mBluetoothAdapter;
    /** 主线程通信的Handler*/
    private final Handler mHandler;
    /** 发送和接收数据的处理类*/
    private ConnectedThread mConnectedThread;

    public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, Handler mUIhandler) {
        mmDevice = device;
        mBluetoothAdapter = bluetoothAdapter;
        mHandler = mUIhandler;

        BluetoothSocket tmp = null;
        try {
            // 创建客户端Socket
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            e.printStackTrace();
        }

        mmSoket = tmp;
    }

    @Override
    public void run() {
        super.run();
        // 关闭正在发现设备.(如果此时又在查找设备,又在发送数据,会有冲突,影响传输效率)
        mBluetoothAdapter.cancelDiscovery();

        try {
            // 连接服务器
            mmSoket.connect();
        } catch (IOException e) {
            // 连接异常就关闭
            try {
                mmSoket.close();
            } catch (IOException e1) {
            }
            return;
        }

        manageConnectedSocket(mmSoket);
    }

    private void manageConnectedSocket(BluetoothSocket mmSoket) {
        // 通知主线程连接上了服务端socket,更新UI
        mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
        // 新建一个线程进行通讯,不然会发现线程堵塞
        mConnectedThread = new ConnectedThread(mmSoket,mHandler);
        mConnectedThread.start();
    }

    /**
     * 关闭当前客户端
     */
    public void cancle() {
        try {
            mmSoket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送数据
     * @param data
     */
    public void sendData(byte[] data) {
        if(mConnectedThread != null) {
            mConnectedThread.write(data);
        }
    }
}

五、服务端代码

public class AccepThread extends Thread {

    /** 连接的名称*/
    private static final String NAME = "BluetoothClass";
    /** UUID*/
    private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
    /** 服务端蓝牙Sokcet*/
    private final BluetoothServerSocket mmServerSocket;
    private final BluetoothAdapter mBluetoothAdapter;
    /** 线程中通信的更新UI的Handler*/
    private final Handler mHandler;
    /** 监听到有客户端连接,新建一个线程单独处理,不然在此线程中会堵塞*/
    private ConnectedThread mConnectedThread;

    public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {
        mBluetoothAdapter = adapter;
        this.mHandler = handler;

        // 获取服务端蓝牙socket
        mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
    }

    @Override
    public void run() {
        super.run();
        // 连接的客户端soacket
        BluetoothSocket socket = null;

        // 服务端是不退出的,要一直监听连接进来的客户端,所以是死循环
        while (true){
            // 通知主线程更新UI,客户端开始监听
            mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
            try {
                // 获取连接的客户端socket
                socket =  mmServerSocket.accept();
            } catch (IOException e) {
                // 通知主线程更新UI, 获取异常
                mHandler.sendEmptyMessage(Constant.MSG_ERROR);
                e.printStackTrace();
                // 服务端退出一直监听线程
                break;
            }

            if(socket != null) {
                // 管理连接的客户端socket
                manageConnectSocket(socket);

                // 这里应该是手动断开,案例应该是只保证连接一个客户端,所以连接完以后,关闭了服务端socket
//                try {
//                    mmServerSocket.close();
//                    mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
            }
        }
    }

    /**
     * 管理连接的客户端socket
     * @param socket
     */
    private void manageConnectSocket(BluetoothSocket socket) {
        // 只支持同时处理一个连接
        // mConnectedThread不为空,踢掉之前的客户端
        if(mConnectedThread != null) {
            mConnectedThread.cancle();
        }

        // 主线程更新UI,连接到了一个客户端
        mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
        // 新建一个线程,处理客户端发来的数据
        mConnectedThread = new ConnectedThread(socket, mHandler);
        mConnectedThread.start();
    }

    /**
     * 断开服务端,结束监听
     */
    public void cancle() {
        try {
            mmServerSocket.close();
            mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送数据
     * @param data
     */
     public void sendData(byte[] data){
         if(mConnectedThread != null) {
             mConnectedThread.write(data);
         }
     }
}

六、共同通讯处理类

public class ConnectedThread extends Thread{
    /** 当前连接的客户端BluetoothSocket*/
    private final BluetoothSocket mmSokcet;
    /** 读取数据流*/
    private final InputStream mmInputStream;
    /** 发送数据流*/
    private final OutputStream mmOutputStream;
    /** 与主线程通信Handler*/
    private Handler mHandler;
    private String TAG = "ConnectedThread";

    public ConnectedThread(BluetoothSocket socket,Handler handler) {
        mmSokcet = socket;
        mHandler = handler;

        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }

        mmInputStream = tmpIn;
        mmOutputStream = tmpOut;
    }

    @Override
    public void run() {
        super.run();
        byte[] buffer = new byte[1024];

        while (true) {
            try {
                // 读取数据
                int bytes = mmInputStream.read(buffer);

                if(bytes > 0) {
                    String data = new String(buffer,0,bytes,"utf-8");
                    // 把数据发送到主线程, 此处还可以用广播
                    Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);
                    mHandler.sendMessage(message);
                }

                Log.d(TAG, "messge size :" + bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 踢掉当前客户端
    public void cancle() {
        try {
            mmSokcet.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端发送数据
     * @param data
     */
    public void write(byte[] data) {
        try {
            mmOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

七、基于安卓蓝牙通信的DEMO应用

7.1 DEMO运行截图

html5搜索蓝牙并连接 html5蓝牙通信_蓝牙_03

7.2 代码仓库

作业六代码仓库

7.3 核心代码

package com.example.bluetoothdemo;


import android.Manifest;
import android.annotation.SuppressLint;
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.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.sql.SQLOutput;
import java.util.UUID;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    public static final int REQUEST_BT_ENABLE_CODE = 200;
    public static final String BT_UUID = "00001101-0000-1000-8000-00805F9B34FB";//uuid

    private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器
    private BlueToothStateReceiver mReceiver;//广播接收器
    private ConnectThread mConnectThread; //客户端线程
    private AcceptThread mAcceptThread; //服务端线程

    private RvAdapter mRvAdapter;

    @SuppressLint("StaticFieldLeak")
    private static MsgAdapter mMessageAdapter;

    private EditText inputEt;

    @SuppressLint("HandlerLeak")
    private static final Handler mHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            mMessageAdapter.addMessage((String) msg.obj);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //这是我是为了6.0以上的设备能搜索到结果,动态申请了位置权限。但是没有处理结果,因为我测试肯定点同意~- -
        requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1001);
        initUI();
        registerRec();
    }

    private void initUI() {
        findViewById(R.id.open).setOnClickListener(this);
        findViewById(R.id.close).setOnClickListener(this);
        findViewById(R.id.start).setOnClickListener(this);
        findViewById(R.id.stop).setOnClickListener(this);
        findViewById(R.id.send).setOnClickListener(this);

        inputEt = (EditText) findViewById(R.id.input);

        RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.devices);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRvAdapter = new RvAdapter(this);
        mRecyclerView.setAdapter(mRvAdapter);
        mRvAdapter.setOnItemClickListener(new RvAdapter.OnItemClickListener() {
            @Override
            public void onClick(BluetoothDevice device) {
                mConnectThread = new ConnectThread(device);
                mConnectThread.start();
            }
        });

        RecyclerView mMessageView = (RecyclerView) findViewById(R.id.msglist);
        mMessageView.setLayoutManager(new LinearLayoutManager(this));
        mMessageAdapter = new MsgAdapter(this);
        mMessageView.setAdapter(mMessageAdapter);
    }


    private void openBT() {
        if (mBluetoothAdapter == null) {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        }
        //1.设备不支持蓝牙,结束应用
        if (mBluetoothAdapter == null) {
            finish();
            return;
        }
        //2.判断蓝牙是否打开

        try {
            if (!mBluetoothAdapter.enable()) {
                //没打开请求打开
                Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(btEnable, REQUEST_BT_ENABLE_CODE);
            }
        }
        catch (Exception EX) {
            System.out.println("异常");
        }


    }

    private void registerRec() {
        //3.注册蓝牙广播
        mReceiver = new BlueToothStateReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_FOUND);//搜多到蓝牙
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索结束
        registerReceiver(mReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        if (mReceiver != null) {
            unregisterReceiver(mReceiver);
        }
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_BT_ENABLE_CODE) {
            if (resultCode == RESULT_OK) {
                //用户允许打开蓝牙
                mMessageAdapter.addMessage("用户同意打开蓝牙");
            } else if (resultCode == RESULT_CANCELED) {
                //用户取消打开蓝牙
                mMessageAdapter.addMessage("用户拒绝打开蓝牙");
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.open:
                openBT();
                mMessageAdapter.addMessage("打开蓝牙");
                if (mAcceptThread == null && mBluetoothAdapter != null) {
                    mAcceptThread = new AcceptThread();
                    mAcceptThread.start();
                    mMessageAdapter.addMessage("启动服务线程");
                }
                break;
            case R.id.close:
                mBluetoothAdapter.disable();
                break;
            case R.id.start:
                if (mBluetoothAdapter != null) {
                    mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
                    mBluetoothAdapter.startDiscovery();
                    mMessageAdapter.addMessage("开始搜索蓝牙");
                } else {
                    openBT();
                    if (mBluetoothAdapter != null) {
                        mRvAdapter.clearDevices();//开始搜索前清空上一次的列表
                        mBluetoothAdapter.startDiscovery();
                        mMessageAdapter.addMessage("开始搜索蓝牙");
                    }
                }
                break;
            case R.id.stop:
                if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering()) {
                    mBluetoothAdapter.cancelDiscovery();
                }
                break;
            case R.id.send:
                String msg = inputEt.getText().toString();
                if (TextUtils.isEmpty(msg)) {
                    Toast.makeText(this, "消息为空", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (mConnectThread != null) {//证明我主动去链接别人了
                    mConnectThread.write(msg);
                } else if (mAcceptThread != null) {
                    mAcceptThread.write(msg);
                }
                mMessageAdapter.addMessage("发送消息:" + msg);
                break;
        }
    }

    class BlueToothStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(MainActivity.this, "触发广播", Toast.LENGTH_SHORT).show();
            String action = intent.getAction();
            switch (action) {
                case BluetoothDevice.ACTION_FOUND:
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    Toast.makeText(MainActivity.this, "找到设备" + device.getName(), Toast.LENGTH_SHORT).show();
                    if (mRvAdapter != null) {
                        mRvAdapter.addDevice(device);
                    }
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                    mMessageAdapter.addMessage("搜索结束");
                    break;
            }
        }
    }


    class ConnectThread extends Thread {
        private BluetoothDevice mDevice;
        private BluetoothSocket mSocket;
        private InputStream btIs;
        private OutputStream btOs;
        private boolean canRecv;
        private PrintWriter writer;

        public ConnectThread(BluetoothDevice device) {
            mDevice = device;
            canRecv = true;
        }

        @Override
        public void run() {
            if (mDevice != null) {
                try {
                    //获取套接字
                    BluetoothSocket temp = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));
                    //mDevice.createRfcommSocketToServiceRecord(UUID.fromString(BT_UUID));//sdk 2.3以下使用
                    mSocket = temp;
                    //发起连接请求
                    if (mSocket != null) {
                        mSocket.connect();
                    }
                    sendHandlerMsg("连接 " + mDevice.getName() + "成功!");
                    //获取输入输出流
                    btIs = mSocket.getInputStream();
                    btOs = mSocket.getOutputStream();

                    //通讯-接收消息
                    BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
                    String content = null;
                    while (canRecv) {
                        content = reader.readLine();
                        sendHandlerMsg("收到消息:" + content);
                    }


                } catch (IOException e) {
                    e.printStackTrace();
                    sendHandlerMsg("错误:" + e.getMessage());
                } finally {
                    try {
                        if (mSocket != null) {
                            mSocket.close();
                        }
                        //btIs.close();//两个输出流都依赖socket,关闭socket即可
                        //btOs.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        sendHandlerMsg("错误:" + e.getMessage());
                    }
                }
            }
        }

        private void sendHandlerMsg(String content) {
            Message msg = mHandler.obtainMessage();
            msg.what = 1001;
            msg.obj = content;
            mHandler.sendMessage(msg);
        }

        public void write(String msg) {
            if (btOs != null) {
                try {
                    if (writer == null) {
                        writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
                    }
                    writer.println(msg);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    writer.close();
                    sendHandlerMsg("错误:" + e.getMessage());
                }
            }
        }
    }

    class AcceptThread extends Thread {
        private BluetoothServerSocket mServerSocket;
        private BluetoothSocket mSocket;
        private InputStream btIs;
        private OutputStream btOs;
        private PrintWriter writer;
        private boolean canAccept;
        private boolean canRecv;

        public AcceptThread() {
            canAccept = true;
            canRecv = true;
        }

        @Override
        public void run() {
            try {
                //获取套接字
                try {
                    BluetoothServerSocket temp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord("TEST", UUID.fromString(BT_UUID));
                    mServerSocket = temp;
                }
                catch (Exception exception) {
                    System.out.println("异常");
                }

                //监听连接请求 -- 作为测试,只允许连接一个设备
                if (mServerSocket != null) {
                    // while (canAccept) {
                    mSocket = mServerSocket.accept();
                    sendHandlerMsg("有客户端连接");
                    // }
                }
                //获取输入输出流
                btIs = mSocket.getInputStream();
                btOs = mSocket.getOutputStream();
                //通讯-接收消息
                BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
                String content = null;
                while (canRecv) {
                    content = reader.readLine();
                    sendHandlerMsg("收到消息:" + content);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (mSocket != null) {
                        mSocket.close();
                    }
                    // btIs.close();//两个输出流都依赖socket,关闭socket即可
                    // btOs.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    sendHandlerMsg("错误:" + e.getMessage());
                }
            }
        }

        private void sendHandlerMsg(String content) {
            Message msg = mHandler.obtainMessage();
            msg.what = 1001;
            msg.obj = content;
            mHandler.sendMessage(msg);
        }

        public void write(String msg) {
            if (btOs != null) {
                try {
                    if (writer == null) {
                        writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
                    }
                    writer.println(msg);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    writer.close();
                    sendHandlerMsg("错误:" + e.getMessage());
                }
            }
        }
    }
}