【Android App】发送BLE广播及通过主从BLE实现聊天应用讲解及实战(附源码和演示 超详细)

news2024/11/23 21:42:39

需要源码请点赞关注收藏后评论区留言私信~~~

一、发送BLE广播

调用蓝牙适配器的getBluetoothLeAdvertiser方法,获得BluetoothLeAdvertiser广播器对象。 广播器的主要方法说明如下:

startAdvertising方法表示开始发送BLE广播,

stopAdvertising方法表示停止发送BLE广播。 在广播回调对象的onStartSuccess方法中,要给BLE服务端添加服务及其特征值,并开启GATT服务器等待客户端连接。

开启GATT服务器后的回调

openGattServer方法的第二个输入参数为BluetoothGattServerCallback类型,表示这里要传入事先定义的GATT服务器回调对象。 BluetoothGattServerCallback接口定义了许多方法,

常用方法有: onConnectionStateChange:BLE连接的状态发生变化时回调。此时判断如果已经连接,就从输入参数获取客户端的设备对象,并处理后续的连接逻辑。

onCharacteristicWriteRequest:收到BLE客户端写入请求时回调。该方法会收到客户端发来的消息。

同样此处需要两部手机上分别安装测试App,一台充当服务器端,另一台充当客户端

效果如下 填写名称则默认充当服务器端 另一台客户端进行搜索接听广播

 

 代码如下

package com.example.iot;

import androidx.appcompat.app.AppCompatActivity;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.example.iot.constant.BleConstant;
import com.example.iot.util.BluetoothUtil;

public class BleAdvertiseActivity extends AppCompatActivity {
    private static final String TAG = "BleAdvertiseActivity";
    private CheckBox ck_bluetooth; // 声明一个复选框对象
    private EditText et_name; // 声明一个编辑框对象
    private Button btn_advertise; // 声明一个按钮对象
    private TextView tv_hint; // 声明一个文本视图对象
    
    private BluetoothManager mBluetoothManager; // 声明一个蓝牙管理器对象
    private BluetoothAdapter mBluetoothAdapter; // 声明一个蓝牙适配器对象
    private BluetoothGattServer mGattServer; // 声明一个蓝牙GATT服务器对象
    private boolean isAdvertising = false; // 是否正在广播

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ble_advertise);
        initView(); // 初始化视图
        initBluetooth(); // 初始化蓝牙适配器
        if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
            ck_bluetooth.setChecked(true);
        }
    }

    // 初始化视图
    private void initView() {
        ck_bluetooth = findViewById(R.id.ck_bluetooth);
        et_name = findViewById(R.id.et_name);
        btn_advertise = findViewById(R.id.btn_advertise);
        tv_hint = findViewById(R.id.tv_hint);
        ck_bluetooth.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (isChecked) { // 开启蓝牙功能
                ck_bluetooth.setText("蓝牙开");
                btn_advertise.setEnabled(true);
                btn_advertise.setText("发送低功耗蓝牙广播");
                if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
                    BluetoothUtil.setBlueToothStatus(true); // 开启蓝牙功能
                }
            } else { // 关闭蓝牙功能
                ck_bluetooth.setText("蓝牙关");
                btn_advertise.setEnabled(false);
                isAdvertising = false;
                BluetoothUtil.setBlueToothStatus(false); // 关闭蓝牙功能
            }
        });
        btn_advertise.setEnabled(false);
        btn_advertise.setOnClickListener(v -> {
            if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
                Toast.makeText(this, "请先开启蓝牙再发送低功耗蓝牙广播", Toast.LENGTH_SHORT).show();
                return;
            }
            if (TextUtils.isEmpty(et_name.getText().toString())) {
                Toast.makeText(this, "请先输入服务器名称", Toast.LENGTH_SHORT).show();
                return;
            }
            if (isAdvertising) {
                stopAdvertise(); // 停止低功耗蓝牙广播
            } else {
                startAdvertise(et_name.getText().toString()); // 开始低功耗蓝牙广播
            }
            isAdvertising = !isAdvertising;
            btn_advertise.setText(isAdvertising?"停止低功耗蓝牙广播":"发送低功耗蓝牙广播");
        });
    }

    // 初始化蓝牙适配器
    private void initBluetooth() {
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "当前设备不支持低功耗蓝牙", Toast.LENGTH_SHORT).show();
            finish(); // 关闭当前页面
        }
        // 获取蓝牙管理器,并从中得到蓝牙适配器
        mBluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter(); // 获取蓝牙适配器
    }

    // 开始低功耗蓝牙广播
    private void startAdvertise(String ble_name) {
        // 设置广播参数
        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setConnectable(true) // 是否允许连接
                .setTimeout(0) // 设置超时时间
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
                .build();
        // 设置广播内容
        AdvertiseData advertiseData = new AdvertiseData.Builder()
                .setIncludeDeviceName(true) // 是否把设备名称也广播出去
                .setIncludeTxPowerLevel(true) // 是否把功率电平也广播出去
                .build();
        mBluetoothAdapter.setName(ble_name); // 设置BLE服务端的名称
        // 获取BLE广播器
        BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
        // BLE服务端开始广播,好让别人发现自己
        advertiser.startAdvertising(settings, advertiseData, mAdvertiseCallback);
    }

    // 停止低功耗蓝牙广播
    private void stopAdvertise() {
        if (mBluetoothAdapter != null) {
            // 获取BLE广播器
            BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
            if (advertiser != null) {
                advertiser.stopAdvertising(mAdvertiseCallback); // 停止低功耗蓝牙广播
            }
        }
    }

    // 创建一个低功耗蓝牙广播回调对象
    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settings) {
            Log.d(TAG, "低功耗蓝牙广播成功:"+settings.toString());
            addService(); // 添加读写服务UUID,特征值等
            String desc = String.format("BLE服务端“%s”正在对外广播", et_name.getText().toString());
            tv_hint.setText(desc);
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.d(TAG, "低功耗蓝牙广播失败,错误代码为"+errorCode);
            tv_hint.setText("低功耗蓝牙广播失败,错误代码为"+errorCode);
        }
    };

    // 添加读写服务UUID,特征值等
    private void addService() {
        BluetoothGattService gattService = new BluetoothGattService(
                BleConstant.UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY);
        // 只读的特征值
        BluetoothGattCharacteristic charaRead = new BluetoothGattCharacteristic(BleConstant.UUID_CHAR_READ,
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ);
        // 只写的特征值
        BluetoothGattCharacteristic charaWrite = new BluetoothGattCharacteristic(BleConstant.UUID_CHAR_WRITE,
                BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_WRITE);
        gattService.addCharacteristic(charaRead); // 将特征值添加到服务里面
        gattService.addCharacteristic(charaWrite); // 将特征值添加到服务里面
        // 开启GATT服务器等待客户端连接
        mGattServer = mBluetoothManager.openGattServer(this, mGattCallback);
        mGattServer.addService(gattService); // 向GATT服务器添加指定服务
    }

    // 创建一个GATT服务器回调对象
    private BluetoothGattServerCallback mGattCallback = new BluetoothGattServerCallback() {
        // BLE连接的状态发生变化时回调
        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            Log.d(TAG, "onConnectionStateChange device=" + device.toString() + " status=" + status + " newState=" + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                runOnUiThread(() -> {
                    String desc = String.format("%s\n已连接BLE客户端,对方名称为%s,MAC地址为%s",
                            tv_hint.getText().toString(), device.getName(), device.getAddress());
                    tv_hint.setText(desc);
                });
            }
        }

        // 收到BLE客户端写入请求时回调
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic chara, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, chara, preparedWrite, responseNeeded, offset, value);
            String message = new String(value); // 把客户端发来的数据转成字符串
            Log.d(TAG, "收到了客户端发过来的数据 " + message);
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopAdvertise(); // 停止低功耗蓝牙广播
        if (mGattServer != null) {
            mGattServer.close(); // 关闭GATT服务器
        }
    }

}

二、通过主从BLE实现聊天应用

调用蓝牙管理器对象的openGattServer方法,会开启GATT服务器并返回BluetoothGattServer类型的服务端对象。

BluetoothGattServer的常用方法说明如下:

addService:向GATT服务器添加指定服务。

sendResponse:向GATT客户端发送应答,告诉它成功收到了数据。 notifyCharacteristicChanged:向GATT客户端发送本地特征值已更新的通知。

close:关闭GATT服务器。

BLE客户端的管理对象

调用设备对象的connectGatt方法,连接GATT服务器并获得BluetoothGatt类型的客户端对象。 BluetoothGatt的常用方法说明如下:

discoverServices:开始查找GATT服务器提供的服务。

getServices:获取GATT服务器提供的服务列表。

writeCharacteristic:往GATT服务器写入特征值。

setCharacteristicNotification:开启或关闭特征值的通知。

disconnect:断开GATT连接。

close:关闭GATT客户端。

GATT服务端与客户端的通信流程

(1)建立GATT连接 先开启服务器,然后客户端才能连上服务器。

(2)客户端向服务端发消息 GATT客户端调用writeCharacteristic方法,会往GATT服务器写入特征值。

(3)服务端向客户端发消息 GATT客户端开启了通知后,GATT服务端调用notifyCharacteristicChanged方法向客户端发送特征值变更通知。

运行测试App效果如下

首先注册好服务器端 等待客户端连接

 服务器端与客户端可以进行简单的通话

 

 代码如下

package com.example.iot;

import androidx.appcompat.app.AppCompatActivity;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.iot.adapter.BlueListAdapter;
import com.example.iot.bean.BlueDevice;
import com.example.iot.constant.BleConstant;
import com.example.iot.util.BluetoothUtil;
import com.example.iot.util.ChatUtil;
import com.example.iot.util.DateUtil;
import com.example.iot.util.Utils;
import com.example.iot.util.ViewUtil;
import com.example.iot.widget.NoScrollListView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public class BleClientActivity extends AppCompatActivity {
    private static final String TAG = "BleClientActivity";
    private TextView tv_hint; // 声明一个文本视图对象
    private ScrollView sv_chat; // 声明一个滚动视图对象
    private LinearLayout ll_show; // 声明一个线性视图对象
    private LinearLayout ll_input; // 声明一个线性视图对象
    private EditText et_input; // 声明一个编辑框对象
    private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象
    private int dip_margin; // 每条聊天记录的四周空白距离
    private String mMinute = "00:00";

    private NoScrollListView nslv_device; // 声明一个不滚动列表视图对象
    private BlueListAdapter mListAdapter; // 声明一个蓝牙设备的列表适配器对象
    private Map<String, BlueDevice> mDeviceMap = new HashMap<>(); // 蓝牙设备映射
    private List<BlueDevice> mDeviceList = new ArrayList<>(); // 蓝牙设备列表

    private BluetoothAdapter mBluetoothAdapter; // 声明一个蓝牙适配器对象
    private BluetoothDevice mRemoteDevice; // 声明一个蓝牙设备对象
    private BluetoothGatt mBluetoothGatt; // 声明一个蓝牙GATT客户端对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ble_client);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        initView(); // 初始化视图
        initBluetooth(); // 初始化蓝牙适配器
        mHandler.postDelayed(mScanStart, 200);
    }

    // 初始化视图
    private void initView() {
        dip_margin = Utils.dip2px(this, 5);
        nslv_device = findViewById(R.id.nslv_device);
        tv_hint = findViewById(R.id.tv_hint);
        sv_chat = findViewById(R.id.sv_chat);
        ll_show = findViewById(R.id.ll_show);
        ll_input = findViewById(R.id.ll_input);
        et_input = findViewById(R.id.et_input);
        findViewById(R.id.btn_send).setOnClickListener(v -> sendMesssage());
        nslv_device = findViewById(R.id.nslv_device);
        mListAdapter = new BlueListAdapter(this, mDeviceList);
        nslv_device.setAdapter(mListAdapter);
        nslv_device.setOnItemClickListener((parent, view, position, id) -> {
            BlueDevice item = mDeviceList.get(position);
            // 根据设备地址获得远端的蓝牙设备对象
            mRemoteDevice = mBluetoothAdapter.getRemoteDevice(item.address);
            Log.d(TAG, "onItemClick address="+mRemoteDevice.getAddress()+", name="+mRemoteDevice.getName());
            // 连接GATT服务器
            mBluetoothGatt = mRemoteDevice.connectGatt(this, false, mGattCallback);
        });
    }

    // 初始化蓝牙适配器
    private void initBluetooth() {
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "当前设备不支持低功耗蓝牙", Toast.LENGTH_SHORT).show();
            finish(); // 关闭当前页面
        }
        // 获取蓝牙管理器,并从中得到蓝牙适配器
        BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bm.getAdapter(); // 获取蓝牙适配器
        if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
            BluetoothUtil.setBlueToothStatus(true); // 开启蓝牙功能
        }
    }

    // 创建一个开启BLE扫描的任务
    private Runnable mScanStart = new Runnable() {
        @Override
        public void run() {
            if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
                // 获取BLE设备扫描器
                BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
                scanner.startScan(mScanCallback); // 开始扫描BLE设备
            } else {
                mHandler.postDelayed(this, 2000);
            }
        }
    };

    // 创建一个扫描回调对象
    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            if (TextUtils.isEmpty(result.getDevice().getName())) {
                return;
            }
            Log.d(TAG, "callbackType="+callbackType+", result="+result.toString());
            // 下面把找到的蓝牙设备添加到设备映射和设备列表
            BlueDevice device = new BlueDevice(result.getDevice().getName(), result.getDevice().getAddress(), 0);
            mDeviceMap.put(device.address, device);
            mDeviceList.clear();
            mDeviceList.addAll(mDeviceMap.values());
            runOnUiThread(() -> mListAdapter.notifyDataSetChanged());
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
        }
    };

    private UUID read_UUID_chara; // 读的特征编号
    private UUID read_UUID_service; // 读的服务编号
    private UUID write_UUID_chara; // 写的特征编号
    private UUID write_UUID_service; // 写的服务编号
    private UUID notify_UUID_chara; // 通知的特征编号
    private UUID notify_UUID_service; // 通知的服务编号
    private UUID indicate_UUID_chara; // 指示的特征编号
    private UUID indicate_UUID_service; // 指示的服务编号

    // 创建一个GATT客户端回调对象
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        // BLE连接的状态发生变化时回调
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.d(TAG, "onConnectionStateChange status="+status+", newState="+newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功
                gatt.discoverServices(); // 开始查找GATT服务器提供的服务
                // 获取BLE设备扫描器
                BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
                scanner.stopScan(mScanCallback); // 停止扫描BLE设备
                runOnUiThread(() -> {
                    String desc = String.format("已连接BLE服务端,对方名称为“%s”,MAC地址为%s",
                            mRemoteDevice.getName(), mRemoteDevice.getAddress());
                    tv_hint.setText(desc);
                    ll_input.setVisibility(View.VISIBLE);
                    nslv_device.setVisibility(View.GONE);
                });
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开
                mBluetoothGatt.close(); // 关闭GATT客户端
            }
        }

        // 发现BLE服务端的服务列表及其特征值时回调
        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            Log.d(TAG, "onServicesDiscovered status"+status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 获得特征值的第一种办法:直接问硬件厂商,然后把特征值写在代码中
//                BluetoothGattService service = mBluetoothGatt.getService(BleConstant.UUID_SERVER);
//                if (service == null) {
//                    Log.d(TAG, "onServicesDiscovered service is null");
//                    return;
//                }
//                BluetoothGattCharacteristic chara1 = service.getCharacteristic(BleConstant.UUID_CHAR_READ);
//                boolean b = mBluetoothGatt.setCharacteristicNotification(chara1, true);
//                Log.d(TAG, "onServicesDiscovered 设置通知 " + b);
                // 获得特征值的第二种办法:通过特征属性的匹配关系,寻找对应的各路特征值
                List<BluetoothGattService> gattServiceList= mBluetoothGatt.getServices();
                for (BluetoothGattService gattService : gattServiceList) {
                    List<BluetoothGattCharacteristic> charaList = gattService.getCharacteristics();
                    for (BluetoothGattCharacteristic chara : charaList) {
                        int charaProp = chara.getProperties(); // 获取该特征的属性
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                            read_UUID_chara = chara.getUuid();
                            read_UUID_service = gattService.getUuid();
                            Log.d(TAG, "read_chara=" + read_UUID_chara + ", read_service=" + read_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                            write_UUID_chara = chara.getUuid();
                            write_UUID_service = gattService.getUuid();
                            Log.d(TAG,"write_chara="+write_UUID_chara+", write_service="+write_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
                            write_UUID_chara = chara.getUuid();
                            write_UUID_service = gattService.getUuid();
                            Log.d(TAG,"no_response write_chara="+write_UUID_chara+", write_service="+write_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                            notify_UUID_chara = chara.getUuid();
                            notify_UUID_service = gattService.getUuid();
                            Log.d(TAG,"notify_chara="+notify_UUID_chara+", notify_service="+notify_UUID_service);
                        }
                        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                            indicate_UUID_chara = chara.getUuid();
                            indicate_UUID_service = gattService.getUuid();
                            Log.d(TAG,"indicate_chara="+indicate_UUID_chara+", indicate_service="+indicate_UUID_service);
                        }
                    }
                    BluetoothGattService service = mBluetoothGatt.getService(read_UUID_service);
                    if (read_UUID_service != null) {
                        BluetoothGattCharacteristic chara = service.getCharacteristic(read_UUID_chara);
                        // 开启或关闭特征值的通知(第二个参数为true表示开启)
                        boolean b = mBluetoothGatt.setCharacteristicNotification(chara, true);
                        Log.d(TAG, "onServicesDiscovered 设置通知 " + b);
                    }
                }
            } else {
                Log.d(TAG, "onServicesDiscovered fail-->" + status);
            }
        }

        // 收到BLE服务端的数据变更时回调
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic chara) {
            super.onCharacteristicChanged(gatt, chara);
            String message = new String(chara.getValue()); // 把服务端返回的数据转成字符串
            Log.d(TAG, "onCharacteristicChanged "+message);
            runOnUiThread(() ->appendChatMsg(message, false)); // 往聊天窗口添加聊天消息
        }

        // 收到BLE服务端的数据写入时回调
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic chara, int status) {
            super.onCharacteristicWrite(gatt, chara, status);
            Log.d(TAG, "onCharacteristicWrite status="+status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                isLastSuccess = true;
            } else {
                Log.d(TAG, "write fail->" + status);
            }
        }

    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect(); // 断开GATT连接
        }
    }

    private boolean isLastSuccess = true; // 上一条消息是否发送成功
    // 发送聊天消息
    private void sendMesssage() {
        String message = et_input.getText().toString();
        if (TextUtils.isEmpty(message)) {
            Toast.makeText(this, "请先输入聊天消息", Toast.LENGTH_SHORT).show();
            return;
        }
        et_input.setText("");
        ViewUtil.hideOneInputMethod(this, et_input); // 隐藏软键盘
        new MessageThread(message).start(); // 启动消息发送线程
        appendChatMsg(message, true); // 往聊天窗口添加聊天消息
    }

    // 定义一个消息发送线程
    private class MessageThread extends Thread {
        private List<String> msgList; // 消息列表
        public MessageThread(String message) {
            msgList = ChatUtil.splitString(message, 20);
        }

        @Override
        public void run() {
            // 拿到写的特征值
            BluetoothGattCharacteristic chara = mBluetoothGatt.getService(BleConstant.UUID_SERVER)
                    .getCharacteristic(BleConstant.UUID_CHAR_WRITE);
            for (int i=0; i<msgList.size(); i++) {
                if (isLastSuccess) { // 需要等到上一条回调成功之后,才能发送下一条消息
                    isLastSuccess = false;
                    Log.d(TAG, "writeCharacteristic "+msgList.get(i));
                    chara.setValue(msgList.get(i)); // 设置写特征值
                    mBluetoothGatt.writeCharacteristic(chara); // 往GATT服务器写入特征值
                } else {
                    i--;
                }
                try {
                    sleep(300); // 休眠300毫秒,等待上一条的回调通知
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 往聊天窗口添加聊天消息
    private void appendChatMsg(String content, boolean isSelf) {
        appendNowMinute(); // 往聊天窗口添加当前时间
        // 把单条消息的线性布局添加到聊天窗口上
        ll_show.addView(ChatUtil.getChatView(this, content, isSelf));
        // 延迟100毫秒后启动聊天窗口的滚动任务
        new Handler(Looper.myLooper()).postDelayed(() -> {
            sv_chat.fullScroll(ScrollView.FOCUS_DOWN); // 滚动到底部
        }, 100);
    }

    // 往聊天窗口添加当前时间
    private void appendNowMinute() {
        String nowMinute = DateUtil.getNowMinute();
        if (!mMinute.substring(0, 4).equals(nowMinute.substring(0, 4))) {
            mMinute = nowMinute;
            ll_show.addView(ChatUtil.getHintView(this, nowMinute, dip_margin));
        }
    }

}

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/53711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springBoot开源MES生产制造执行系统源码带文字搭建教程

源码分享&#xff01;需要源码学习参考可私信。 技术框架&#xff1a;springBoot mybatis-plus shiro hutool layui swagger freemarker mysql8 echarts 运行环境&#xff1a;IntelliJ IDEA 2022 maven nginx 宝塔面板 系统功能 用户管理&#xff1a;用户是系统操…

用VS开发一款“飞机大战“单机游戏<C++>

显示界面如上图所示 自己找的背景和飞机素材,先将素材奉上. 接下来我先简单分析一下这个单机游戏的运行逻辑: 就像显示界面所显示的那样,我们想要实现的是自己的飞机在发射子弹(子弹在上图没显示),然后当子弹射到敌方飞机,这里设置了两种类型的飞机,如果读者想定义更多类型的…

基于51单片机农业大棚温控系统

资料编号&#xff1a;197 大棚种植温控系统概述&#xff1a; 本文介绍的是一个由单片机构成的温度控制系统&#xff0c;主要用来提供测温的解决方案&#xff0c;同时还能实时监控温度变化趋势&#xff0c;以及报警功能。它利用STC89C52RC单片机&#xff0c;DS18B20&#xff0c…

概率图模型:HMM(隐马),MEMM(最大熵),CRF(条件随机场)

1.概率图模型&#xff1a;HMM&#xff08;隐马&#xff09;,MEMM&#xff08;最大熵&#xff09;,CRF&#xff08;条件随机场&#xff09;概率&#xff1a;既然是一个图那么就是一个有圈有边的结构&#xff0c;圈代表随机向量&#xff0c;随机变量之间有边&#xff0c;边上有概…

数字验证学习笔记——UVM学习2 覆盖方法

一、覆盖方法 覆盖机制可以将原来所属的类型替换为另外一个新的类型。 在覆盖之后&#xff0c;原本用来创建原属类型的请求&#xff0c;将由工厂来创建新的替换类型。 无需再修改原始代码&#xff0c;继而保证了原有代码的封装性。新的替换类型必须与被替换类型兼容&#xff…

OpenStack 学习之 OVN : L2网络 ( Logical switches 逻辑交换机)

OVN Manual install & Configuration Open vSwitch 官网 参考 OVN学习&#xff08;一&#xff09; OVN实战一之GNS3操作指南及OVN入门 简单理解和知识 按照 OVN Manual install & Configuration 分别叫做 Controller 节点和 Compute 节点 &#xff0c;其他一般叫做…

Android Studio 打一个正式签名的Apk

如何打一个带正式签名文件的app (给自己的劳动成果冠名) 1. 选择build -> generate signed bundle/apk 2. 这里有两个选择, bundle or apk, 我们选择apk 于是勾选 apk, 并点下一步 3. 来到选择证书文件的地方, 但是我们这是第一次做, 还没有证书文件, 所以选择新建一个证…

【Docker学习系列】Docker学习2-docker设置阿里云镜像加速器

在上一篇中&#xff0c;我们学会了在centos中安装docer。我们知道&#xff0c;镜像都是外网的&#xff0c;镜像一般都是比较大的&#xff0c;因为种种原因&#xff0c;我们知道&#xff0c;从外网下载比较慢的。所以&#xff0c;本文&#xff0c;凯哥就介绍怎么将docker的镜像拉…

keil5打开keil4工程无法编译的情况解决办法!!!!!!

目录 1.情况 1.keil5使用&#xff08;打开&#xff09;keil4文件工程的时候报错 2.解决办法 如果是kei5打开kei4工程文件出现 步骤1&#xff1a; 步骤2&#xff1a; 步骤3&#xff1a; 1.情况 1.keil5使用&#xff08;打开&#xff09;keil4文件工程的时候报错 --- Erro…

vscode插件开发

作为一个前端开发相信&#xff0c;大家对于vscode非常熟悉。vscode是微软开源的一款基于 Electron 开发的代码编辑器。并且vscode支持通过插件来扩展编辑器的功能&#xff0c;比如Prettier插件帮助我们快速格式化代码&#xff0c;ES7 React/Redux/React-Native snippets插件帮助…

Kotlin高仿微信-第31篇-支付-服务

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

hadoop集群中存在配置较低的数据节点应用如何应对磁盘数据溢满的问题之rebalance

现象 登录hdfs web ui发现集群中一个节点的负载远高于另外的节点 分析 一般情况下集群在数据盘配置一样&#xff0c;几乎使用不到rebalance&#xff0c;但是我们的集群中有一个比较小的数据节点&#xff0c;因此我们需要关注各节点数据分布情况&#xff0c;及时进行rebalan…

手撕一个图片色卡提取器,可自定义提取色卡数量!

在一些特殊的业务场景中&#xff0c;我们需要一次性提取一张图片中的色卡信息&#xff0c;并且需要使用十六进制的颜色表示方法进行展示。 今天得空做了一个小工具&#xff0c;用来自定义的提取某一张图片中的色卡信息&#xff0c;需要提取某张图片中的色卡可以自行选择。 实现…

Python基础之SQLite数据库

Python与SQLite数据库 一、概述 对于非常简单的应用而言&#xff0c;使用文件作为持久化存储通常就足够了&#xff0c;但是大多数复杂的数据驱动的应用则需要全功能的关系数据库。 SQLite 的目标则是介于两者之间的中小系统。它量级轻、速度快&#xff0c;没有服务器&#xf…

数学辅导微信小程序设计与实现的源码+文档

摘 要 网络的广泛应用给生活带来了十分的便利。所以把数学辅导管理与现在网络相结合&#xff0c;利用java技术建设数学辅导微信小程序&#xff0c;实现数学辅导的信息化。则对于进一步提高数学辅导管理发展&#xff0c;丰富数学辅导管理经验能起到不少的促进作用。 数学辅导微…

【python】 int、float、double与16进制字符串的互相转换

import structdef intToHex(num): # int转16进制return hex(num)[2:].upper()def hexToInt(hexString): # 16进制转intreturn int(hexString, 16)def floatToHex(floatValue): # float转16进制return struct.pack(>f, floatValue).hex().upper()def hexToFloat(hexString…

chloris.earth ——Chloris 全球生物量 2003 - 2019 数据平台

概述 Chloris 全球生物量 2003 - 2019 数据集提供了地球陆地木本植被生态系统地上生物量存量和变化的估计值。它涵盖 2003 年至 2019 年期间&#xff0c;按年度时间步长计算。全球数据集的空间分辨率约为 4.6 公里。 这些地图和数据集是通过结合来自星载卫星的多个遥感测量结果…

如何保证PCB孔铜高可靠?

PCB板上电路导通&#xff0c;都是靠线路或过孔来传导的&#xff0c;从PCB制造流程可以看出&#xff0c;PCB完成铜厚是由PCB基铜厚度加板电厚度加图电厚度三部分组成&#xff0c;PCB孔铜厚度&#xff0c;是在两个电镀流程中完成&#xff0c;即全板电镀孔铜的厚度加图形电镀孔铜厚…

k8s概念

文章目录k8s概念为什么叫他k8s以及谁开发的k8s是什么k8s特点时光回溯传统部署时代&#xff1a;虚拟化部署时代容器部署时代&#xff1a;容器的优点为什么需要k8s&#xff0c;他能做什么&#xff1f;Kubernetes 不是什么&#xff1f;k8s概念 为什么叫他k8s以及谁开发的 kubern…