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

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

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

一、发送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));
        }
    }
}

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

相关文章
|
3月前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
126 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
11天前
|
移动开发 缓存 开发框架
轻型社交同城交友圈子系统/兴趣爱好搭子聊天APP/同城本地行业信息圈子论坛
基于 UniApp 和 ThinkPHP6 构建,实现多端同步的轻量级社交系统。前端采用 UniApp 跨端开发框架,支持微信小程序、H5、APP 等多平台,结合 Vue.js 语法与图鸟 UI 组件库,快速构建美观界面。后端使用 TP6 提供 RESTful API,搭配 MySQL 数据库与 Redis 缓存优化性能。核心功能包括兴趣圈子管理、即时通讯、付费圈子、广告与会员体系等。同时,通过七牛云内容检测与实名认证保障社区安全,采用 Nginx+Redis 高并发架构确保稳定性。免费源码,适合开发者快速搭建同城社交平台,并可通过 AI 推荐与 AR 功能进一步提升用户体验。
77 4
|
2月前
|
存储 文件存储 Android开发
仿第八区APP分发下载打包封装系统源码
该系统为仿第八区APP分发下载打包封装系统源码,支持安卓、iOS及EXE程序分发,自动判断并稳定安装。智能提取应用信息,自动生成PLIST文件和图标,提供合理的点数扣除机制。支持企业签名在线提交、专属下载页面生成、云端存储(阿里云、七牛云),并优化签名流程,支持中文包及合并分发,确保高效稳定的下载体验。 [点击查看源码](https://download.csdn.net/download/huayula/90463452)
223 22
|
3月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
106 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
3月前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
121 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
2月前
|
小程序
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡
244 0
【04】微信支付商户申请下户到配置完整流程-微信开放平台移动APP应用通过-微信商户继续申请-微信开户函-视频声明-以及对公打款验证-申请+配置完整流程-优雅草卓伊凡
|
3月前
|
小程序 搜索推荐
2025同城线下陪玩APP开发/电竞游戏平台搭建游戏陪玩APP源码/语音APP开发
线下陪玩约玩APP旨在满足现代人的社交、兴趣分享、专业指导及休闲娱乐需求。用户可通过平台结识新朋友、找到志同道合的伙伴,并享受高质量的陪玩服务。平台提供用户注册登录、陪玩师筛选与预约、实时沟通等功能,支持个性化游戏体验和高效匹配。
141 0
2025同城线下陪玩APP开发/电竞游戏平台搭建游戏陪玩APP源码/语音APP开发
|
3月前
|
安全 JavaScript 前端开发
小游戏源码开发之可跨app软件对接是如何设计和开发的
小游戏开发团队常需应对跨平台需求,为此设计了成熟的解决方案。流程涵盖游戏设计、技术选型、接口设计等。首先明确游戏功能与特性,选择合适的技术架构和引擎(如Unity或Cocos2d-x)。接着设计通用接口,确保与不同App的无缝对接,并制定接口规范。开发过程中实现游戏逻辑和界面,完成登录、分享及数据对接功能。最后进行测试优化,确保兼容性和性能,发布后持续维护更新。
|
3月前
|
前端开发 Java 测试技术
语音app系统软件源码开发搭建新手启蒙篇
在移动互联网时代,语音App已成为生活和工作的重要工具。本文为新手开发者提供语音App系统软件源码开发的启蒙指南,涵盖需求分析、技术选型、界面设计、编码实现、测试部署等关键环节。通过明确需求、选择合适的技术框架、优化用户体验、严格测试及持续维护更新,帮助开发者掌握开发流程,快速搭建功能完善的语音App。
|
1月前
|
人工智能 JSON 小程序
【一步步开发AI运动APP】七、自定义姿态动作识别检测——之规则配置检测
本文介绍了如何通过【一步步开发AI运动APP】系列博文,利用自定义姿态识别检测技术开发高性能的AI运动应用。核心内容包括:1) 自定义姿态识别检测,满足人像入镜、动作开始/停止等需求;2) Pose-Calc引擎详解,支持角度匹配、逻辑运算等多种人体分析规则;3) 姿态检测规则编写与执行方法;4) 完整示例展示左右手平举姿态检测。通过这些技术,开发者可轻松实现定制化运动分析功能。