Android GB∕T 19056-2021 汽车行驶记录仪-定位性能测试

news2025/7/15 15:20:44

        最近在做汽车相关的项目,然后要根据最新发布的新国标接入,我们这边之前没有做过的,我是第一个,好惨啊。远程调试不通,后来还专门到现场进行了测试,来到刚刚转正没几天就开始出差了,命太苦了。

1. 言归正传,先看一下协议说明:

 我之前没看懂什么叫起始段,然后走了很多弯路,然后在现场问了一个其他公司的老手之后才发现原来前面有定义的:

         其实只要认真看文档,加上构建buffer的知识其实还是很好写出来的,但我两者都不具备,所以就走了很多弯路下面直接上代码。第一个注意点是我们Android设备是作为蓝牙监听方,那么就需要有一个监听的uuid,这个uuid在车管所那边是固定的:00001101-0000-1000-8000-00805F9B34FB,这个你问他们,也不会告诉你的,因为对方开发测试程序的人就不在。

2. 源代码

2.1 创建蓝牙服务器

调用:

acceptThread = new AcceptThread("00001101-0000-1000-8000-00805F9B34FB");
        acceptThread.start();
/**
     * 蓝牙监听线程
     */
    private class AcceptThread extends Thread {
        private BluetoothServerSocket mmServerSocket = null;

        public AcceptThread(String uuid) {
            BluetoothServerSocket tmp = null;
            try {
                PLog.i("AcceptThread uuid = " + uuid);
                // 创建一个蓝牙服务器
                mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("VDRXXXXXXXX", UUID.fromString(uuid));
                PLog.i("AcceptThread mmServerSocket = " + mmServerSocket);
            } catch (Exception e) {
                Log.e(TAG, "Socket's listen() method failed", e);
            }
        }

        public void run() {
            BluetoothSocket bluetoothSocket = null;
            while (true) {
                try {
                    // 接受客户端的连接请求
                    bluetoothSocket = mmServerSocket.accept();
                    PLog.i("AcceptThread bluetoothSocket = " + bluetoothSocket);
                } catch (Exception e) {
                    Log.e(TAG, "Socket's accept() method failed", e);
                    break;
                }

                if (bluetoothSocket != null) {
                    PLog.i("AcceptThread accept success");
                    // 开启接收线程
                    socketThread = new MyBluetoothService.ConnectedThread(bluetoothSocket, MainActivity.this);
                    socketThread.start();

                    // 关闭服务
                    try {
                        mmServerSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }

        // Closes the connect socket and causes the thread to finish.
        public void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "Could not close the connect socket", e);
            }
        }
    }

2.2 接收线程

初始化:

public ConnectedThread(BluetoothSocket socket, GpsInfoCallBack gpsInfoCallBack) {
            PLog.i("ConnectedThread start");
            this.gpsInfoCallBack = gpsInfoCallBack;
            mmSocket = socket;
            // Get the input and output streams; using temp objects because
            // member streams are final.
            try {
                mmInStream = socket.getInputStream();
            } catch (IOException e) {
                Log.e(TAG, "Error occurred when creating input stream", e);
            }
            try {
                mmOutStream = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "Error occurred when creating output stream", e);
            }

            PLog.i("ConnectedThread mmInStream =" + mmInStream + ", mmOutStream = " + mmOutStream);
        }

run方法:

public void run() {
            // 读取数据
            mmBuffer = new byte[1024];
            int numBytes; // bytes returned from read()

            // Keep listening to the InputStream until an exception occurs.
            while (true) {
                try {
                    // Read from the InputStream.
                    numBytes = mmInStream.read(mmBuffer);
                    if (numBytes == -1) {
                        continue;
                    }
                    byte[] start_bytes = new byte[8];
                    byte[] data_bytes = new byte[2];
                    byte[] check_bytes = new byte[1];

                    PLog.i("ConnectedThread 接收到数据 numBytes = " + numBytes + ", mmBuffer = "
                            + CommUtils.bytesToHexString(mmBuffer));
                    System.arraycopy(mmBuffer, 0, start_bytes, 0, 8);
                    System.arraycopy(mmBuffer, 8, data_bytes, 0, 2);
                    System.arraycopy(mmBuffer, 10, check_bytes, 0, 1);

                    String startStr = CommUtils.bytesToHexString(start_bytes);

                    int dataLength = CommUtils.bytesToShortBigEndian(data_bytes);

                    String checkStr = CommUtils.bytesToHexString(check_bytes);
                    PLog.i("ConnectedThread startStr = " + startStr + ", dataLength = "
                            + dataLength + ", checkStr = " + checkStr);

                    if (startStr.startsWith("757856")) {
                        PLog.i("ConnectedThread 开始测试");
                        // 表示开始测试
                        startTimerTask();
                    } else if (startStr.startsWith("757857")) {
                        // 表示结束测试
                        PLog.i("ConnectedThread 结束测试");
                        cancel();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    PLog.w("ConnectedThread read data error = ", e);
                    break;
                }
            }
        }

2.3 数据回传

        private void startTimerTask() {
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    String data = gpsInfoCallBack.getCallback();
                    PLog.i("ConnectedThread data = " + data);
                    if (TextUtils.isEmpty(data)) {
                        PLog.i("ConnectedThread 数据 is empty");
                        return;
                    }
                    String[] splitArray = data.split("\\|");
                    if (splitArray.length < 2) {
                        PLog.i("ConnectedThread 数据不符合规则");
                        return;
                    }
                    String GGA = splitArray[0];
                    String RMC = splitArray[1];

                    byte[] sendData = buildSendData3(GGA, RMC);
                    String sendDataHex = CommUtils.bytesToHexString(sendData);

                    PLog.i("ConnectedThread sendDataHex = " + sendDataHex);

                    write(sendData);
                    PLog.i("ConnectedThread senddata success ");
                }
            };
            timer.schedule(timerTask, 0, 1000);
        }

2.4 数据构建

        这里有一点需要注意参数gga和rmc需要完成的数据帧,不需要对数据做处理:

eg:

$GNGGA,021620.994,3135.0893,N,12013.6337,E,0,0,,26.8,M,6.8,M,,*5A
$GNRMC,021621.494,A,3135.0889,N,12013.6337,E,0.791,2.88,111122,,,A*4F

至于每个数据代表什么,网上有很多资料的,这里就不一一赘述了。

private byte[] buildSendData3(String gga, String rmc) {
            // 起始段
            byte[] sendBuff = new byte[1024];
            System.arraycopy(mmBuffer, 0, sendBuff, 0, sendBuff.length);
            // 起始字节 记录仪 57 78
            System.arraycopy(CommUtils.hexToBytes("57"), 0, sendBuff, 0, 1);
            System.arraycopy(CommUtils.hexToBytes("78"), 0, sendBuff, 1, 1);

            // 命令字
            System.arraycopy(CommUtils.hexToBytes("D6"), 0, sendBuff, 2, 1);

            // 数据长度放最后计算

            // 传输状态字 80-通讯机发出 c0--通讯机重传  00--记录仪正常发出 40--记录仪重传
            System.arraycopy(CommUtils.hexToBytes("00"), 0, sendBuff, 5, 1);
            // 计算序列号
            byte[] ack_bytes = new byte[2];
            System.arraycopy(mmBuffer, 6, ack_bytes, 0, 2);
            String ackHex = CommUtils.bytesToHexString(ack_bytes);
            int ack = CommUtils.hex2Int(ackHex);
            int newAck = ack + 1;
            PLog.i("buildSendData3 newAck = " + newAck);
            String newHexAck = CommUtils.numToHex16(newAck);
            // 传输序列号 匹配成功后为 01,之后每个数据 + 1
            System.arraycopy(CommUtils.hexToBytes(newHexAck), 0, sendBuff, 6, 2);

            // 数据段
            String ggaHex = CommUtils.str2HexStr(gga);
            byte[] ggaBytes = CommUtils.hexToBytes(ggaHex);
            int ggaLength = ggaBytes.length;
            String rmcHex = CommUtils.str2HexStr(rmc);
            byte[] rmcBytes = CommUtils.hexToBytes(rmcHex);
            int rmcLength = rmcBytes.length;

            SimpleDateFormat format_nomalHH = new SimpleDateFormat("yy-MM-dd HH:mm:ss", Locale.getDefault());
            String date = format_nomalHH.format(new Date());
            String[] temp = date.split(" ");
            String year = temp[0].split("-")[0];
            String month = temp[0].split("-")[1];
            String day = temp[0].split("-")[2];
            String hour = temp[1].split(":")[0];
            String minute = temp[1].split(":")[1];
            String second = temp[1].split(":")[2];
            // 1. 时间
            System.arraycopy(BCDUtils.StrToBCDBytes(year), 0, sendBuff, 8, 1);
            System.arraycopy(BCDUtils.StrToBCDBytes(month), 0, sendBuff, 9, 1);
            System.arraycopy(BCDUtils.StrToBCDBytes(day), 0, sendBuff, 10, 1);
            System.arraycopy(BCDUtils.StrToBCDBytes(hour), 0, sendBuff, 11, 1);
            System.arraycopy(BCDUtils.StrToBCDBytes(minute), 0, sendBuff, 12, 1);
            System.arraycopy(BCDUtils.StrToBCDBytes(second), 0, sendBuff, 13, 1);
            // 2. GGA 数据长度
            String ggaLengthHex = CommUtils.numToHex16(ggaLength);
            System.arraycopy(CommUtils.hexToBytes(ggaLengthHex), 0, sendBuff, 14, 2);
            // 3. RMC数据长度
            String rmcLengthHex = CommUtils.numToHex16(rmcLength);
            System.arraycopy(CommUtils.hexToBytes(rmcLengthHex), 0, sendBuff, 16, 2);

            PLog.i("buildSendData3 ggaLengthHex = " + ggaLengthHex + ", ggaLength = " + ggaLength);
            PLog.i("buildSendData3 rmcLengthHex = " + rmcLengthHex + ", rmcLength = " + rmcLength);
            // 4. GGA 数据 ascii
            System.arraycopy(ggaBytes, 0, sendBuff, 18, ggaLength);
            // 5. RMC 数据 ascii
            System.arraycopy(rmcBytes, 0, sendBuff, 18 + ggaLength, rmcLength);

            int start = 18 + ggaLength + rmcLength;
            PLog.i("buildSendData3 start = " + start);

            int totalDatalength = start + 1;
            // 数据帧长度-为起始段(8)、数据段、校验字(1)三个部分的总字节数。
            String dataLengthHex = CommUtils.numToHex16(totalDatalength);
            PLog.i("buildSendData3 dataLenth = " + totalDatalength + ", datalengthHex = " + dataLengthHex);
            System.arraycopy(CommUtils.hexToBytes(dataLengthHex), 0, sendBuff, 3, 2);

            // 校验字 最后一位
            byte[] checkByte = new byte[1];
            checkByte[0] = (byte) (sendBuff[0] ^ sendBuff[1]);
            for (int i = 2; i < start; i++) {
                checkByte[0] = (byte) (checkByte[0] ^ sendBuff[i]);
            }
            String chekHexStr = CommUtils.bytesToHexString(checkByte);
            PLog.i("buildSendData3 chekHexStr = " + chekHexStr);
            System.arraycopy(CommUtils.hexToBytes(chekHexStr), 0, sendBuff, start, 1);
            return sendBuff;
        }

        主要它有些是16进制的数据,需要转成二进制去写入,这个转换如果有问题,你拼接的在对对方也是无响应的,因为对方的设备是按照固定的数据格式接收的,有问题的数据,一律不接收。

 3. 工具类

        最后把这个工具类放上,可以供给大家作参考,觉得可以的,可以支持一下博主,感谢。

package com.example.testgpsproject;


import java.io.ByteArrayOutputStream;

public class CommUtils {

    /**
     * 将日期转成6字节的bcd
     *
     * @param asc
     * @return
     */
    public static byte[] str2Bcd(String asc) {
        int len = asc.length();
        int mod = len % 2;

        if (mod != 0) {
            asc = "0" + asc;
            len = asc.length();
        }

        byte abt[] = new byte[len];
        if (len >= 2) {
            len = len / 2;
        }

        byte bbt[] = new byte[len];
        abt = asc.getBytes();
        int j, k;

        for (int p = 0; p < asc.length() / 2; p++) {
            if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
                j = abt[2 * p] - '0';
            } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
                j = abt[2 * p] - 'a' + 0x0a;
            } else {
                j = abt[2 * p] - 'A' + 0x0a;
            }

            if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
                k = abt[2 * p + 1] - '0';
            } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
                k = abt[2 * p + 1] - 'a' + 0x0a;
            } else {
                k = abt[2 * p + 1] - 'A' + 0x0a;
            }

            int a = (j << 4) + k;
            byte b = (byte) a;
            bbt[p] = b;
        }
        return bbt;
    }

    /**
     * string 转16进制字符串
     *
     * @param str
     * @return
     */
    public static String str2HexStr(String str) {
        char[] chars = "0123456789abcdef".toCharArray();
        StringBuilder sb = new StringBuilder("");
        byte[] bs = str.getBytes();
        int bit;
        for (int i = 0; i < bs.length; i++) {
            bit = (bs[i] & 0x0f0) >> 4;
            sb.append(chars[bit]);
            bit = bs[i] & 0x0f;
            sb.append(chars[bit]);
            // sb.append(' ');
        }
        return sb.toString().trim();
    }

    /**
     * 16进制字符串 转成 普通字符串
     *
     * @param hexStr
     * @return
     */
    public static String hexStr2Str(String hexStr) {
        String str = "0123456789abcdef";
        char[] hexs = hexStr.toCharArray();
        byte[] bytes = new byte[hexStr.length() / 2];
        int n;
        for (int i = 0; i < bytes.length; i++) {
            n = str.indexOf(hexs[2 * i]) * 16;
            n += str.indexOf(hexs[2 * i + 1]);
            bytes[i] = (byte) (n & 0xff);
        }
        return new String(bytes);
    }


    /*小端,低字节在后*/
    public static short bytesToShortLittleEndian(byte[] bytes) {
        // byte数组中序号小的在右边
        return (short) (bytes[0] & 0xFF | (bytes[1] & 0xFF) << 8);
    }

    /**
     * 16进制转10进制
     *
     * @param hexstr
     * @return
     */
    public static int hex2Int(String hexstr) {
        return Integer.parseInt(hexstr, 16);
    }

    // 数字转16进制 2个字节表示
    public static String numToHex16(int b) {
        return String.format("%04x", b);
    }

    /*大端,高字节在后*/
    public static short bytesToShortBigEndian(byte[] bytes) {
        // byte数组中序号大的在右边
        return (short) (bytes[1] & 0xFF | (bytes[0] & 0xFF) << 8);
    }

    public static byte[] hexToBytes(String hex) {
        hex = hex.length() % 2 != 0 ? "0" + hex : hex;

        byte[] b = new byte[hex.length() / 2];
        for (int i = 0; i < b.length; i++) {
            int index = i * 2;
            int v = Integer.parseInt(hex.substring(index, index + 2), 16);
            b[i] = (byte) v;
        }
        return b;
    }

    public static String bytesToHexString(byte... src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 通过16进制字符串计算出校验字段的值
     *
     * @param hexStr 序号1-7的16进制字符串
     * @return
     */
    public static String getCheckHex(String hexStr) {
        byte[] sendBuff = CommUtils.hexToBytes(hexStr);
        PLog.i("getCheckHex length = " + sendBuff.length);
        byte[] checkByte = new byte[1];
        checkByte[0] = (byte) (sendBuff[0] ^ sendBuff[1]);
        for (int i = 2; i < sendBuff.length; i++) {
            checkByte[0] = (byte) (checkByte[0] ^ sendBuff[i]);
        }

        String chekStr = CommUtils.bytesToHexString(checkByte);
        return chekStr;
    }
}

4. 总结:

        其实文档已经说的很清楚了,但是数据的组装,以及网上没有相关的资料,我是扒了它的不分源码才找到头绪的,记录一下。

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

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

相关文章

前沿系列--Transform架构[架构分析+代码实现]

文章目录前言总体架构总体任务使用输入部分EmbeddingPosition Encodingwhy实现注意部分注意力机制/自注意力掩码作用如何工作形状解释完整实现多头注意力实现Norm处理FeedForward 以及连接编码器解码器中间层组装输出层模型组装总结前言 Transform这玩意的大名我想就不用我多说…

2022年笔记本电脑市场总结:华为份额激增95%,联想、苹果均下滑

2022年即将过去&#xff0c;又来到了进行今年笔记本市场总结的环节。 今年笔记本电脑行业依然没有突破天花板。可以看到&#xff0c;2022年上半年期间市场还保持着良好的增长态势&#xff0c;到了下半年则出现了需求萎靡的状态。从一整年的数据来看&#xff0c;笔记本电脑行业还…

安装nnpy出现错误以及解决

今天在安装P4C的时候&#xff0c;参考了这篇博客 P4语言环境安装-前端编译器p4c、后端编译器p4c-bm2-ss、交换模型bmv2、Tutorial-张华Guido 在进行到这一步时&#xff0c;出现了错误 #安装python-pip sudo apt install python-pip #我是安装在/home/guido(user_name)/路径下…

阿里云斩获2022全球分布式云大会两项大奖

12 月 21日&#xff0c;“2022 全球分布式云大会深圳站”正式举办。阿里云弹性计算团队凭借在算力领域的创新突破与全面的分布式云产品矩阵布局&#xff0c;荣获“2022 年度中国算力先锋 TOP3”、“2022 年度分布式算力市场领导力企业”两项大奖。 图一&#xff1a;2022年度中国…

智能勘探 | AIRIOT智慧油田管理解决方案

石油勘探和开采地处偏远地区&#xff0c;涉及面广且生产规模大。特殊的作业环境下&#xff0c;使得工作人员作业条件艰苦&#xff0c;仅靠人工值守难度很大&#xff0c;不可避免的遇到一系列硬核挑战&#xff1a; 1、设备维护难度较高&#xff1b; 2、采油厂分布地域广、分散…

北面羽绒服成热议产品,小红书透露出哪些营销新趋势?

小红书浓厚的种草氛围&#xff0c;为品牌创造了良好的营销环境&#xff0c;想要在小红书做好内容种草&#xff0c;需要洞察用户的真实需求来推广产品&#xff0c;实现营销效果的最大化。那如何发现小红书上的热门品类&#xff1f;制定品牌营销策略&#xff1f;挑选优质合作达人…

面试题题review

面试题 已知一个几乎有序的数组&#xff0c;几乎有序是指&#xff0c;如果把数组排好顺序的话&#xff0c;每个元素移动的距离可以不超过k&#xff0c;并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。给定一个int数组A&#xff0c;同时给定A的大小…

多准则决策问题评估方法 | 层次分析法(含代码)

目前多准则决策问题的评估方法主要分为定性分析方法和定量分析方法两类。定性分析方法主要包括专家咨询、熵权法、案例研究和德尔菲法等&#xff1b;定量分析法主要包括层次分析法、主成分分析法、因子分析法、模糊综合评价法、色综合评价法以及数据包络分析法&#xff08;DEA法…

启科 QuSaaS 真随机数解决方案与 Amazon Braket 结合实践

作者&#xff1a;1.丘秉宜&#xff0c;2.邵伟&#xff0c;3.黄文&#xff0c;4.郭梦杰 1.亚马逊云科技 HERO&#xff1b;2.开发者生态负责人&#xff1b;3.DEVOPS 工程师&#xff1b;4.资深研发工程师 1、概述 随机性&#xff08;Randomness&#xff09;是偶然性的一种形式&…

【JavaEE】Tomcat

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录【Tomcat】&#xff1a;http服务器THINK努力成为你想成为的人一定很酷&#xff01; 【Tomcat】&#xff1a;http服务器 http客户端就是我们平时使用的浏览器&#xff0c;但是我们还需要开发实现一个服务器来搭建网…

[FireshellCTF2020]Caas

打开界面&#xff0c;是一个运行代码的*框&#xff0c;然后我们输入 本来以为会是一个ssti模板&#xff0c;{{8*8}}却仍然还是报错&#xff0c;print echo 等输出都不行 只能分析分析报错信息看看有没有什么有用的东西 /tmp目录下的&#xff0c;class_7eamm1tu3.c后缀名是c&…

【论文阅读】(2020)Knapsack polytopes: a survey(上)

文章目录一、Abstract 摘要二、Introduction 介绍三、General polyhedral structure 一般多面体结构3.1 Basic properties 基本性质3.2 Covers 覆盖不等式四、Binary formulations based on strong covers 基于强覆盖的二元公式五、Lifting 提升5.1 Sequential up-lifting5.2 S…

2. bean加载控制

1. Controller加载控制 因为功能不同&#xff0c;要避免Spring错误的加载到SpringMVC的bean 1.1 Controller加载控制与业务bean加载控制 SpringMVC相关bean&#xff08;表现层bean&#xff09; Spring控制的bean 业务bean&#xff08;Service&#xff09; 功能bean&#xf…

Java——图

概念 图是由顶点和边组成的一种数据结构&#xff0c;我们之前介绍的树形结构中&#xff0c;树的每一个节点就是顶点&#xff0c;顶点与顶点中的连线就是边&#xff0c;也就是说&#xff0c;树是一种特殊的图 图中的边如果有方向&#xff0c;那么这个图就称为有向图&#xff0…

个人总结详细版的C++调用Opencv和Halcon封装dll

一、前言&#xff1a; 在C调用opencv和Halcon封装的过程中踩过很多坑&#xff0c;然而网上却查不到清晰地教程。在此个人总结详细教程&#xff0c;以免后人踩坑。记录下&#xff0c;以后自己忘了也可以来看看。 二、教程细节 2.1 我使用的IDE是vs2017,下面所有的介绍也都是以此…

理解操作系统(Linux)

操作系统是一款对软硬件资源进行管理的软件&#xff01; 操作系统为什么要对软硬件资源进行管理呢&#xff1f; 操作系统通过合理的管理软硬件资源的手段&#xff0c;为用户提供良好的&#xff08;稳定的、高效的、安全的&#xff09;执行环境。 操作系统是如何进行管理的呢…

机器学习100天(十七):017 逻辑回归梯度下降

机器学习 100 天,今天讲的是:逻辑回归-梯度下降! 在讲解了逻辑回归的基本原理和损失函数之后,我们来推导逻辑回归模型中参数 w 和 b 的梯度表达式。 我们之前介绍过,计算逻辑回归的代价函数实际上包含了下面三个过程: Z = W T X + b Z=W^TX+b Z=

ASOC系统简析

一 嵌入式音频系统介绍 上图是音频系统的硬件模拟图&#xff0c;声卡通过I2S接口与cpu进行音频数据传输&#xff0c;通过I2C接口与cpu进行控制通讯。 录音数据通路&#xff1a;麦克风---->声卡------I2S------>DMA---->内存&#xff1b; 播放数据通路&#xff1a;内存…

HaaS EDU物联网项目实战:野外救援项目

HaaS EDU K1是一款高颜值、高性能、高集成度的物联网开发板&#xff0c;板载功能强大的4核&#xff08;双核300Mhz M33双核1GHz A7&#xff09;主芯片&#xff0c;2.4G/5G双频Wi-Fi&#xff0c;双模蓝牙&#xff08;经典蓝牙/BLE&#xff09;&#xff0c;并自带丰富的传感器与小…

Exception in thread “main“ java.lang.NoClassDefFoundError

项目场景&#xff1a; 验证继承情况下子类创建对象时&#xff0c;先调用父类的构造方法&#xff0c;再调用子类的构造方法 问题描述 随机&#xff08;不同次数的测试下&#xff09;会产生Exception in thread "main" java.lang.NoClassDefFoundError错误 package e…