Android Handler 完全指南

在 Android 開發中,Handler 是線程通信的核心工具 —— 當你在子線程下載圖片后需要更新 UI,當你在 TCP 連接中收到數據需要通知界面,當你需要延遲執行某個任務時,都會用到 Handler。這個看似簡單的類,卻蘊含著 Android 的消息循環機制,也是很多開發者容易出錯的地方(如內存泄漏、消息發送時機錯誤)。

本文將從 Handler 的底層原理講起,通過實例解析其在 TCP 通信、UI 更新等場景中的應用,深入分析常見問題(如內存泄漏、消息丟失)及解決方案,最終掌握 Handler 的正確使用姿勢。

一、Handler 核心原理:Android 消息循環機制

要正確使用 Handler,必須先理解其背后的 “消息循環” 機制 —— 這是 Android 主線程不阻塞的關鍵。

1.1 為什么需要 Handler?

Android 規定:只有主線程(UI 線程)能更新 UI,子線程不能直接操作 View。這是因為 View 不是線程安全的,多線程并發修改可能導致 UI 混亂。

但實際開發中,很多操作需要在子線程執行(如下載、網絡請求、TCP 數據接收),執行完成后需通知 UI 線程更新。此時就需要 Handler 作為 “橋梁”:

  • 子線程:執行耗時操作,完成后通過 Handler 發送消息;
  • 主線程:通過 Handler 接收消息,更新 UI。

例如在 TCP 通信中:

1.子線程通過Socket接收數據;

2.調用handler.sendMessage()將數據發送到主線程;

3.主線程的 Handler 接收消息,顯示數據到 TextView。

1.2 Handler 的工作流程

Handler 的運行依賴四個核心組件,它們協同完成消息傳遞:

組件

作用

所在線程

Handler

發送和處理消息(sendMessage() handleMessage())

可在任意線程創建

Message

消息載體(存儲需要傳遞的數據)

無固定線程

MessageQueue

消息隊列(存放 Handler 發送的消息)

所屬 Looper 線程

Looper

消息循環(不斷從隊列中取消息并分發)

所屬線程(如主線程)

完整工作流程

1.創建 Handler:在主線程創建 Handler,綁定主線程的 Looper 和 MessageQueue;

2.發送消息:子線程調用handler.sendMessage(msg),將消息放入主線程的 MessageQueue;

3.循環取消息:主線程的 Looper 不斷從 MessageQueue 中取出消息;

4.處理消息:Looper 將消息分發到 Handler 的handleMessage()方法,在主線程執行。

這個流程的關鍵是:Handler 綁定哪個線程的 Looper,消息就會在哪個線程處理。主線程的 Looper 由系統自動創建,子線程需手動創建 Looper(如 IntentService)。

1.3 主線程與子線程的 Looper 差異

線程類型

Looper 創建方式

MessageQueue 狀態

典型應用場景

主線程

系統自動創建(ActivityThread)

一直存在(直到 APP 退出)

UI 更新、Handler.postDelayed

子線程

需手動調用Looper.prepare()

需調用Looper.loop()啟動

后臺任務、TCP 長連接監聽

注意

  • 主線程的 Looper 是 “永久循環” 的,不會退出(否則 APP 會崩潰);
  • 子線程的 Looper 若不手動停止,會導致線程無法銷毀(需調用looper.quit())。

二、Handler 基礎使用:從消息發送到處理

掌握 Handler 的基本用法,是實現線程通信的第一步。

2.1 基本使用步驟

步驟 1:創建 Handler(主線程)

在 Activity 中創建 Handler,重寫handleMessage()處理消息:

public class MainActivity extends AppCompatActivity {// 1. 在主線程創建Handler(自動綁定主線程Looper)private Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {// 3. 主線程處理消息switch (msg.what) {case 1: // 消息類型1:顯示文本String text = (String) msg.obj;mTextView.setText(text);break;case 2: // 消息類型2:更新進度int progress = msg.arg1;mProgressBar.setProgress(progress);break;}}};
}
  • 關鍵:通過Looper.getMainLooper()明確綁定主線程,避免在子線程創建時出錯;
  • 消息類型:用msg.what區分不同消息(如 1 表示文本,2 表示進度);
  • 攜帶數據
  • 簡單數據:msg.arg1 msg.arg2(int 類型);
  • 對象數據:msg.obj(需強轉,如 String、自定義對象)。
步驟 2:子線程發送消息

在子線程中執行耗時操作,完成后通過 Handler 發送消息:

// 子線程執行TCP數據接收(示例)
new Thread(() -> {try {// 假設已建立TCP連接,獲取輸入流InputStream inputStream = mSocket.getInputStream();byte[] buffer = new byte[1024];int length;// 循環接收數據(TCP長連接)while ((length = inputStream.read(buffer)) != -1) {String data = new String(buffer, 0, length);// 1. 創建消息Message msg = Message.obtain(); // 推薦:復用消息池,減少內存消耗msg.what = 1; // 消息類型:文本msg.obj = data; // 攜帶數據// 2. 發送消息到主線程mHandler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();}
}).start();
  • 消息創建:優先用Message.obtain()而非new Message(),前者從消息池復用對象,減少 GC;
  • 發送方式:除sendMessage()外,還有:
  • sendEmptyMessage(int what):發送空消息(僅類型);
  • sendMessageDelayed(msg, delayMillis):延遲發送消息;
  • post(Runnable r):發送 Runnable(內部轉為消息)。
步驟 3:使用 Runnable 簡化代碼

若只需在主線程執行一段代碼,可直接用post()發送 Runnable,無需重寫handleMessage():

// 子線程中下載圖片后更新UI
new Thread(() -> {// 1. 子線程下載圖片(耗時操作)Bitmap bitmap = downloadImage("https://example.com/image.jpg");// 2. 通過Handler在主線程顯示圖片mHandler.post(() -> {// 此代碼在主線程執行mImageView.setImageBitmap(bitmap);});
}).start();

post()的本質是將 Runnable 包裝成 Message,當消息被處理時,執行 Runnable 的run()方法。

三、Handler 在 TCP 通信中的實戰應用

在 TCP 長連接中,Handler 是子線程(接收數據)與主線程(更新 UI)通信的核心工具,需結合 TCP 特性設計消息處理邏輯。

3.1 消息類型設計

TCP 通信中需處理多種場景(連接成功、接收數據、連接失敗),可通過what定義消息類型:

// 定義消息類型常量
public static final int MSG_CONNECT_SUCCESS = 1; // 連接成功
public static final int MSG_CONNECT_FAILED = 2;  // 連接失敗
public static final int MSG_RECEIVE_DATA = 3;    // 收到數據
public static final int MSG_DISCONNECT = 4;      // 斷開連接// 初始化Handler
private Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what) {case MSG_CONNECT_SUCCESS:mStatusTv.setText("TCP連接成功");break;case MSG_CONNECT_FAILED:String error = (String) msg.obj;mStatusTv.setText("連接失敗:" + error);break;case MSG_RECEIVE_DATA:String data = (String) msg.obj;mDataTv.append("收到:" + data + "\n");break;case MSG_DISCONNECT:mStatusTv.setText("TCP已斷開");break;}}
};

3.2 在 TCP 客戶端中集成 Handler

將 Handler 與前文的 TcpClient 結合,實現消息分發:

public class TcpClient {private Handler mHandler; // 外部傳入的主線程Handler// 構造方法接收Handlerpublic TcpClient(String host, int port, Handler handler) {mHost = host;mPort = port;mHandler = handler;}// 連接成功后通過Handler通知UIprivate void onConnectSuccess() {Message msg = Message.obtain();msg.what = MainActivity.MSG_CONNECT_SUCCESS;mHandler.sendMessage(msg);}// 收到數據后通過Handler通知UIprivate void onDataReceived(byte[] data) {Message msg = Message.obtain();msg.what = MainActivity.MSG_RECEIVE_DATA;msg.obj = new String(data, StandardCharsets.UTF_8);mHandler.sendMessage(msg);}// 連接失敗時通知UIprivate void onConnectFailed(Exception e) {Message msg = Message.obtain();msg.what = MainActivity.MSG_CONNECT_FAILED;msg.obj = e.getMessage();mHandler.sendMessage(msg);}
}

在 Activity 中初始化:

// Activity中創建Handler并傳入TcpClient
mTcpClient = new TcpClient("192.168.1.100", 8080, mHandler);

這種設計將 “TCP 通信邏輯” 與 “UI 更新邏輯” 分離,符合單一職責原則。

3.3 延遲任務:TCP 重連實現

Handler 的postDelayed()可實現延遲任務,適合 TCP 斷線后的重連邏輯:

// TCP斷開后延遲3秒重連
private void scheduleReconnect() {mHandler.postDelayed(() -> {if (!isConnected) {mStatusTv.setText("嘗試重連...");mTcpClient.connect(); // 重新連接}}, 3000); // 延遲3秒
}// 在斷開連接時調用
@Override
public void onDisconnect() {mHandler.sendEmptyMessage(MSG_DISCONNECT);scheduleReconnect(); // 觸發重連
}
  • 優勢:相比Thread.sleep(),postDelayed()不會阻塞線程;
  • 注意:重連前需判斷連接狀態,避免重復連接。

四、Handler 常見問題及解決方案

Handler 使用不當會導致內存泄漏、消息丟失等問題,這些也是面試高頻考點。

4.1 Handler 內存泄漏及解決

現象:Activity 銷毀后,Handler 仍持有 Activity 引用,導致 Activity 無法被回收,引發內存泄漏。

原因

1.Handler 是非靜態內部類,默認持有外部 Activity 的引用;

2.若 Handler 發送的消息未處理完(如延遲消息),消息會持有 Handler 引用;

3.形成引用鏈:Message → Handler → Activity,導致 Activity 無法回收。

解決方案

  1. 使用靜態內部類 + 弱引用
    // 1. 靜態內部類(不持有外部Activity引用)
    private static class MyHandler extends Handler {// 2. 弱引用持有Activity(不會阻止回收)private WeakReference<MainActivity> mActivityRef;public MyHandler(MainActivity activity) {mActivityRef = new WeakReference<>(activity);}@Overridepublic void handleMessage(@NonNull Message msg) {MainActivity activity = mActivityRef.get();// 3. 判斷Activity是否已回收if (activity == null || activity.isFinishing()) {return;}// 4. 安全操作UIswitch (msg.what) {case MSG_RECEIVE_DATA:activity.mDataTv.append((String) msg.obj);break;}}
    }// 在Activity中初始化
    private MyHandler mHandler = new MyHandler(this);

  2. Activity 銷毀時移除消息
    @Override
    protected void onDestroy() {super.onDestroy();// 移除所有未處理的消息和回調mHandler.removeCallbacksAndMessages(null);// 斷開TCP連接if (mTcpClient != null) {mTcpClient.disconnect();}
    }

關鍵:靜態內部類打破引用鏈,弱引用避免強持有,onDestroy移除消息確保無殘留。

4.2 消息發送時機錯誤

現象:在 Activity 銷毀后發送消息,導致handleMessage中操作已銷毀的 View,引發空指針異常。

場景

  • TCP 子線程接收數據后,Activity 已銷毀,但仍調用handler.sendMessage();
  • 延遲消息發送后,Activity 被銷毀,消息仍會執行。

解決方案

1.發送消息前檢查 Activity 狀態

// 子線程發送消息前檢查
if (activity != null && !activity.isFinishing()) {mHandler.sendMessage(msg);
}

2.在handleMessage中檢查

@Override
public void handleMessage(@NonNull Message msg) {MainActivity activity = mActivityRef.get();if (activity == null || activity.isFinishing()) {return; // Activity已銷毀,不處理}// 處理消息
}

4.3 消息順序與優先級

現象:消息按發送順序處理,但某些場景需要優先處理重要消息(如 TCP 連接成功需優先于數據接收)。

解決方案

1.設置消息優先級:通過msg.setAsynchronous(true)設置異步消息(優先級高于同步消息);

2.使用sendMessageAtFrontOfQueue():將消息插入隊列頭部,優先處理:

// 連接成功消息優先處理
Message msg = Message.obtain();
msg.what = MSG_CONNECT_SUCCESS;
mHandler.sendMessageAtFrontOfQueue(msg); // 插入隊列頭部

4.4 子線程中創建 Handler 報錯

現象:在子線程中直接創建 Handler,拋出Can't create handler inside thread that has not called Looper.prepare()異常。

原因:子線程默認沒有 Looper,Handler 無法綁定 MessageQueue。

解決方案

  1. 手動初始化 Looper
    new Thread(() -> {// 1. 初始化LooperLooper.prepare();// 2. 創建Handler(綁定當前子線程的Looper)Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {// 在子線程處理消息(如TCP數據解析)}};// 3. 啟動消息循環Looper.loop();
    }).start();

  2. 使用HandlerThread(推薦)
    // 1. 創建HandlerThread(自帶Looper的線程)
    HandlerThread handlerThread = new HandlerThread("TCP-Parse-Thread");
    handlerThread.start();// 2. 獲取子線程的Looper
    Looper looper = handlerThread.getLooper();// 3. 創建綁定子線程的Handler
    Handler parseHandler = new Handler(looper) {@Overridepublic void handleMessage(@NonNull Message msg) {// 在子線程解析TCP數據(不阻塞主線程)byte[] data = (byte[]) msg.obj;parseTcpData(data);}
    };// 4. 發送消息到子線程Handler
    Message msg = Message.obtain();
    msg.obj = receivedData;
    parseHandler.sendMessage(msg);

適用場景:TCP 數據接收后需復雜解析(如協議解碼),可在子線程 Handler 中處理,避免阻塞主線程。

五、Handler 高級用法:替代方案與最佳實踐

除了基礎用法,Handler 還有一些高級技巧,以及更現代的替代方案。

5.1 Handler 與 Thread、AsyncTask 的對比

工具

優勢

劣勢

適用場景

Handler

靈活控制消息、支持延遲、適合長連接

需手動管理消息和線程

TCP 通信、UI 更新、延遲任務

Thread

簡單直接,適合單一耗時操作

無法直接更新 UI,無消息機制

單次耗時操作(如下載一個文件)

AsyncTask

封裝了線程切換,適合短耗時操作

生命周期關聯差,易內存泄漏

簡單后臺任務(如獲取網絡接口)

結論:Handler 是最靈活的線程通信工具,尤其適合需要持續通信的場景(如 TCP)。

5.2 Kotlin 中使用 Handler:協程替代方案

在 Kotlin 中,可使用協程(Coroutine)替代 Handler 的部分功能,代碼更簡潔:

// 協程實現延遲任務(替代Handler.postDelayed)
lifecycleScope.launch {delay(3000) // 延遲3秒(非阻塞)// 在主線程執行mStatusTv.text = "3秒后更新"
}// 協程實現子線程→主線程通信(替代Handler.sendMessage)
lifecycleScope.launch(Dispatchers.IO) { // 子線程// TCP接收數據val data = tcpClient.receiveData()// 切換到主線程withContext(Dispatchers.Main) {mDataTv.append(data) // 更新UI}
}

優勢

  • 無需手動管理 Handler 和消息;
  • 代碼線性執行,可讀性更高;
  • 自動綁定生命周期(lifecycleScope在頁面銷毀時取消任務)。

注意:協程是基于 Handler 和線程池實現的,并非完全替代 Handler,復雜消息場景仍需 Handler。

5.3 Handler 最佳實踐總結

1.內存安全

  • 用靜態 Handler + 弱引用,避免內存泄漏;
  • Activity 銷毀時移除所有消息。

2.性能優化

  • 用Message.obtain()復用消息,減少對象創建;
  • 避免發送大量小消息(可合并為批量消息)。

3.場景適配

  • UI 更新:綁定主線程 Handler;
  • 子線程任務:用HandlerThread或協程;
  • 延遲任務:優先postDelayed(),而非Thread.sleep()。

4.代碼規范

  • 消息類型用常量定義(如MSG_RECEIVE_DATA);
  • 明確區分不同線程的 Handler(可在變量名標注,如mMainHandler mParseHandler)。

六、Handler 在 Android 系統中的應用

Handler 不僅是開發者的工具,也是 Android 系統的核心機制,理解這些應用能加深對 Handler 的理解。

6.1 主線程消息循環(ActivityThread)

Android 應用啟動時,ActivityThread的main()方法會初始化主線程 Looper:

public static void main(String[] args) {// 初始化主線程LooperLooper.prepareMainLooper();// 創建ActivityThreadActivityThread thread = new ActivityThread();// 啟動消息循環Looper.loop();
}

所有四大組件(Activity、Service 等)的生命周期回調,都是通過 Handler 發送消息觸發的 —— 這就是為什么生命周期方法運行在主線程。

6.2 View 的 post () 方法

View 的post()方法內部使用 Handler 實現,可將任務投遞到主線程執行:

// 本質是通過Handler發送Runnable
mTextView.post(() -> {mTextView.setText("延遲更新");
});

其源碼實現類似:

public boolean post(Runnable action) {// 獲取View所在線程的Handler(通常是主線程)final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// 若View未附加到窗口,用主線程HandlerViewRootImpl.getRunQueue().post(action);return true;
}

6.3 定時任務(Choreographer)

Android 的 UI 刷新(如invalidate())依賴Choreographer,其內部用 Handler 接收垂直同步信號(VSYNC),確保 UI 繪制與屏幕刷新同步。

七、總結:Handler 的核心價值

Handler 是 Android 線程通信的基石,其核心價值在于:

1.線程隔離:嚴格區分 UI 線程和子線程,確保 UI 操作的線程安全;

2.消息調度:靈活控制消息的發送時機、順序和優先級;

3.系統基石:Android 的四大組件、UI 刷新等核心機制都依賴 Handler。

掌握 Handler 不僅能解決實際開發中的線程通信問題,更能理解 Android 系統的運行原理。在 TCP 通信、文件下載、定時任務等場景中,正確使用 Handler 能讓你的代碼更穩定、高效。

最后記住:Handler 的本質是 “消息傳遞工具”,合理使用它,而不是被它的復雜機制嚇倒 —— 從基礎用法開始,逐步理解原理,就能輕松應對各種場景。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/91293.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/91293.shtml
英文地址,請注明出處:http://en.pswp.cn/web/91293.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

梳理Ego-Planner模式下5通道、6通道與無人機模式的關系

Copilot said: 你需要梳理Ego-Planner模式下5通道、6通道與無人機模式的關系&#xff0c;以及軌跡跟隨、緊急情況和人工接管的流程。下面結合你的描述和三張圖片&#xff0c;對核心邏輯進行總結和整理&#xff1a; --- ## 一、狀態組合與對應模式&#xff08;見圖1&#xff09;…

odbc和jdbc什么區別

ODBC 和 JDBC 都是用于連接和操作數據庫的標準接口&#xff0c;但它們分別面向不同的編程語言和平臺。下面是它們的區別與對比&#xff0c;幫助你理解它們的用途和適用場景。&#x1f4ca; 一、基本概念對比特性ODBC&#xff08;Open Database Connectivity&#xff09;JDBC&am…

境外期貨Level2高頻Tick歷史行情數據獲取與應用指南

在金融量化分析中&#xff0c;本地數據的高效使用是提升策略效果的重要基礎。本文以CSV格式的本地數據為核心&#xff0c;以外盤期貨分鐘數據、CME/COMEX/CBOT歷史行情為例&#xff0c;闡述專業化的數據處理與應用方法&#xff0c;為研究者提供可行性方案。一、數據預處理標準化…

迅為RK3588開發板安卓GPIO調用-APP運行測試

將網盤上的安卓工程文件復制到 Windows 電腦上。確保工程路徑中使用英文字符&#xff0c;不包含中文。接著&#xff0c;啟動 Android Studio&#xff0c;點擊“Open”按鈕選擇應用工程文件夾&#xff0c;然后點擊“OK”。由于下載 Gradle 和各種 Jar 包可能需要一段時間&#x…

以太坊下一階段的關鍵——隱私

1. 引言 隨著以太坊慶祝其十周年紀念&#xff0c;Aztec Labs 聯合創始人兼 CEO Zac Williamson 和以太坊基金會 PSE 負責人 Sam Richards 表示&#xff0c;以太坊必須加強其對隱私的原始承諾。 以太坊慶祝十周年紀念&#xff0c;標志著智能合約、去中心化金融&#xff08;DeF…

CTFpwn學習筆記1-棧溢出

棧溢出通過寫入超出數組定義范圍的字符長度達到溢出&#xff0c;從而覆蓋棧上其余數據&#xff0c;覆蓋返回地址約等于控制程序執行流例如&#xff1a;經過ida反編譯后&#xff0c;發現這里要將v2的值修改為11.28125才能獲得flag&#xff0c;同時我們可以發現這里使用了gets這個…

使用 Android Studio 中的 Gemini,讓 Flutter 開發更便捷

作者 / Flutter 產品經理 Ander Dobo 及 Gemini in Android Studio 產品經理 Sandhya Mohan在 Android Studio 中創建 Android 應用的 Flutter 開發者將迎來一次重大的飛躍: Android Studio 中的 Gemini 已全面支持 Dart 和 Flutter 開發&#xff01;這意味著您可以直接在您青睞…

Deep Learning_ Foundations and Concepts-Springer (2024)【拜讀】前向編碼器20章

Diffusion Models 擴散模型 我們已經了解到&#xff0c;構建強大的生成模型的一種有效方法是&#xff1a;先引入一個關于潛在變量z的分布p(z)&#xff0c;然后使用深度神經網絡將z變換到數據空間x。由于神經網絡具有通用性&#xff0c;能夠將簡單固定的分布轉化為關于x的高度靈…

Spring全局異常處理最佳實踐

全局異常處理器詳解 什么是全局異常處理器&#xff1f; 全局異常處理器是Spring框架提供的統一異常處理機制&#xff0c;用于集中處理應用程序中所有控制器&#xff08;Controller&#xff09;層拋出的異常。它的核心價值在于&#xff1a; 統一異常處理&#xff1a;避免在每個C…

STL學習(十一、常用的算數算法和集合算法)

目錄 一、常用的算數算法 1.accmulate 2.fill 二、常用的集合算法 1.set_intersection 2.set_union 3.set_difference 一、常用的算數算法 包含頭文件為<numeric> 1.accmulate 函數原型 accmulate(iterator beg, iterator end, value) // 計算元素累計和 // …

DeepSort 算法分析詳解

DeepSort 算法分析詳解 DeepSort 簡介 DeepSort (Deep Learning Sort) 是一種基于深度學習的多目標跟蹤算法&#xff0c;由 Wojke 等人于 2017 年提出。它是對傳統 Sort (Simple Online and Realtime Tracking) 算法的改進&#xff0c;通過引入深度特征提取網絡來增強目標關聯的…

基于深度學習的醫學圖像分析:使用Capsule Networks實現醫學圖像分類

前言 醫學圖像分析是計算機視覺領域中的一個重要應用&#xff0c;特別是在醫學圖像分類任務中&#xff0c;深度學習技術已經取得了顯著的進展。醫學圖像分類是指將醫學圖像分配到預定義的類別中&#xff0c;這對于疾病的早期診斷和治療具有重要意義。近年來&#xff0c;Capsule…

G9打卡——ACGAN

&#x1f368; 本文為&#x1f517;365天深度學習訓練營中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 1.導入庫及參數 import argparse import os import numpy as npimport torchvision.transforms as transforms from torchvision.utils import save_imagefrom…

應用war/jar包是用TongWeb企業版,還是嵌入版?

在判斷應用應該采用TongWeb哪個版本時&#xff0c;存在一種錯誤的觀點&#xff1a;如果應用包是jar包&#xff0c;則需要采用TongWeb嵌入版&#xff1b;如果應用包是war包&#xff0c;則需要采用TongWeb企業版。 正確的判斷方法&#xff1a;1. 首先應用為jar包&#xff0c;且符…

Linux ARM 平臺 C 語言操作 Excel 文件的常用庫與工具匯總(支持 xls 和 xlsx)

在 Linux 或嵌入式 ARM 平臺開發中&#xff0c;使用 C 語言操作 Excel 文件是一項常見需求&#xff0c;特別是在工業設備數據采集、日志導出、報表生成等場景。Excel 文件格式復雜&#xff0c;手工解析成本高&#xff0c;因此使用現成的庫可以極大簡化開發工作。 本文整理了若…

Apache Ignite 集群標識(Cluster ID)和集群標簽(Cluster Tag)

這是一個關于 Apache Ignite 集群標識&#xff08;Cluster ID&#xff09;和集群標簽&#xff08;Cluster Tag&#xff09; 的重要配置概念。我們來一步步深入理解這段文檔的含義&#xff0c;并結合實際場景說明其用途。&#x1f9e9; 一、核心概念&#xff1a;Cluster ID 與 C…

基于 Hadoop 生態圈的數據倉庫實踐 —— OLAP 與數據可視化(三)

目錄 三、Impala OLAP 實例 1. 建立 olap 庫、表、視圖 2. 初始裝載數據 3. 修改銷售訂單定期裝載腳本 4. 定義 OLAP 需求 5. 執行 OLAP 查詢 三、Impala OLAP 實例 本節使用前面銷售訂單的例子說明如何使用 Impala 做 OLAP 類型的查詢&#xff0c;以及實際遇到的問題及解…

如何不讓android studio自動換行

一、關閉逗號后自動換行設置 打開設置界面 進入 File → Settings &#xff08;Windows/Linux&#xff09;或 Preferences &#xff08;macOS&#xff09;。 導航至 Editor → Code Style → 選擇語言&#xff08;如 Java 或 Kotlin &#xff09;。 二、修改換行規則…

Jenkinsfile 報錯

Started by user 六件套Obtained Jenkinsfile from git https://gitee.com/duoshuijiao/vitepress-jenkins-cicd-demoorg.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:WorkflowScript: 28: Expected a step line 28, column 66.fingerprint:…

工業一體機全封閉抗干擾賦能自動化產線高效作業

在自動化產線智能設備等工業場景中&#xff0c;工業一體機的應用面臨多重挑戰&#xff1a;高溫、粉塵、電磁干擾等惡劣環境易導致設備誤操作&#xff0c;傳統工控機平均無故障時間不足4000小時&#xff1b;封閉車間散熱效率低下&#xff0c;風扇散熱失效風險增加&#xff0c;產…