基于 Camunda BPM 的工作流引擎示例項目

項目介紹

這是一個基于 Camunda BPM 的工作流引擎示例項目,包含完整的后臺接口和前端頁面,實現了流程的設計、部署、執行等核心功能。

技術棧

后端

  • Spring Boot 2.7.9
  • Camunda BPM 7.18.0
  • MySQL 8.0
  • JDK 1.8

前端

  • Vue 3
  • Element Plus
  • Bpmn.js
  • Vite

功能特性

  • 流程定義管理
    • 流程設計器
    • 流程部署
    • 流程定義列表
    • 流程圖查看
  • 流程實例管理
    • 啟動流程
    • 實例列表
    • 實例流程圖
  • 任務管理
    • 任務列表
    • 任務處理

使用說明

環境準備

  1. 安裝 JDK 1.8 或以上版本
  2. 安裝 MySQL 8.0
  3. 安裝 Node.js 16.0 或以上版本

數據庫配置

  1. 創建數據庫
CREATE DATABASE camunda DEFAULT CHARACTER SET utf8mb4;
  1. 修改數據庫配置
    編輯 server/src/main/resources/application.yaml 文件:
spring:datasource:url: jdbc:mysql://localhost:3306/camundausername: your_usernamepassword: your_password

后端啟動

  1. 進入后端項目目錄
cd server
  1. 編譯打包
mvn clean package
  1. 運行項目
java -jar target/camunda-demo-1.0-SNAPSHOT.jar

前端啟動

  1. 進入前端項目目錄
cd web
  1. 安裝依賴
npm install
  1. 啟動開發服務器
npm run dev
  1. 訪問項目
    打開瀏覽器訪問: http://localhost:5173

使用流程

  1. 流程設計

    • 點擊"新建流程"進入流程設計器
    • 使用工具欄組件設計流程
    • 設計完成后點擊"部署"按鈕部署流程
  2. 流程管理

    • 在流程定義列表中查看所有已部署的流程
    • 可以查看流程圖、編輯流程、啟動新的流程實例
  3. 流程執行

    • 在流程實例列表中查看所有運行中的流程實例
    • 點擊查看實例圖可以查看當前流程的執行狀態
  4. 任務處理

    • 在任務列表中查看待處理的任務
    • 點擊"完成任務"按鈕處理任務

開發說明

  • 后端接口統一以 /api 開頭
  • 前端開發服務器已配置代理,自動轉發 /api 請求到后端服務
  • 流程設計器基于 bpmn-js,支持標準 BPMN 2.0 規范

注意事項

  • 首次啟動時,Camunda 會自動創建所需的數據庫表
  • 建議使用 Chrome 或 Firefox 瀏覽器訪問
  • 流程圖導出支持 BPMN 格式

源碼下載

Camunda Demo

演示截圖

1.流程定義列表
在這里插入圖片描述

2.流程實例列表
在這里插入圖片描述

3.任務列表
在這里插入圖片描述

4.流程定義頁面
在這里插入圖片描述

5.流程定義編輯頁面
在這里插入圖片描述

核心源碼

server/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>camunda-demo</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.9</version></parent><properties><java.version>1.8</java.version><camunda.spring-boot.version>7.18.0</camunda.spring-boot.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.camunda.bpm.springboot</groupId><artifactId>camunda-bpm-spring-boot-starter-rest</artifactId><version>${camunda.spring-boot.version}</version></dependency><dependency><groupId>org.camunda.bpm.springboot</groupId><artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId><version>${camunda.spring-boot.version}</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>

server/src/main/java/com/example/controller/ProcessController.java

package com.example.controller;import com.example.dto.ProcessDefinitionDTO;
import com.example.dto.ProcessInstanceDTO;
import com.example.dto.TaskDTO;
import lombok.RequiredArgsConstructor;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.repository.Deployment;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;@RestController
@RequestMapping("/api/process")
@RequiredArgsConstructor
public class ProcessController {private final ProcessEngine processEngine;@GetMapping("/definitions")public List<ProcessDefinitionDTO> getProcessDefinitions() {List<ProcessDefinitionDTO> list = processEngine.getRepositoryService().createProcessDefinitionQuery().latestVersion().list().stream().map(def -> new ProcessDefinitionDTO(def.getId(),def.getDeploymentId(),null,null,def.getVersion())).collect(Collectors.toList());for (ProcessDefinitionDTO dto : list) {Deployment deployment = processEngine.getRepositoryService().createDeploymentQuery().deploymentId(dto.getDeploymentId()).singleResult();dto.setName(deployment.getName());dto.setCreateTime(deployment.getDeploymentTime());}return list;}@GetMapping("/instances")public List<ProcessInstanceDTO> getProcessInstances() {return processEngine.getRuntimeService().createProcessInstanceQuery().list().stream().map(instance -> new ProcessInstanceDTO(instance.getId(),instance.getProcessDefinitionId(),instance.getBusinessKey())).collect(Collectors.toList());}@GetMapping("/tasks")public List<TaskDTO> getTasks() {return processEngine.getTaskService().createTaskQuery().list().stream().map(task -> new TaskDTO(task.getId(),task.getName(),task.getAssignee(),task.getProcessInstanceId())).collect(Collectors.toList());}@PostMapping("/start")public ResponseEntity<Void> startProcess(@RequestParam String processDefinitionId, @RequestBody(required = false) Map<String, Object> variables) {processEngine.getRuntimeService().startProcessInstanceById(processDefinitionId, variables);return ResponseEntity.ok().build();}@PostMapping("/tasks/{taskId}/complete")public void completeTask(@PathVariable String taskId, @RequestBody(required = false) Map<String, Object> variables) {processEngine.getTaskService().complete(taskId, variables);}@GetMapping("/definition/xml")public String getProcessDefinitionXml(@RequestParam String processDefinitionId) {InputStream inputStream = processEngine.getRepositoryService().getProcessModel(processDefinitionId);return convertStreamToString(inputStream);}@GetMapping("/instance/xml")public String getProcessInstanceXml(@RequestParam String processDefinitionId, @RequestParam String procInstId) {InputStream inputStream = processEngine.getRepositoryService().getProcessModel(processDefinitionId);return convertStreamToString(inputStream);}// 將 InputStream 轉換為 Stringprivate String convertStreamToString(InputStream is) {if (is == null) {return null;}BufferedReader reader = new BufferedReader(new InputStreamReader(is));StringBuilder sb = new StringBuilder();String line;try {while ((line = reader.readLine()) != null) {sb.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();} finally {try {is.close();} catch (IOException e) {e.printStackTrace();}}return sb.toString();}@PostMapping("/deploy")public ResponseEntity<Void> deployProcess(@RequestParam String processName, @RequestBody Map<String, Object> map) {String bpmnXml = (String) map.get("bpmnXml");processEngine.getRepositoryService().createDeployment().name(processName).addString("process.bpmn", bpmnXml).deploy();return ResponseEntity.ok().build();}
}

web/src/main.js

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'import 'bpmn-js/dist/assets/bpmn-js.css'
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')

web/src/views/ProcessDefinitionList.vue

<template><el-table :data="definitions" border><el-table-column prop="deploymentId" label="流程定義ID" align="center"></el-table-column><el-table-column prop="name" label="流程名稱" width="200" align="center"></el-table-column><el-table-column label="創建時間" width="200" align="center"><template #default="scope">{{ formatDate(scope.row.createTime) }}</template></el-table-column><el-table-column prop="version" label="版本" width="80" align="center"></el-table-column><el-table-column label="操作" width="360" align="center"><template #default="scope"><el-button type="success" @click="viewDefinition(scope.row.processDefinitionId)">查看流程圖</el-button><el-button type="warning" @click="editDefinition(scope.row.processDefinitionId)">編輯流程</el-button><el-button type="primary" @click="startProcess(scope.row.processDefinitionId)">啟動流程</el-button></template></el-table-column></el-table>
</template><script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import {useRouter} from "vue-router";const definitions = ref([])const fetchDefinitions = async () => {const response = await axios.get('/api/process/definitions')definitions.value = response.data
}const startProcess = async (processDefinitionId) => {try {await axios.post(`/api/process/start?processDefinitionId=${processDefinitionId}`)ElMessage.success('流程啟動成功')} catch (error) {ElMessage.error('流程啟動失敗')}
}const router = useRouter()const viewDefinition = (processDefinitionId) => {router.push({path: "/view/definition",query: {processDefinitionId},})
}const editDefinition = (processDefinitionId) => {router.push({path: "/definition",query: {processDefinitionId},})
}onMounted(() => {fetchDefinitions()
})function formatDate(isoString) {if (!isoString) return '';const date = new Date(isoString);const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份從0開始const day = String(date.getDate()).padStart(2, '0');const hours = String(date.getHours()).padStart(2, '0');const minutes = String(date.getMinutes()).padStart(2, '0');const seconds = String(date.getSeconds()).padStart(2, '0');return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
</script>

web/src/views/ProcessEditor.vue

<template><div style="margin-bottom: 20px"><el-button type="primary" @click="saveBpmn">部署</el-button><el-button type="info" @click="exportBpmn">導出 BPMN</el-button><el-button type="danger" @click="triggerImport">導入 BPMN</el-button><!-- 隱藏的文件輸入 --><input type="file" ref="fileInput" @change="importBpmn" accept=".bpmn,.xml" hidden /></div><div ref="canvas" class="canvas"></div>
</template><script setup>
import {onMounted, ref} from 'vue'
import BpmnModeler from 'bpmn-js/lib/Modeler'
import {ElMessage, ElMessageBox} from "element-plus";
import axios from "axios";
import {useRoute, useRouter} from "vue-router";const canvas = ref()
let bpmnModeler = null// 示例 BPMN 內容(你可以從后端獲取或創建空流程)
let defaultBpmnXml = `
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"xmlns:camunda="http://camunda.org/schema/1.0/bpmn"id="Definitions_0fr9mxs"targetNamespace="http://bpmn.io/schema/bpmn"><bpmn:process id="process" name="流程" isExecutable="true"></bpmn:process><bpmndi:BPMNDiagram id="BPMNDiagram-process"><bpmndi:BPMNPlane id="BPMNPlane-process" bpmnElement="process"></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</bpmn:definitions>
`.trim()const route = useRoute()onMounted(async () => {bpmnModeler = new BpmnModeler({container: canvas.value,})if (route.query.processDefinitionId) {const res = await axios.get(`/api/process/definition/xml?processDefinitionId=${route.query.processDefinitionId}`)if (res.data) {defaultBpmnXml = res.data;}}// 加載默認 BPMN 或從后端獲取現有流程定義await bpmnModeler.importXML(defaultBpmnXml)// 中英文映射表const titleMap = new Map([["Activate global connect tool", "連線"],["Create start event", "創建開始事件"],["Create end event", "創建結束事件"],["Create task", "創建任務"]]);// 白名單:只顯示這些功能項(基于 data-action)const allowedActions = ['create.start-event','create.end-event','global-connect-tool','create.task',];// 白名單:只顯示這些功能項(基于 data-action)const contextPadAllowedActions = ['replace','delete','connect',];// 更新所有 .entry 的 titlefunction updateTitles() {document.querySelectorAll('.djs-palette-entries .entry').forEach(entry => {const originalTitle = entry.getAttribute('title');if (originalTitle && titleMap.has(originalTitle)) {entry.setAttribute('title', titleMap.get(originalTitle));}});}// 控制顯示哪些條目(基于 data-action)function setDisplayByAction(allowedActions) {document.querySelectorAll('.djs-palette-entries .entry').forEach(entry => {const action = entry.getAttribute('data-action');entry.style.display = allowedActions.includes(action) ? 'block' : 'none';});}// 控制顯示哪些條目(基于 data-action)function setContextPadDisplayByAction(allowedActions) {document.querySelectorAll('.djs-context-pad-parent .djs-context-pad .group .entry').forEach(entry => {const action = entry.getAttribute('data-action');entry.style.display = allowedActions.includes(action) ? 'block' : 'none';});}// 執行函數updateTitles();setDisplayByAction(allowedActions);// 新增:監聽上下文菜單變化const observer = new MutationObserver(() => {setContextPadDisplayByAction(contextPadAllowedActions)})const contextPadParent = document.querySelector('.djs-context-pad-parent')if (contextPadParent) {observer.observe(contextPadParent, {childList: true,subtree: true})} else {// 如果還沒渲染出來,可以稍后重試setTimeout(() => {const contextPadParent = document.querySelector('.djs-context-pad-parent')if (contextPadParent) {observer.observe(contextPadParent, {childList: true,subtree: true})}}, 1000)}
})const router = useRouter()const saveBpmn = async () => {const {xml} = await bpmnModeler.saveXML({format: true})ElMessageBox.prompt('請輸入流程名稱', '流程部署', {confirmButtonText: '確認',cancelButtonText: '取消',}).then(({value}) => {try {axios.post('/api/process/deploy', {bpmnXml: xml}, {params: {processName: value}})ElMessage.success('流程部署成功')router.push({path: "/",})} catch (error) {ElMessage.error('流程部署失敗')console.error(error)}}).catch(() => {})
}const fileInput = ref();// ===== 導出 BPMN 文件 =====
const exportBpmn = async () => {const { xml } = await bpmnModeler.saveXML({ format: true })const blob = new Blob([xml], { type: "application/xml;charset=utf-8" })const url = URL.createObjectURL(blob)const a = document.createElement("a")a.href = urla.download = "process.bpmn"a.click()URL.revokeObjectURL(url)
}// ===== 觸發文件選擇 =====
const triggerImport = () => {fileInput.value.click()
}// ===== 導入 BPMN 文件 =====
const importBpmn = async (event) => {const file = event.target.files[0]if (!file) returnconst reader = new FileReader()reader.onload = async () => {try {await bpmnModeler.importXML(reader.result + "")ElMessage.success('BPMN 文件導入成功')} catch (err) {ElMessage.error('BPMN 文件導入失敗')console.error(err)}}reader.readAsText(file)
}
</script><style scoped>
.canvas {height: calc(100% - 60px);border: 1px solid #ccc;
}
</style>

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

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

相關文章

Day06_刷題niuke20250707

試卷01&#xff1a; 單選題 C 1. 在C中,一個程序無論由多少個源程序文件組成,其中有且僅有一個主函數main().說法是否正確&#xff1f; A 正確 B 錯誤 正確答案&#xff1a;A 官方解析&#xff1a; 在C程序設計中,一個完整的程序確實有且僅有一個main函數作為程序的入口點,這…

洛谷 P5788 【模板】單調棧

題目背景模板題&#xff0c;無背景。2019.12.12 更新數據&#xff0c;放寬時限&#xff0c;現在不再卡常了。題目描述給出項數為 n 的整數數列 a1…n?。定義函數 f(i) 代表數列中第 i 個元素之后第一個大于 ai? 的元素的下標&#xff0c;即 f(i)mini<j≤n,aj?>ai??{…

linux系統運行時_安全的_備份_還原_方法rsync

1.問題與需求 問題: 新部署的機器設備(主控RK3588), 沒有經過燒錄定制鏡像, 研發部署, 直接組裝發送到客戶現場需要通過frpc遠程部署: 安裝ros2 python包 docker鏡像 環境配置 自啟動配置 SN設備信息寫自動部署腳本, 實現一鍵部署升級無奈物聯網卡做了白名單限制, apt 和…

18套精美族譜Excel模板,助力家族文化傳承!

【資源分享】18套精美族譜Excel模板&#xff0c;助力家族文化傳承&#xff01; &#x1f3af; 本文分享一套完整的家族譜系資源&#xff0c;包含18個精心設計的Excel模板&#xff0c;從基礎模板到專業圖表&#xff0c;滿足各類家族的族譜制作需求。 一、為什么要制作族譜&…

MySQL Galera Cluster企業級部署

一、MySQL Galera Cluster簡介 主要特點 同步復制&#xff1a; 所有的寫操作&#xff08;包括插入、更新、刪除&#xff09;在集群中的所有節點上都是同步的。這意味著每個節點上的數據是完全一致的。 多主節點&#xff1a; 集群中的每個節點都是主節點。所有節點都可以處理讀…

HTTP 重定向

什么是 HTTP 重定向&#xff1f; HTTP 重定向&#xff08;HTTP Redirect&#xff09; 是服務器向客戶端&#xff08;通常是瀏覽器&#xff09;發出的指令&#xff0c;告訴客戶端某個請求的資源已被移到新的位置。重定向通常通過發送一個特殊的 HTTP 狀態碼&#xff08;例如 3x…

本地加載非在線jar包設置

項目中存在私有jar包&#xff0c;提示在線獲取不到&#xff0c;需要先獲取到完整的jar包在打進maven中再在項目中進行maven依賴引入 mvn install:install-file -DfileD:\tools\maven\apache-maven-3.5.2\local_repository2\org\ahjk\SixCloudCommon\1.0\SixCloudCommon-1.0-SN…

Codeforces Round 979 (Div. 2)

A c[1]-b[1]0&#xff0c;之后每個c[1]-b[1]最大都是maxa-mina&#xff0c;最大和最小放前兩個 B ans2^(a1)-2^s-1&#xff0c;1一個最小 C 我們可以把式子化為(....)||(....)||(....)括號里沒有||&#xff0c;如果括號全是1那么A贏&#xff0c;A盡量選擇把1選在一起 D …

UI前端大數據處理性能瓶頸突破:分布式計算框架的應用

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩!一、引言&#xff1a;前端大數據處理的性能困境與破局之路在數據爆炸增長的時代&#xff0c;UI…

病蟲害數據集

數據是泰迪杯主辦方提供的已經標記好的數據&#xff0c;4k畫質的圖片&#xff0c;總大小8個G 鏈接&#xff1a;https://pan.baidu.com/s/1fvmNHGrLvflEovjfCjDLOw?pwd6666 提取碼&#xff1a;6666 蟲害包括&#xff1a; 八點灰燈蛾 褐飛虱屬 白背飛虱 二化螟 蟋蟀 黃足…

JAVA基礎:關于JDK環境變量設置的若干相關細節及注意事項

一、JDK下載安裝 網址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 以 win11 為例&#xff0c;根據網址下載安裝包后&#xff0c;點擊安裝&#xff0c;注意設置安裝路徑 二、基礎常識 1.Java三大使用平臺 Java SE(Java Standard Edition): 標準版&…

C++高頻知識點(四)

文章目錄 16. 虛基類要解決什么問題&#xff1f;17. C中如何進行類型轉換操作&#xff1f;列舉并解釋四種類型轉換方式。18. 什么是函數重載&#xff1f;如何進行函數重載&#xff1f;19. 解釋C中的友元函數和友元類&#xff0c;并解釋其使用場景。友元函數友元類 20. 請解釋C中…

【Servlet資源轉發介紹】

文章目錄 前言一、Servlet 資源轉發是什么&#xff1f;1. 為什么要資源轉發&#xff1f; 二、資源轉發 vs 重定向三、如何使用 RequestDispatcher 進行資源轉發1. 引入依賴2. 獲取 RequestDispatcher3. forward 示例4. include 示例JSP 中 include 指令或動作Servlet 中 includ…

牛客周賽 Round 99題解

Round 99 思路&#xff1a;我們之間去用字符串去統計即可&#xff0c;輸入一個字符串&#xff0c;看相鄰有沒有99即可 #include<bits/stdc.h> using namespace std; #define int long long string s; signed main() {cin>>s;int ns.size();for(int i1;i<n;i){i…

AR 如何改變我們構建網站的方式

想坐在沙發上試鞋子&#xff1f;歡迎來到 Web AR 的世界。還記得你在網頁上逛商城時&#xff0c;點擊一副墨鏡&#xff0c;然后鏡頭打開&#xff0c;它就自動出現在你臉上的那一瞬間嗎&#xff1f;不需要下載 App&#xff0c;不需要跳轉&#xff0c;只需一個瀏覽器。這不是科幻…

華為OD機試 2025B卷 - 貨幣單位轉換(C++PythonJAVAJSC語言)

2025B卷目錄點擊查看: 華為OD機試2025B卷真題題庫目錄|機考題庫 + 算法考點詳解 2025B卷 100分題型 題目描述 記賬本上記錄了若干條多國貨幣金額,需要轉換成人民幣分(fen),匯總后輸出。 每行記錄一條金額,金額帶有貨幣單位,格式為數字+單位,可能是單獨元,或者單獨分…

php協程

開發需求:在一套老項目中&#xff08;fastadmin&#xff09;實現一個定時任務&#xff0c;每分鐘訪問幾十個接口&#xff0c;拿到數據。 使用的swoole&#xff0c;在thinkphp5中實現協程。啟動命令php swoole.php <?php //chdir(__DIR__); define(APP_PATH, __DIR__ . /app…

【教程】強制關閉Windows防火墻的自啟動

轉載請注明出處&#xff1a;小鋒學長生活大爆炸[xfxuezhagn.cn] 如果本文幫助到了你&#xff0c;歡迎[點贊、收藏、關注]哦~ 背景說明 字節云的Windows server真是有點問題&#xff0c;忽然就開始自動開啟防火墻&#xff0c;手動關閉了過幾個小時又重新開啟了&#xff0c;導致…

【Qt】QSignalMapper

QSignalMapper 是 Qt 提供的一個用于信號映射的類&#xff0c;它允許將多個信號源&#xff08;例如按鈕點擊&#xff09;映射到一個單一的槽函數&#xff0c;并傳遞自定義參數。這在需要根據不同的觸發對象執行相似邏輯時非常有用。 用法說明 創建 QSignalMapper 實例&#xf…

Android Binder與AIDL與Service使用案例及分析

水一篇以前寫的文章?? Binder是Android內置的一種比較高效的跨進程機制,它很復雜,也很好用,可以讓我們像調用普通方法那樣完成跨進程式方法調用和數據傳遞。我們現在只需要知道它比較復雜以及怎么使用即可。 ALDL全名Android interface Definition Language, 是Android…