Android 串口通信

引言

在iot项目中,Android 端总会有和硬件通信。

通信这里:串口通信,蓝牙通信或者局域网通信。

这里讲一下串口通信。

什么是串口?

“串口”(Serial Port)通常是指一种用于与外部设备进行串行通信的接口。如下是其中一种DB9的形式:

更加简单的,还有这样的形式:

只要有三条线,TX、RX和GND,或者A、B和GND,就可以去实现通讯。

...................................................... 奇怪??????我们手机上看不到这些玩意。

标准的Android智能手机和平板电脑通常并不直接暴露硬件级别的串口接口(如RS-232)给用户或开发者。

这是因为这些设备为了便携性和成本考虑,往往采用了不同的通信方式(如USB、蓝牙、Wi-Fi等)来与外部设备交互。

然而,在一些特定的Android设备(如自动售卖机的大尺寸屏幕、嵌入式Android设备等)或者通过特定的硬件扩展(如USB转串口适配器)上,开发者可能能够访问到串口接口。

为了在Android应用中使用串口通信,开发者可能会使用到一些第三方库或框架,这些库或框架通过JNI(Java Native Interface)技术调用底层Linux系统的串口通信接口。这些库通常提供了更高级别的API,使得开发者能够在不直接处理底层细节的情况下实现串口通信。

应用场景


不知道大家有没有接触过自动售卖机,在自动售卖机里面装有Android屏。在海外,大部分国家都没有普及扫码支付,都是使用一些纸币、硬币以及刷卡进行支付。而这些支付大部分都是使用串口的形式去对接,比如投入多少钱,通过串口发送给数据Android屏,Android屏收到后,触发指令出商品,今天呢,我们就来聊聊串口开发。
 

正文

串口常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。串口的主要特点是通信速度比较慢,但传输距离可以很长。常见的串口标准有RS-232、RS-485、TTL等。

通讯参数

一般这些数据,都是下位机提供给上位机的【上位机指的就是我们的Android屏幕,下位机指的就是上面我们提到的外部设备】,我们按照参数打开串口就可以收发数据了。

通讯接口是什么?

就是 RS232和RS485 。

RS232和RS485在收发数据上的区别主要体现在传输方式、传输距离、通信模式以及电平标准等方面。

RS232支持全双工和半双工两种传输方式,全双工可以实现数据的双向同时传输,而半双工则只能实现数据的单向传输,简单来说,就是只能一边来发送数据,另外一边不能主动发数据,只能响应数据,类似客户端和服务器的通讯一样。

RS485属于半双工总线,即在同一时刻,总线上只能有一个设备在发送数据,而其他设备则处于接收状态。

 波特率是什么?

34800,9600

波特率表示的是单位时间内传输的码元符号的个数,波特率越高,单位时间内传输的数据量就越大。但过大也会存在丢包的情况,视情况设定。

停止位、数据位、校验位的解释:

1.停止位:用于表示单个数据包的结束。常见的停止位有1位、1.5位和2位。停止位的主要作用是提供一个时间间隔,以确保数据包的完整性和正确性。例如,如果设置为1位停止位,则每个数据包后面都会跟随一个逻辑高电平(或逻辑低电平)的时间间隔,用于标识数据包的结束。

2.数据位:用于传输实际的数据信息。数据位的长度可以根据需要进行设置,常见的有5位、6位、7位和8位等。数据位越长,每个数据包所能携带的信息量就越大。在串口通信中,数据位通常是固定的,例如常用的ASCII码就是基于7位或8位数据位进行传输的。

3.校验位:用于检测数据传输过程中的错误。校验位可以通过多种方式生成,如奇校验、偶校验或无校验等。如果设置了校验位,则接收方会根据校验位的值来判断接收到的数据是否存在错误。如果存在错误,则可以根据具体的协议进行错误处理或重传。

下位机的数据发送案例

 厂家自定义协议

我们简单来看看他的协议,以及我们应该如何发送数据和接收数据。
(1)需要厂家提供通讯参数

(2)通讯文档,比如,查询下位机状态,还有很多协议内容,这里就讲一个:

有了这些信息,先不着急写代码,先使用串口工具测试一下收发数据是否正常。打开串口通讯工具,设置通讯参数,然后发送数据就可以了。

例如:  发送  AA 01 02 DD     接收  AA 02 02 01 DD

粘包、如何知道返回的数据对应谁的,数据通知…等等

在真实项目中,并不会如上面怎么简单,发送数据和接收数据就可以了,需要考虑:

  1. 数据丢包情况,需要重发,只到收到数据为止。
  2. 数据粘包的情况,需要和下位机约定好规则。
  3. 数据发送过来是二进制,我们需要转换,具体也是和下位机约定好规则

如何知道返回的数据对应谁的?

简单来说,就是你发送一个数据的时候,记录到一个变量里面。等读到数据后,你把数据和变量里面记录的内容发送上来,然后再继续发送下一个数据。以此类推。这样你就会知道数据是谁的了。

注意,这样的话,数据的发送,你就需要存储到一个集合里面,不断的往里面取,而不是异步随便调用send方法发送数据了。
 

 如何处理粘包的情况?

粘包是指在串口通信过程中,由于多种原因导致的多个独立的数据包在传输过程中被接收端视为一个连续的数据流,从而使得数据包之间的边界变得不明确,进而使得数据的解析变得困难。

比如:本来下位机返回的是AA 03 03 07 00 DD变成了AA 03 03 07 00 DD AA 03 03 07 00 DD,或者AA 03 03 07 00 DDAA 03 03两条数据连在一起情况。

怎么会出现这样的问题呢?

1、发送方发送数据的速度较快:当发送方连续发送多个数据包,且发送速度较快时,如果接收方的处理速度跟不上,就可能导致多个数据包在接收端被合并成一个大的数据流,即发生粘包现象。【降低上位机数据发送的频率】
2、接收方处理数据的速度较慢:接收方的处理速度是影响是否发生粘包的重要因素。如果接收方的处理速度较慢,无法及时将接收到的数据按照数据包进行分割和处理,就会发生粘包。【优化下位机代码】
3、传输数据量太大:有时候传输数据量太大,导致数据截断,或者缓存区不够。

处理办法:

**添加固定长度头部和尾部:**发送方在每个数据包前添加固定长度的头部,头部中包含数据包的长度信息,接收方根据头部中的长度信息来解析数据。如下:

左边蓝色是上位机发送给下位机的

右边橙色是下位机返回给上位机的。

消息头,数据内容长度,结束,这样我们就可以很好的处理数据了,如果数据发回来的不完整,或者连在一起,我们可以视情况,对数据进行解析分段,或者丢弃。

使用

Android SerialPort库通过JNI调用底层Linux的串口设备驱动,使得开发者可以通过简单的API来进行串口通信操作。

android-serialport-api下有两个主要的类

谷歌给的库:

https://code.google.com/archive/p/android-serialport-api/ 仅支持串口名称及波特率 。

那么我找到了两个做了扩展的两个库,根据情况用:

1、下面示例就讲这个

GitHub - licheedev/Android-SerialPort-API: Fork自Google开源的Android串口通信Demo,修改成Android Studio项目

2、这个库用着比较简单 

GitHub - xmaihh/Android-Serialport: 移植谷歌官方串口库,仅支持串口名称及波特率,该项目添加支持校验位、数据位、停止位、流控配置项

使用见: 快速使用Android串口_io.github.xmaihh:serialport-CSDN博客

示例:

 添加依赖

在 module 中的 build.gradle 中的 dependencies 中添加以下依赖:

dependencies {//串口implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0'
}

低版本的 gradle 在Project 中的 build.gradle 中的 allprojects 中添加以下 maven仓库 (不添加任然无法加载SerialPort);

allprojects {repositories {maven { url "https://jitpack.io" }//maven仓库}
}

高版本的 gradle 已经废弃了 allprojects 在 settings.gradle 中 repositories 添加以下maven仓库(不添加任然无法加载SerialPort);

dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()jcenter() // Warning: this repository is going to shut down soonmaven { url "https://jitpack.io" }//maven仓库}
}

编写串口处理类

1、串口处理类:SerialHandle ;简单概括这个类,就是通过串口对象去获取两个流(输入流、输出流),通过者两个流来监听数据或者写入指令,硬件收到后执行。同时注意配置参数(只要支持串口通讯的硬件,一般说明书上都会有写)

package com.chj233.serialmode.serialUtil;import android.serialport.SerialPort;
import android.util.Log;import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;/*** 串口实处理类*/
public class SerialHandle implements Runnable {private static final String TAG = "串口处理类";private String path = "";//串口地址private SerialPort mSerialPort;//串口对象private InputStream mInputStream;//串口的输入流对象private BufferedInputStream mBuffInputStream;//用于监听硬件返回的信息private OutputStream mOutputStream;//串口的输出流对象 用于发送指令private SerialInter serialInter;//串口回调接口private ScheduledFuture readTask;//串口读取任务/*** 添加串口回调** @param serialInter*/public void addSerialInter(SerialInter serialInter) {this.serialInter = serialInter;}/*** 打开串口** @param devicePath 串口地址(根据平板的说明说填写)* @param baudrate   波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)* @param isRead     是否持续监听串口返回的数据* @return 是否打开成功*/public boolean open(String devicePath, int baudrate, boolean isRead) {return open(devicePath, baudrate, 7, 1, 2, isRead);}/*** 打开串口** @param devicePath 串口地址(根据平板的说明说填写)* @param baudrate   波特率(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)* @param dataBits   数据位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)* @param stopBits   停止位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)* @param parity     校验位(根据对接的硬件填写 - 硬件说明书上"通讯"中会有标注)* @param isRead     是否持续监听串口返回的数据* @return 是否打开成功*/public boolean open(String devicePath, int baudrate, int dataBits, int stopBits, int parity, boolean isRead) {boolean isSucc = false;try {if (mSerialPort != null) close();File device = new File(devicePath);mSerialPort = SerialPort // 串口对象.newBuilder(device, baudrate) // 串口地址地址,波特率.dataBits(dataBits) // 数据位,默认8;可选值为5~8.stopBits(stopBits) // 停止位,默认1;1:1位停止位;2:2位停止位.parity(parity) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN).build(); // 打开串口并返回mInputStream = mSerialPort.getInputStream();mBuffInputStream = new BufferedInputStream(mInputStream);mOutputStream = mSerialPort.getOutputStream();isSucc = true;path = devicePath;if (isRead) readData();//开启识别} catch (Throwable tr) {close();isSucc = false;} finally {return isSucc;}}// 读取数据private void readData() {if (readTask != null) {readTask.cancel(true);try {Thread.sleep(160);} catch (InterruptedException e) {e.printStackTrace();}//此处睡眠:当取消任务时 线程池已经执行任务,无法取消,所以等待线程池的任务执行完毕readTask = null;}readTask = SerialManage.getInstance().getScheduledExecutor()//获取线程池.scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//执行一个循环任务}@Override//每隔 150 毫秒会触发一次runpublic void run() {if (Thread.currentThread().isInterrupted()) return;try {int available = mBuffInputStream.available();if (available == 0) return;byte[] received = new byte[1024];int size = mBuffInputStream.read(received);//读取以下串口是否有新的数据if (size > 0 && serialInter != null) serialInter.readData(path, received, size);} catch (IOException e) {Log.e(TAG, "串口读取数据异常:" + e.toString());}}/*** 关闭串口*/public void close(){try{if (mInputStream != null) mInputStream.close();}catch (Exception e){Log.e(TAG,"串口输入流对象关闭异常:" +e.toString());}try{if (mOutputStream != null) mOutputStream.close();}catch (Exception e){Log.e(TAG,"串口输出流对象关闭异常:" +e.toString());}try{if (mSerialPort != null) mSerialPort.close();mSerialPort = null;}catch (Exception e){Log.e(TAG,"串口对象关闭异常:" +e.toString());}}/*** 向串口发送指令*/public void send(final String msg) {byte[] bytes = hexStr2bytes(msg);//字符转成byte数组try {mOutputStream.write(bytes);//通过输出流写入数据} catch (Exception e) {e.printStackTrace();}}/*** 把十六进制表示的字节数组字符串,转换成十六进制字节数组** @param* @return byte[]*/private byte[] hexStr2bytes(String hex) {int len = (hex.length() / 2);byte[] result = new byte[len];char[] achar = hex.toUpperCase().toCharArray();for (int i = 0; i < len; i++) {int pos = i * 2;result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1]));}return result;}/*** 把16进制字符[0123456789abcde](含大小写)转成字节* @param c* @return*/private static int hexChar2byte(char c) {switch (c) {case '0':return 0;case '1':return 1;case '2':return 2;case '3':return 3;case '4':return 4;case '5':return 5;case '6':return 6;case '7':return 7;case '8':return 8;case '9':return 9;case 'a':case 'A':return 10;case 'b':case 'B':return 11;case 'c':case 'C':return 12;case 'd':case 'D':return 13;case 'e':case 'E':return 14;case 'f':case 'F':return 15;default:return -1;}}}

2、串口回调SerialInter;简单概括一下这个类,就是将SerialHandle类中产生的结果,返回给上一层的业务代码,解偶合。

package com.chj233.serialmode.serialUtil;/*** 串口回调*/
public interface SerialInter {/*** 连接结果回调* @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)* @param isSucc 连接是否成功*/void connectMsg(String path,boolean isSucc);/*** 读取到的数据回调* @param path 串口地址(当有多个串口需要统一处理时,可以用地址来区分)* @param bytes 读取到的数据* @param size 数据长度*/void readData(String path,byte[] bytes,int size);}

 3、串口统一管理SerialManage;简单概括一下这个类,用于管理串口的连接以及发送等功能,尤其是发送指令,极短时间内发送多个指令(例如:1毫秒内发送10个指令),多个指令之间会相互干扰。可能执行了第一个指令,可能一个都没执行。这个类不是必须的,如果有更好的方法可以自己定义。

package com.chj233.serialmode.serialUtil;import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;/*** 串口管理类*/
public class SerialManage {private static SerialManage instance;private ScheduledExecutorService scheduledExecutor;//线程池 同一管理保证只有一个private SerialHandle serialHandle;//串口连接 发送 读取处理对象private Queue<String> queueMsg = new ConcurrentLinkedQueue<String>();//线程安全到队列private ScheduledFuture sendStrTask;//循环发送任务private boolean isConnect = false;//串口是否连接private SerialManage() {scheduledExecutor = Executors.newScheduledThreadPool(8);//初始化8个线程}public static SerialManage getInstance() {if (instance == null) {synchronized (SerialManage.class) {if (instance == null) {instance = new SerialManage();}}}return instance;}/*** 获取线程池** @return*/public ScheduledExecutorService getScheduledExecutor() {return scheduledExecutor;}/*** 串口初始化** @param serialInter*/public void init(SerialInter serialInter) {if (serialHandle == null) {serialHandle = new SerialHandle();startSendTask();}serialHandle.addSerialInter(serialInter);}/*** 打开串口*/public void open() {isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//设置地址,波特率,开启读取串口数据}/*** 发送指令** @param msg*/public void send(String msg) {/*此处没有直接使用 serialHandle.send(msg); 方法去发送指令因为 某些硬件在极短时间内只能响应一个指令,232通讯一次发送多个指令会有物理干扰,让硬件接收到指令不准确;所以 此处将指令添加到队列中,排队执行,确保每个指令一定执行.若不相信可以试试用serialHandle.send(msg)方法循环发送10个不同的指令,看看10个指令的执行结果。*/queueMsg.offer(msg);//向队列添加指令}/*** 关闭串口*/public void colse() {serialHandle.close();//关闭串口}//启动发送发送任务private void startSendTask() {cancelSendTask();//先检查是否已经启动了任务 ? 若有则取消//每隔100毫秒检查一次 队列中是否有新的指令需要执行sendStrTask = scheduledExecutor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {if (!isConnect) return;//串口未连接 退出if (serialHandle == null) return;//串口未初始化 退出String msg = queueMsg.poll();//取出指令if (msg == null || "".equals(msg)) return;//无效指令 退出serialHandle.send(msg);//发送指令}}, 0, 100, TimeUnit.MILLISECONDS);}//取消发送任务private void cancelSendTask() {if (sendStrTask == null) return;sendStrTask.cancel(true);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}sendStrTask = null;}}

demo调用

package com.chj233.serialmode;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;
import android.view.View;import com.chj233.serialmode.serialUtil.SerialInter;
import com.chj233.serialmode.serialUtil.SerialManage;public class MainActivity extends AppCompatActivity implements SerialInter {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);SerialManage.getInstance().init(this);//串口初始化SerialManage.getInstance().open();//打开串口findViewById(R.id.send_but).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SerialManage.getInstance().send("Z");//发送指令 Z }});}@Overridepublic void connectMsg(String path, boolean isSucc) {String msg = isSucc ? "成功" : "失败";Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);}@Override//若在串口开启的方法中 传入false 此处不会返回数据public void readData(String path, byte[] bytes, int size) {
//        Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);}
}

多串口的使用
使用思想:一个单例对象控制一个串口,多串口时,写多个“SerialManage”就可以了。这里仅仅做举例不去考虑代码是否优雅,可以自行优化这段代码。(此案例中的SerialManage1、SerialManage2、SerialManage3、SerialManage4需要自己去复制,参照上面的SerialManage)

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化串口1SerialManage1.getInstance().init(new SerialInter(){@Overridepublic void connectMsg(String path, boolean isSucc) {String msg = isSucc ? "成功" : "失败";Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);}@Override//若在串口开启的方法中 传入false 此处不会返回数据public void readData(String path, byte[] bytes, int size) {
//        Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);}});//开启串口1SerialManage1.getInstance().open();//初始化串口2SerialManage2.getInstance().init(new SerialInter(){@Overridepublic void connectMsg(String path, boolean isSucc) {String msg = isSucc ? "成功" : "失败";Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);}@Override//若在串口开启的方法中 传入false 此处不会返回数据public void readData(String path, byte[] bytes, int size) {
//        Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);}});//打开串口2SerialManage2.getInstance().open();//初始化串口3SerialManage3.getInstance().init(new SerialInter(){@Overridepublic void connectMsg(String path, boolean isSucc) {String msg = isSucc ? "成功" : "失败";Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);}@Override//若在串口开启的方法中 传入false 此处不会返回数据public void readData(String path, byte[] bytes, int size) {
//        Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);}});//打开串口3SerialManage3.getInstance().open();//初始化串口4SerialManage4.getInstance().init(new SerialInter(){@Overridepublic void connectMsg(String path, boolean isSucc) {String msg = isSucc ? "成功" : "失败";Log.e("串口连接回调", "串口 "+ path + " -连接" + msg);}@Override//若在串口开启的方法中 传入false 此处不会返回数据public void readData(String path, byte[] bytes, int size) {
//        Log.e("串口数据回调","串口 "+ path + " -获取数据" + bytes);}});//打开串口4SerialManage4.getInstance().open();findViewById(R.id.send_but1).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SerialManage1.getInstance().send("Z");//给串口1发送指令 Z}});findViewById(R.id.send_but2).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SerialManage2.getInstance().send("Z");//给串口2发送指令 Z}});findViewById(R.id.send_but3).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SerialManage3.getInstance().send("Z");//给串口3发送指令 Z}});findViewById(R.id.send_but4).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {SerialManage4.getInstance().send("Z");//给串口4发送指令 Z}});}}

总结

串口通讯对于Android开发者来说,仅需关注如何连接、操作(发送指令)、读取数据;无论是232、485还是422,对于开发者来说连接、操作、读取代码都是一样的。

参考文章 : 

Android串口开发:Serialport(如何进行串口开发,数据发送,TX和RX,A和B,粘包)_android 串口-CSDN博客

 

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

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

相关文章

【计算机网络】OSI模型、TCP/IP模型、路由器、集线器、交换机

一、计算机网络分层结构 计算机网络分层结构 指将计算机网络的功能划分为多个层次&#xff0c;每个层次都有其特定的功能和协议&#xff0c;并且层次之间通过接口进行通信。 分层设计的优势&#xff1a; 模块化&#xff1a;各层独立发展&#xff08;如IPv4→IPv6&#xff0c…

从人机环境系统智能角度看传统IP的全球化二次创作法则

从人机环境系统智能的视角看&#xff0c;传统IP的全球化二次创作法则需结合技术、文化、伦理与环境的复杂协同。这一过程不仅是内容的本土化改编&#xff0c;更是人、机器与环境在动态交互中实现价值共创的体现。 一、人机环境系统智能的底层逻辑与IP二次创作的融合 1、感知层&…

实现 INFINI Console 与 GitHub 的单点登录集成:一站式身份验证解决方案

本文将为您详细解析如何通过 GitHub OAuth 2.0 协议&#xff0c;为 INFINI Console 实现高效、安全的单点登录&#xff08;Single Sign-On, SSO&#xff09;集成。通过此方案&#xff0c;用户可直接使用 GitHub 账户无缝登录 INFINI Console&#xff0c;简化身份验证流程&#…

记一次复杂分页查询的优化历程:从临时表到普通表的架构演进

1. 问题背景 在项目开发中&#xff0c;我们需要实现一个复杂的分页查询功能&#xff0c;涉及大量 IP 地址数据的处理和多表关联。在我接手这个项目的时候,代码是这样的 要知道代码里面的 ipsList 数据可能几万条甚至更多,这样拼接的sql,必然是要内存溢出的,一味地扩大jvm参数不…

C++关键字之mutable

1.介绍 在C中&#xff0c;mutable是一个关键字&#xff0c;用于修饰类的成员变量。它的主要作用是允许在常量成员函数或常量对象中修改被标记为mutable的成员变量。通常情况下&#xff0c;常量成员函数不能修改类的成员变量&#xff0c;但有些情况下&#xff0c;某些成员变量的…

云计算中的API网关是什么?为什么它很重要?

在云计算架构中&#xff0c;API网关&#xff08;API Gateway&#xff09;是一个重要的组件&#xff0c;主要用于管理、保护和优化不同服务之间的接口&#xff08;API&#xff09;通信。简单来说&#xff0c;API网关就像是一个中介&#xff0c;它充当客户端和后端服务之间的“桥…

深搜专题2:组合问题

描述 组合问题就是从n个元素中抽出r个元素(不分顺序且r < &#xff1d; n)&#xff0c; 我们可以简单地将n个元素理解为自然数1&#xff0c;2&#xff0c;…&#xff0c;n&#xff0c;从中任取r个数。 例如n &#xff1d; 5 &#xff0c;r &#xff1d; 3 &#xff0c;所…

uniapp多端适配

UniApp是一个基于Vue.js开发多端应用的框架&#xff0c;它可以让开发者编写一次代码&#xff0c;同时适配iOS、Android、Web等多个平台。 环境搭建&#xff1a; UniApp基于Vue.js开发&#xff0c;所以需要先安装Vue CLI npm install -g vue/cli 创建一个新的UniApp项目&…

Error [ERR_REQUIRE_ESM]: require() of ES Module

报错信息&#xff1a; 【报错】Message.js 导入方式不对&#xff0c;用的是 ES Moudle 的语法&#xff0c;提示使用 import 引入文件 项目开发没有用到 js-message 依赖&#xff0c;是 node-ipc 依赖中用到的 js-message 依赖&#xff0c; node-ipc 中限制 js-message 版本&a…

给小米/红米手机root(工具基本为官方工具)——KernelSU篇

目录 前言准备工作下载刷机包xiaomirom下载刷机包【适用于MIUI和hyperOS】“hyper更新”微信小程序【只适用于hyperOS】 下载KernelSU刷机所需程序和驱动文件 开始刷机设置手机第一种刷机方式【KMI】推荐提取boot或init_boot分区 第二种刷机方式【GKI】不推荐 结语 前言 刷机需…

CSS通过webkit-scrollbar设置滚动条样式

查看::-webkit-scrollbar-*各项关系 以下图为例&#xff0c;可以分别定义滚动条背景、滚动轨道、滚动滑块的样式。 需要先给外部容器设置高度&#xff0c;再设置overflow: auto&#xff0c;最后设置三个webkit属性。 <!DOCTYPE html> <html lang"en">…

自制操作系统前置知识汇编学习

今天要做什么&#xff1f; 为了更好的理解书中内容&#xff0c;需要学习下进制分析和汇编。 汇编语言其实应该叫叫机器指令符号化语言&#xff0c;目前的汇编语言是学习操作系统的基础。 一&#xff1a;触发器 电路触发器的锁存命令默认是断开的&#xff0c;是控制电路触发器…

uCOSIII-移植

一、uCOS移植 1.移植 C/OS-III前&#xff0c;需要获取C/OS-III 的源代码&#xff0c;C/CPU 和 C/LIB 这两个组件的源代码。 2.将获取的uCOSIII源代码添加到工程文件中&#xff1a; ①.uC-CPU/ARM-Cortex-M/ARMv7-M/ARM/cpu_a.asm、uC-CPU\ARM-Cortex-M\ARMv7-M\cpu_c.c 和 uC-…

Windows使用docker部署fastgpt出现的一些问题

文章目录 Windows使用docker部署FastGPT出现的一些问题1.docker部署pg一直重启的问题2.重启MongoDB之后一直出现“Waiting for MongoDB to start...”3.oneapi启动不了failed to get gpt-3.5-turbo token encoder Windows使用docker部署FastGPT出现的一些问题 1.docker部署pg一…

【Python爬虫(52)】探秘Scrapy:项目结构与配置全解析

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…

【Android】ViewPager的使用

AndroidManifest.xml <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"><applicationandroid:allowBac…

京东广告基于 Apache Doris 的冷热数据分层实践

一、背景介绍 京东广告围绕Apache Doris建设广告数据存储服务&#xff0c;为广告主提供实时广告效果报表和多维数据分析服务。历经多年发展&#xff0c;积累了海量的广告数据&#xff0c;目前系统总数据容量接近1PB&#xff0c;数据行数达到18万亿行&#xff0c;日查询请求量8…

Windows PyCharm的python项目移动存储位置后需要做的变更

项目使用的venv虚拟环境&#xff0c;因此项目移动存储位置后需要重新配置python解释器的位置&#xff0c;否则无法识别&#xff0c;若非虚拟环境中运行&#xff0c;则直接移动后打开即可&#xff0c;无需任何配置。 PyCharm版本为2021.3.3 (Professional Edition)&#xff0c;其…

前后端对接

前端与后端的对接主要通过 接口 进行数据交互&#xff0c;具体流程和方式如下&#xff1a; 1. 明确需求与接口定义 前后端协商&#xff1a;确定需要哪些接口、接口的功能、请求参数和返回格式。接口文档&#xff1a;使用工具&#xff08;如 Swagger、Postman、Apifox&#xff…

简识MQ之Kafka、ActiveMQ、RabbitMQ、RocketMQ传递机制

四种主流消息队列&#xff08;Kafka、ActiveMQ、RabbitMQ、RocketMQ&#xff09;的生产者与消费者传递信息的机制说明&#xff0c;以及实际使用中的注意事项和示例&#xff1a; 1. Apache Kafka 传递机制 模型&#xff1a;基于 发布-订阅模型&#xff0c;生产者向 主题&#…