【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));
        }
    }
}

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

相关文章
|
7天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
13天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
13天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
17天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
16天前
|
监控 安全 开发者
山东布谷科技:关于直播源码|语音源码|一对一直播源码提交App Store的流程及重构经验
分享提交直播源码,一对一直播源码,语音源码到Appstore的重构经验!
|
17天前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码服务器环境配置及app功能
一对一直播源码阿里云服务器环境配置及要求
|
18天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
29 2
|
14天前
|
机器人
布谷直播App系统源码开发之后台管理功能详解
直播系统开发搭建管理后台功能详解!
|
SQL 数据库 Android开发
用SQLite查看编辑android导出的微信聊天记录
  上一篇我们已经能够完成文字版微信聊天记录导出android了,也即复制或剪切MicroMsg.db文件到电脑,以.db格式结尾的文件是数据库文件(database document),需要安装相关数据库软件进行编辑,比如SQLite。
2726 0
|
4天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。

热门文章

最新文章