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.pswp.cn/bicheng/71756.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/71756.shtml
英文地址,請注明出處:http://en.pswp.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;生產者向 主題&#…