Android Ble蓝牙开发总结

625 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

全部源码可发邮件 305591377@qq.com

1.项目需求:实现Ble蓝牙客户端,可复用。

2.项目时间:2020.11.01-2022.3.12

3.项目环境: 软件:AndroidStuio3.2 grade4.6 汇承蓝牙串口助手HID(黄色) 硬件:汇承HC-42蓝牙5.0模块。 连接:使用模块自带小开发板进行连接,插入电脑,打开助手和app即可。 经测试,战舰开发板和串口助手+蓝牙模块也可以实现通讯。

手机是荣耀/vivo/oppo,如果搜索不到设备,打开手机设置,应用,权限,找到app,打开位置权限即可。

4.项目功能:本项目是基于物联网的控制——蓝牙技术,进行开发的。app主要实现蓝牙的扫描、连接、发送数据、接收数据功能。 蓝牙模块插入电脑,通过串口助手进行通讯。 最终实现app与模块之间的数据传输。

5.项目遇到的关键问题: 1.接收数据:可以使用Locat调试工具 2.接收数据显示到列表:使用适配器。 3.发送数据:获取GATT的写,然后发送,具体看代码。 4.定位权限:在旧版本手机下,可以默认打开。在荣耀手机下,很多时候需要手动打开。这可能是个BUG,暂时不影响使用。刚开始测试的时候,荣耀手机总是不能扫描,点击没反应,排查了很久都没有发现问题,最终发现是应用定位的问题。根据惯性,我们手动打开蓝牙和位置信息,但是app的定位默认是静止的,所以一直没有找到问题。 6.实现效果:

图片.png

图片.png

7.项目技术实现细节: 对于蓝牙开发,大致以下步骤 1.打开蓝牙 2.蓝牙扫描,列出可用设备 3.关闭蓝牙扫描(不关闭会一直扫描) 4.找到目标蓝牙设备进行连接 5.连接成功,进行通信 6.关闭蓝牙释放资源 接下来我们要根据上面6个步骤进行API的说明: (1)Service蓝牙功能集合,每一个Service都有一个UUID, (2)Characteristic 在service中也有好多个Characteristic 独立数据项,其中也有独立UUID 上面的两个uuid需要从硬件工程师中获取,我才用南京沁恒微电子股份有限公司的蓝牙助手获取模块的UUID,此处说明一下,人家这个在不知道UUID的情况下可以连接并且发送数据,原理暂时还没有弄懂。 (3)BluetoothAdapter 蓝牙的打开关闭等基本操作 (4)BluetoothDevice 蓝牙设备,扫描到的 (5)BluetoothGatt 蓝牙连接重连断开连接等操作的类 (6)BluetoothGattCharacteristic 数据通信操作类,读写等操作 `

activity_main.xml:
里面定义了两个列表,1个编辑框,3个按钮,使用线性布局。
array_item.xml:
这里面是接收列表的布局,暂时只用了一个控件。

MainActivity.java

1.首先定义一个函数,此函数是进行所有控件的初始化,还有搜索列表的点击事件。
Init_Item();
2.蓝牙初始化。
Init_Bluetooth();

mAdapter.enable();打开蓝牙
mAdapter.startDiscovery();开始扫描
扫描到设备之后,添加到list view,然后列表点击事件。
此时可以进行bluetoothGatt.getServices();操作了,此时连接就OK了。

通过bluetoothGatt可以获取设备的通知和读写。
setNotify(notifyCharacteristic);
setEnableNotify(notifyCharacteristic, true);
通过10.11行两个函数判断是否具有写的功能,如果是,传值
writeCharacteristic.setValue(str);
bluetoothGatt.writeCharacteristic(writeCharacteristic);
通过13.14行进行给模块发送数据,此时发送数据就OK了

接收数据函数onCharacteristicChanged
新建一个类bytesHexStrTranslate,用来处理value值,反正不处理就打印不出来。
接收到值 bytesHexStrTranslate.bytesToHexFun1(value)
然后把接收到的这个值放到list view里面打印出来,这样接收就OK了
至于接收的数据处理过程,暂时就理解为格式处理把。
`

`

**8.项目心得:**历时时间最长的一个项目。经过反思,一是自己基础薄弱,二是每次找网上的。 从没有想着自己写。  这次根据看的资料,一步一步实现,先实现打开关闭蓝牙,然后搜索,然后传输数据。

**9.项目代码:**
(使用方法:直接使用即可)

**MainActivity**

```java
package com.example.augenstern.ble_04;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String servicesUUID  = "0000ffe0-0000-1000-8000-00805f9b34fb";
    private static final String writeUUID     = "0000ffe1-0000-1000-8000-00805f9b34fb";
    private static final String notifyUUID    = "0000ffe0-0000-1000-8000-00805f9b34fb";

    public SimpleAdapter adapter;
    private static final String TAG = "TAG";
    public ListView mListView;
    public Button disConnected,scan,sendButton;
    public EditText sendDataEdit;

    public TextView statusConnected;

    public  ListView receiveList;
    public BluetoothDevice device;
    public BluetoothManager mManager;
    public BluetoothAdapter mAdapter;
    private BluetoothGatt bluetoothGatt;
    public ArrayAdapter<String> mStringArrayAdapter;
    public List<String> mStringList = new ArrayList<>();

    //2021.03.12 listview接收
    private List<String> listDevices = new ArrayList<String>();
    private ArrayAdapter<String> adtDevices;//显示搜索到的设备信息

    private BytesHexStrTranslate bytesHexStrTranslate;
    private BluetoothGattService bluetoothGattService;

    private BluetoothGattCharacteristic writeCharacteristic;
    private BluetoothGattCharacteristic notifyCharacteristic;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Init_Item();
        Init_Bluetooth();



    }

    //控件初始化
    private void Init_Item(){


        disConnected = findViewById(R.id.disConnected);
        disConnected.setOnClickListener(this);
        scan = findViewById(R.id.scan);
        scan.setOnClickListener(this);
        statusConnected = findViewById(R.id.status);
        statusConnected.setText("未连接...");
        sendDataEdit = findViewById(R.id.sendDataEdit);
        sendButton = findViewById(R.id.sendButton);
        sendButton.setOnClickListener(this);
        mListView = findViewById(R.id.devicesList);



        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if(mAdapter.isDiscovering()) mAdapter.cancelDiscovery();
                String info = ((TextView)view).getText().toString();
                Log.d(TAG, "onCreate: info = "+info);
                String adress = info.substring(info.length() - 17);
                Log.d(TAG, "onCreate: adress = "+adress);
                device = mAdapter.getRemoteDevice(adress);
                Log.d(TAG, "onItemClick: device = "+adress);
                bluetoothGatt = device.connectGatt(MainActivity.this, false, bluetoothGattCallback);
            }
        });
    }
    //蓝牙初始化
    private void Init_Bluetooth(){
        mManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        mAdapter = mManager.getAdapter();
        if(!mAdapter.isEnabled()){
            mAdapter.disable();
        }
        mAdapter.enable();
        Log.d(TAG, "on Init_Bluetooth: 蓝牙已经打开.....");

        if(mAdapter.isDiscovering()){
            mAdapter.cancelDiscovery();
            Log.d(TAG, "on Init_Bluetooth: 关闭搜索设备.....");
        }

        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},0);

//        if(Build.VERSION.SDK_INT >= 23){
//            if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.ACCESS_COARSE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
//                ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},0);
//            }else {
//                //权限已打开直接开始扫描
//                Log.d(TAG, "Init_Bluetooth: 定位权限已开启.....");
//                Toast.makeText(MainActivity.this,"定位权限已开启",Toast.LENGTH_SHORT).show();
//            }
//        }else {
//            //API小于23的直接开始扫描
//            Log.d(TAG, "Init_Bluetooth: API小于23,定位权限不用开启");
//            Toast.makeText(MainActivity.this," API小于23,定位权限不用开启",Toast.LENGTH_SHORT).show();
//        }
    }
    //扫描附近设备
    private void scanDevices(){
        mStringArrayAdapter = new ArrayAdapter<>(MainActivity.this,android.R.layout.simple_list_item_1,mStringList);
        mListView.setAdapter(mStringArrayAdapter);

        IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(mReceiver,intentFilter);

        mStringList.clear();
        mStringArrayAdapter.notifyDataSetChanged();

        Set pairedDevice = mAdapter.getBondedDevices();
        mStringList.add("已配对的设备:");
        if(pairedDevice.size() > 0){
            for(Iterator it = pairedDevice.iterator(); it.hasNext();){
                BluetoothDevice pDevice = (BluetoothDevice) it.next();
                mStringList.add("设备名称:"+pDevice.getName()+"\n设备地址:"+pDevice.getAddress());
                Log.d(TAG, "onReceive: 已配对的设备:"+pDevice.getName()+"\t"+pDevice.getAddress());
                mStringArrayAdapter.notifyDataSetChanged();
            }
        }else {
            mStringList.add("无");
            Log.d(TAG, "on scanDevices: 没有已配对的设备.....");
        }
        mStringList.add("附近的设备:");
        mAdapter.startDiscovery();
        Log.d(TAG, "on scanDevices: 开始扫描.....");
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if(action.equals(BluetoothDevice.ACTION_FOUND)){
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                mStringList.add("设备名称:"+device.getName()+"\n设备地址:"+device.getAddress());
                Log.d(TAG, "onReceive: 搜索到的设备:"+device.getName()+"\t"+device.getAddress());
                mStringArrayAdapter.notifyDataSetChanged();
            }
        }
    };

    private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d(TAG, "onConnectionStateChange: 连接成功.....");
                //gatt.setCharacteristicNotification(notifyCharacteristic,true);
                gatt.discoverServices();

                //跳转到通信界面
             /*   Intent intent = new Intent(MainActivity.this,SocketActivity.class);
                intent.putExtra("msg",status);
                startActivity(intent);*/
            }
            else {
                Log.d(TAG, "onConnectionStateChange: 连接断开.....");
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicWrite: 写入成功.....");
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {

            if (status == BluetoothGatt.GATT_SUCCESS) {
                List<BluetoothGattService> supportedGattServices = bluetoothGatt.getServices();
                for (BluetoothGattService BluetoothGattService : gatt.getServices()) {
                    Log.e(TAG, "--->BluetoothGattService" + BluetoothGattService.getUuid().toString());
                    Log.d(TAG, "onItemClick: BluetoothGattServer 服务 -----> " + BluetoothGattService.getUuid().toString());
                    //遍历所有特征
                    for (BluetoothGattCharacteristic bluetoothGattCharacteristic : BluetoothGattService.getCharacteristics()) {
                        Log.e("---->gattCharacteristic", bluetoothGattCharacteristic.getUuid().toString());
                        Log.d(TAG, "onItemClick: bluetoothGattCharacteristic 特征 -----> " + bluetoothGattCharacteristic.getUuid().toString());
                        String str = bluetoothGattCharacteristic.getUuid().toString();
                        Log.d(TAG, "onServicesDiscovered: notifyUUID = "+notifyUUID);
                        Log.d(TAG, "onServicesDiscovered: bluetoothGattCharacteristic = "+bluetoothGattCharacteristic);
                        notifyCharacteristic = bluetoothGattCharacteristic;
                        Log.d(TAG, "onServicesDiscovered: notifyCharacteristic 特征 = "+notifyCharacteristic);
                        writeCharacteristic = bluetoothGattCharacteristic;
                        Log.d(TAG, "onServicesDiscovered: writeCharacteristic 特征 = "+writeCharacteristic);
                    }
                }
            }
            if ((null == writeCharacteristic) || (null == notifyCharacteristic)) {
                //连接失败
                Log.d(TAG, "onServicesDiscovered: 失败.....");
            }else {
                setNotify(notifyCharacteristic);
                setEnableNotify(notifyCharacteristic, true);
                Log.d(TAG, "onServicesDiscovered: 成功.....");
            }

        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            final byte[] value = characteristic.getValue();
            Log.d(TAG, "onCharacteristicChanged: 收到数据: "+bytesHexStrTranslate.bytesToHexFun1(value));


            MainActivity.this.runOnUiThread(new Runnable() {
                public void run() {
                    ListView arraylistview=(ListView)findViewById(R.id.receiveList);
                    adtDevices=new ArrayAdapter<String>(MainActivity.this,R.layout.array_item,listDevices);
                    String str=bytesHexStrTranslate.bytesToHexFun1(value);

                    int n=0;
                    listDevices.add(n,str);
                    arraylistview.setAdapter(adtDevices);
                    n++;

                }
            });


        }

    };

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.disConnected:
                if(bluetoothGatt.connect()){
                    bluetoothGatt.disconnect();
                    bluetoothGatt.close();
                    Log.d(TAG, "onClick: 手动断开连接.....");
                    scanDevices();
                }
                break;
            case R.id.scan:
                scanDevices();
                break;
            case R.id.sendButton:
                String str = sendDataEdit.getText().toString();
//                Intent intent = new Intent(MainActivity.this,SocketActivity.class);
//
//                startActivity();
                writeCharacteristic.setValue(str);
                bluetoothGatt.writeCharacteristic(writeCharacteristic);
                Log.d(TAG, "onClick: 数据 %d "+str+"发送成功.....");
                break;
        }
    }
    public boolean setNotify(BluetoothGattCharacteristic data_char) {
        // 查看是否带有可通知属性
        if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) {
            bluetoothGatt.setCharacteristicNotification(data_char, true);
            BluetoothGattDescriptor descriptor = data_char.getDescriptor(
                    UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            bluetoothGatt.writeDescriptor(descriptor);
        } else if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)) {
            bluetoothGatt.setCharacteristicNotification(data_char, true);
            BluetoothGattDescriptor descriptor = data_char.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
            bluetoothGatt.writeDescriptor(descriptor);
        }
        return true;
    }
    public boolean setEnableNotify(BluetoothGattCharacteristic data_char, boolean enable) {
        // 没有打开蓝
        bluetoothGatt.setCharacteristicNotification(data_char, enable);
        return true;
    }
}

class BytesHexStrTranslate {
    private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    public static String bytesToHexFun1(byte[] bytes) {
        // 一个byte为8位,可用两个十六进制位标识
        char[] buf = new char[bytes.length * 2];
        int a = 0;
        int index = 0;
        for (byte b : bytes) { // 使用除与取余进行转换
            if (b < 0) {
                a = 256 + b;
            } else {
                a = b;
            }

            buf[index++] = HEX_CHAR[a / 16];
            buf[index++] = HEX_CHAR[a % 16];
        }

        return new String(buf);
    }

    public static String bytesToHexFun2(byte[] bytes) {
        char[] buf = new char[bytes.length * 2];
        int index = 0;
        for (byte b : bytes) { // 利用位运算进行转换,可以看作方法一的变种
            buf[index++] = HEX_CHAR[b >>> 4 & 0xf];
            buf[index++] = HEX_CHAR[b & 0xf];
        }

        return new String(buf);
    }

    public static String bytesToHexFun3(byte[] bytes) {
        StringBuilder buf = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) { // 使用String的format方法进行转换
            buf.append(String.format("%02x", new Integer(b & 0xff)));
        }

        return buf.toString();
    }

    public static byte[] StringtoBytes(String str) {
        if (str == null || str.trim().equals("")) {
            return new byte[0];
        }

        byte[] bytes = new byte[str.length() / 2];
        for (int i = 0; i < str.length() / 2; i++) {
            String subStr = str.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }

        return bytes;
    }
}
`

`<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/devicesList"
        android:layout_width="match_parent"
        android:layout_height="200dp" />
    <TextView
        android:id="@+id/status"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="20sp"/>

    <Button
        android:id="@+id/scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="扫描设备" />

    <Button
        android:id="@+id/disConnected"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="断开连接" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/sendDataEdit"
            android:layout_width="250dp"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:text="0x100"/>
        <Button
            android:id="@+id/sendButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发送"
            android:textSize="20sp"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >


        <ListView
            android:id="@+id/receiveList"
            android:layout_width="250dp"
            android:layout_height="200dp" />

    </LinearLayout>

</LinearLayout>
`