尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AndroidStudio人脸识别考勤系统APP开发实战解析

发布时间:2026/9/25 7:41:18

资讯中心
01
ARTICLE

AndroidStudio人脸识别考勤系统APP开发实战解析

AndroidStudio人脸识别考勤系统APP开发实战解析
简介这份基于Android Studio开发的人脸识别考勤系统完整项目面向高校课堂考勤与会议签到场景覆盖APP客户端与后台管理双端以人脸识别签到和高德地图定位为核心解决传统点名效率低、易代签等问题适合师生、开发者及毕设选题者参考。资源包共64个文件大小约75.74MB包含51张jpg、9张png运行截图与界面素材、2个md说明文档、1个sql数据库脚本和1个zip工程源码包可直观预览页面SQL脚本可直接导入初始化数据。技术栈上Web端使用Spring Boot、Mybatis Plus、Shiro、layuiApp端采用Android原生布局搭配OkHttp与fastJson通信MySQL 5.7存储数据功能包括老师发布签到、学生人脸识别签到、签到地图、考勤统计及后台学生、班级、教师、角色权限等管理模块层次分明。资源内含完整可运行代码、数据库脚本、README说明和大量运行截图可帮助理解人脸识别考勤与高德地图定位的整合思路、Android端与Java Web服务端的数据交互也可直接作为课程设计、毕业设计或二次开发的蓝本。目前已有121人学习适合需要快速搭建考勤系统原型、研究人脸识别应用落地的开发者。1. 人脸识别考勤系统APP先搞清楚它到底交付了什么拿到这套基于AndroidStudio的人脸识别考勤系统APP第一件事不是急着点运行而是想明白一个问题它和你做过的那种普通登录Demo差在哪。普通Demo只验证“你是不是库里的人”考勤系统还要回答“你在哪打卡”“这次算不算数”“数据怎么汇总到后台”。这套资源把安卓端、管理后台、数据库和高德地图定位四块都打包了等于给出一条完整的数据闭环人脸注册→打卡识别→坐标获取→落库→后台出报表。适合两类人一类是正在做毕业设计、需要一套能演示完整业务的学生另一类是刚转向安卓开发的工程师想看看一个带后台的项目边界的真实做法。但如果你期望导入即运行、误识别率直接为零那我建议先降低预期项目里真正值钱的不是“人脸比对”那一下而是“比对之后怎么处理业务状态”。2. 系统架构与模块边界APP、后台、数据库怎么分工先讲整体。这类考勤系统通常不是单工程而是“一个Android客户端 一个管理后台服务 一个数据库实例”的外部组合。Android端负责最重的活摄像头取帧、人脸检测与特征提取、本地比对、拿高德定位坐标再把一条完整考勤记录通过HTTP接口提交给后台。管理后台负责员工管理、打卡记录查询、考勤状态判定和报表导出。数据库在中间承上启下既存员工的人脸特征底库也存每天的考勤明细。这三层如果不先画边界写代码时最容易出现“逻辑写错层”的问题比如定位失败就回滚人脸比对结果或者后台把迟到判定塞进APP端写死。2.1 一次考勤打卡请求的完整数据链路我拿到项目源码后第一件事是找“数据从哪里开始、到哪里终止”。这套系统的标准链路可以用下面的顺序概括摄像头实时预览 - 人脸检测 - 提取人脸特征 - 与本地底库比对 - 命中员工 - 高德定位获取经纬度 - 组装考勤记录 - HTTP POST 到后台 - 后台写 MySQL - 返回签到结果注意这里有两个关键点。第一人脸比对发生在Android本地不是把图片传后台再比对否则打卡高峰期几十台设备同时请求后台压力会很大而且断网时打卡直接瘫痪。第二定位信息是与人脸识别结果同时打包的不会先弹“打卡成功”再异步补坐标否则后台会收到大量没有坐标的历史记录外勤判断就失去依据。你下载源码后可以按这条链路去读代码先看APP端哪个类负责识别再看哪个类负责定位最后抓接口地址和数据库表对应关系。链路理清了后面改什么都好办。2.2 人脸识别库、数据库、定位SDK的选型对比这类资源最常见的技术选型组合我实际拆过的项目基本集中在下面这张表里。看源码时先确认它落在哪一列不要想当然。环节常见方案适用场景注意事项人脸识别离线引擎OpenCVLBPH、虹软、TFLite FaceNet打卡闸机、手机端低延迟需要关注特征维度是否一致升级模型后旧特征是否作废人脸识别云端API百度、阿里、Face对网络稳定、后台计算资源有保障的团队每次调用按次数计费离线不可用不适合做“断网也能签到”Android数据库SQLite、Room本地缓存与人脸特征临时存储只存业务缓存不要把后台核心数据也放本地管理后台数据库MySQL、PostgreSQL考勤记录、员工底库建议给考勤表加索引数据量上来后查报表差距明显定位SDK高德地图定位国内考勤点必须用GCJ-02坐标系和原生GPS坐标混用会偏移后面专门讲我倾向于离线人脸引擎方案比较多原因是考勤场景发生在公司门口或教室门口网络抖动很常见。如果条件允许再在后台加一个人脸特征相似度校准接口用于APP端阈值失效时兜底。数据库侧只要项目能跑得动我一般优先选MySQL因为这套系统已经有管理后台了SQLite只承担APP端的本地缓存职责。2.3 拿到源码包先看哪几个文件目录结构与运行前提不要一头扎进Activity里逐行读。先扫目录能省半天时间。典型目录结构长这样FaceAttend/ ├── app/ # Android 客户端 │ ├── src/main/java/…/face # 人脸检测与比对封装 │ ├── src/main/java/…/location # 高德定位封装 │ └── src/main/AndroidManifest.xml ├── server/ # 管理后台服务 │ ├── src/main/java/…/controller/ # 考勤接口 │ └── src/main/resources/application.yml ├── sql/ │ └── init.sql # 建表与初始数据 └── README.md # 运行说明拿到资源后先做三个动作。第一打开app/build.gradle确认Gradle插件版本和你的Android Studio匹配热词里经常有人问“build:gradle:7.0.4下载哪个版本”其实关键是看项目里声明的是哪个AGP版本再让Android Studio按提示下载对应版本不要手动乱网盘找。第二打开init.sql看表关系确认员工表和考勤表有没有外键关联。第三打开server下的配置文件改数据库连接地址。整个项目能跑通的基础前提是Android Studio能编译、管理后台能启动、MySQL能连上这三件事缺一个都会让你误以为人脸识别代码有Bug。3. AndroidStudio端人脸识别考勤APP摄像头、模型与打卡链路中间最容易被卡住的就是Android端。人脸识别考勤APP和普通相机App最大的区别在于你要在持续预览的帧里实时找脸同时不能在主线程做特征比对还要管理好相机资源和引擎资源的生命周期。下面按实际开发顺序讲。3.1 摄像头预览与人脸检测CameraX与引擎初始化现代Android项目里用CameraX比直接调Camera2省心尤其对考勤这种“只需要持续预览取帧”的场景。核心取帧分析代码如下// Kotlin在打卡页初始化摄像头预览与人脸分析 val imageAnalysis ImageAnalysis.Builder() .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 丢帧不丢最新 .setTargetResolution(Size(480, 640)) // 分辨率不必太高识别人脸够用 .build() imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor()) { imageProxy - val bitmap imageProxy.toBitmap() // 引擎检测所有人脸返回人脸框列表 val faceList faceEngine.detectFaces(bitmap) if (faceList.isNotEmpty()) { // 取面积最大的人脸框避免误抓到背景里的海报脸 val maxFace faceList.maxBy { it.width * it.height } // 在人脸区域内提取特征向量后续统一送入比对模块 val feature faceEngine.extractFeature(bitmap, maxFace) compareQueue.offer(feature) // 特征比对是耗时操作塞到队列由子线程处理 } // 这一行一定要执行否则预览会卡死 imageProxy.close() } preview.surfaceProvider.also { previewView.surfaceProvider }这段代码有两个关键参数。STRATEGY_KEEP_ONLY_LATEST表示当分析速度跟不上摄像头帧率时系统只保留最新一帧避免积压导致打卡界面越来越慢对多人同时进出尤其有用。480x640是我习惯的初检分辨率特征提取足够比直接用1080P省电也省内存。注意倒数第二行必须调用imageProxy.close()漏掉它会导致预览缓冲池耗尽表现就是“打开摄像头黑屏几秒后崩掉”这是新手翻车率最高的点之一。3.2 人脸特征采集与注册为什么至少取三张底库数据质量决定识别率上限。很多项目里“注册员工”就是让用户端坐拍一张照片然后提特征入库实际用起来同一个员工在不同光线、不同角度下忽高忽低。正确做法是采集时让员工正脸、左侧旋转10度、右侧旋转10度各拍一张然后对特征向量取均值入库。示例代码如下fun enrollEmployee(employeeId: String, images: ListBitmap): Boolean { // 特征向量维度常见为128或512具体看引擎实现 val sumFeature FloatArray(featureDim) for (img in images) { val face faceEngine.detectFaces(img).firstOrNull() ?: continue val feature faceEngine.extractFeature(img, face) if (feature null) continue // 多张图特征做归一化累加降低单张过曝或过暗的影响 for (i in sumFeature.indices) { sumFeature[i] feature[i] / images.size } } // 入库前的特征压缩转成Base64或ByteArray再写入后台 val base64Feature Base64.encodeToString(sumFeature, Base64.NO_WRAP) return api.enrollEmployee(employeeId, base64Feature) }这里隐含一个容易被忽视的问题特征向量不能直接当字符串存不同引擎的浮点精度不一致后台拿到后要按相同顺序解析。把take 3改为take 5并不能无限提升效果超过5张后均值收益递减反而让员工注册耗时变长。实际测试中3张已经能把同一个人在不同光线下的特征波动拉平不少。3.3 打卡判断逻辑相似度阈值、定位与防重放人脸比对完成后不能只回答“相似度多少”还要回答“这次打卡是否有效”。有效性由三个条件同时约束人脸匹配通过、定位坐标在考勤点半径内、同一员工不在短时间内重复打卡。核心逻辑用伪代码可以写得很清楚fun handleAttendance(feature: FloatArray) { val matched faceEngine.compareFeature(feature, localDb.allFeatures) if (matched.similarity 0.82) { // 阈值越高误识越低但也越严格 showToast(未识别到有效人脸) return } // 防重复打卡同一员工5分钟内的成功记录直接忽略 val lastRecord repository.findLastClock(matched.employeeId) if (lastRecord ! null lastRecord.time.isWithinMinutes(5)) { showToast(已打卡请勿重复操作) return } // 最后一步才拿定位确认员工确实在这个考勤点附近 val location locationProvider.getOnceLocation() val distance calculateDistance(location, attendPointCoordinates) if (distance ATTEND_RADIUS_METER) { // 考勤半径一般设为50~100米 showToast(不在考勤范围内) return } repository.submit(AttendanceRecord(matched.employeeId, location, System.currentTimeMillis())) }这段逻辑的顺序我建议不要调换先人脸再防重放最后定位。如果把定位放最前面员工在门口等GPS定位的几秒里会有一种“被卡住”的体验。相似度阈值方面打卡场景我一般不低于0.8低于0.8在底库超500人的时候误识率会明显抬头0.85更稳但员工戴帽子、换眼镜时体验会差。实际部署时建议留一个后台可调的阈值开关不要写死在代码里否则每次调整都要重新发包。4. 管理后台与数据库设计考勤数据不写烂的关键APP端把打卡请求提交到后台之后重头戏才刚开始。我见过太多APP写得还行、后台只有一张临时表接数据反例查报表时一塌糊涂。考勤系统能不能说服老师或领导靠的就是数据库设计和后台状态判断。4.1 员工表、考勤表、设备表先设计这三张表下载源码后优先看init.sql如果里面没有下面这三张核心表那后台八成是半成品。基础表结构应包含-- 员工表人脸特征向量入库 CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, department VARCHAR(50), face_feature BLOB NOT NULL, -- 特征向量不要直接存原图 feature_version INT DEFAULT 1, -- 引擎升级后版本变化旧特征需要迁移 status TINYINT DEFAULT 1 -- 1在职 0离职 ); -- 考勤记录表一次签到一行 CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, employee_id INT NOT NULL, device_type TINYINT DEFAULT 1, -- 1手机 2闸机 3后台补录 clock_time DATETIME NOT NULL, -- 以服务端收到的时间为准 device_time DATETIME, -- 保留设备本地时间供排查 longitude DECIMAL(10,6), latitude DECIMAL(10,6), location_text VARCHAR(255), status TINYINT DEFAULT 1, -- 1正常 2迟到 3早退 4外勤 FOREIGN KEY (employee_id) REFERENCES employee(id) ); CREATE INDEX idx_att_emp_time ON attendance(employee_id, clock_time);每个字段都有存在的理由。face_feature存BLOB是为了让员工可以删掉重录而不必改业务表结构feature_version是很多项目忘掉的一个关键字段人脸识别引擎升级后特征向量维度或算法都可能变化老员工的旧特征必须重新注册否则会出现“换版本后同一批老员工全部识别失败”。device_time特意保留下来是为了后面排查“时间对不上”时用它对比服务端时间。4.2 考勤状态机与后台接口迟到早退外勤怎么判考勤状态不能在APP端定死因为排班时间、迟到容忍分钟数会变这些规则应该放在后台配置里。后台判定逻辑常见的做法是public AttendanceStatus resolveStatus(Attendance attendance, LocalTime workStart, LocalTime workEnd) { LocalTime t attendance.getClockTime().toLocalTime(); // 先判断是否外勤定位点和公司考勤点距离超过阈值 double distance GeoUtils.distance( attendance.getLatitude(), attendance.getLongitude(), companyPoint.getLatitude(), companyPoint.getLongitude()); if (distance 500) { return AttendanceStatus.FIELD_TRIP; // 外勤 } // 再判断迟到打卡时间超过上班时间容忍值 if (t.isAfter(workStart.plusMinutes(toleranceMinutes))) { return AttendanceStatus.LATE; } // 早退判断打卡时间早于下班时间-容忍值要排除外勤场景 if (t.isBefore(workEnd.minusMinutes(toleranceMinutes))) { return AttendanceStatus.LEAVE_EARLY; } return AttendanceStatus.NORMAL; }这里的关键点在于“外勤优先于迟到早退”。如果先判迟到那外勤人员上午10点定位在客户公司就会被错误标成迟到。我的经验是状态判定顺序固定为外勤→迟到→早退→正常并且把考勤规则参数上班时间、下班时间、容忍分钟数、考勤半径放到后台配置表不要编译进Java代码。4.3 设备时间不可信统一以服务端时间记账多台设备同时打卡时最隐蔽的坑是时间不一致。手机本地时间可以被用户改成任意值如果后台信任device_time很容易出现“凌晨打卡”“明天打卡”这种脏数据。解决方式其实很简单后台收到HTTP请求的那一刻记录clock_time同时保留device_time仅供排查。-- 排查时间异常时的SQL样例 SELECT * FROM attendance WHERE clock_time device_time - INTERVAL 5 MINUTE OR clock_time device_time INTERVAL 5 MINUTE;如果这条SQL查出大量记录说明你的APP端在校验身份成功前不应该把设备时间放进业务字段。离线打卡场景例外断网期间先在APP本地SQLite缓存打卡记录联网后提交时要带上“本地打卡时间”和“提交时间”两个字段后台以提交时间为准但补签到状态需要人工审核。5. 高德地图定位集成与常见问题排查坐标漂移、后台被杀、离线地图考勤系统里定位不是“锦上添花”而是判断外勤和考勤半径的核心依据。高德地图定位在这套资源里的作用就是给每次打卡补充经纬度。这块接入本身不难难的是你永远不会在第一次跑通时遇到所有坑。5.1 高德定位SDK接入Key、权限、初始化与打卡取点接入高德定位前先确认三件事包名一致、签名SHA256一致、Key的类型选的是“Android平台定位SDK”。然后在AndroidManifest里声明权限uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / !-- Android 10及以上还需要后台定位权限 -- uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION /权限声明只是第一步高德SDK要求在使用定位前完成动态权限申请。初始化通常在Application里完成class AttApplication : Application() { lateinit var locationClient: LocationClient private set override fun onCreate() { super.onCreate() val option LocationClientOption().apply { // 高精度模式GPS WIFI 基站一起参与 locationMode LocationClientOption.LocationMode.High_Accuracy // 打卡场景只取一次定位避免持续回调耗电 isOnceLocation true isNeedAddress true } locationClient LocationClient(this).apply { setLocOption(option) } } }这里isOnceLocation设为true是最适合考勤打卡的配置。连续定位模式会让APP在后台持续获取坐标考勤场景对实时轨迹没有需求开着反而是耗电大户。如果项目在打卡页同时显示当前位置文本可以再把isNeedAddress打开代价是定位回调会慢几百毫秒。5.2 高频踩坑记录现象、原因、解决以下五条是我在类似考勤项目里实际踩过或排查过的记录按频率排序。第一条同一考勤点上午定位正常下午定位偏移到隔壁街道。现象员工明明站在公司门口后台记录坐标却显示在50米外被判定不在考勤半径内。 原因高德定位在高精度模式下每次定位结果都有波动单次回调直接用于考勤判断过于激进。 解决打卡时连续取两到三次定位结果选择误差半径最小的那次或者把考勤半径从30米放宽到80米同时结合WIFI名称辅助判断。我常用后者因为完全依赖高德SDK单点定位在城市峡谷地带误差很难压住。第二条APP切后台几分钟后再打卡定位回调一直为空。现象员工手机锁屏再解锁打开考勤APP定位转圈几秒后无结果。 原因华为、小米、vivo等厂商为了省电会强制杀掉后台进程高德LocationClient在进程被清理后不能再复用。 解决考勤页在前台时再创建LocationClient不要全局持有通知栏加前台服务提升进程优先级。另外提醒用户在厂商系统里把考勤APP加入“后台高耗电”白名单否则任何定位SDK都救不了。第三条控制台申请Key时填的是release签名调试期一直报INVALID_USER_KEY。现象Android Studio直接点运行时定位不返回日志里出现鉴权失败或INVALID_USER_KEY。 原因debug调试期用的是~/.android/debug.keystore的SHA256不是release签名的SHA256。 解决在Android Studio右侧Gradle面板里找app-Tasks-android-signingReport复制输出的SHA256把debug和release两个指纹都添到高德控制台。如果项目用多渠道打包每个渠道的签名都要匹配否则某渠道包会静默失败。第四条Android 12以上设备不弹定位授权或者后台永远授权拒绝。现象首次安装点“仅在使用时允许”之后再进后台定位返回空卸载重装后权限弹窗不再出现。 原因Android 10及以上把后台定位权限单独拆开ACCESS_BACKGROUND_LOCATION必须单独动态申请部分国产ROM还会在二次授权时默认拒绝。 解决动态权限流程改为先申请ACCESS_FINE_LOCATION用户同意后再申请ACCESS_BACKGROUND_LOCATION不要放在同一个请求列表里一次弹完。同时在引导页弱提示“考勤班组需要后台定位用于补传记录”这一步能显著减少用户主动拒绝的概率。第五条人脸识别偶尔把长得像的两个人搞混。现象底库50人左右阈值0.75连续测试100次出现一两次误识别。 原因阈值设定过低且注册照片只有一张特征向量受光线、角度影响大。 解决先按前面说的多张图注册把底库质量提上来把阈值的默认值提到0.85阈值有过高然后后台记录每次识别的相似度分数连续两周统计分布找到“正确识别的最低分”和“误识别的最高分”取中间值重新设置。阈值调优不是拍脑袋要拿数据说话。6. 进阶用法与验证方法把考勤系统真正用起来如果项目已经能跑通下一步别急着写论文或交付先做一轮“验收基准测试”。这套系统能不能让人信服靠几个硬指标同人识别通过率、误识率、单次打卡耗时、定位误差。我习惯按下面这张表测指标测试方法参考值同人识别通过率同一员工在不同光线、角度下测试50次≥ 97%误识率随机抽30名员工互相比对1000次≤ 0.1%单次打卡耗时从面部出现在画面到页面回显成功≤ 2秒定位误差站在考勤点附近取20次定位求平均误差≤ 30米6.1 验证“识别-定位-入库”全链路单纯在APP上点一遍只能证明“能用”不能证明“数据没丢”。我会用Postman脚本或直接curl模拟管理后台接口来验证服务端逻辑curl -X POST http://192.168.1.10:8080/attendance/check \ -H Content-Type: application/json \ -d { employeeId: 1001, latitude: 31.2304, longitude: 121.4737, deviceTime: 2025-06-10 09:01:00 }这个请求能帮你快速确认三件事接口是否返回正确状态码、后台是否把记录写进MySQL、clock_time是否与deviceTime存在合理偏差。之后再从数据库侧查一遍attendance表看坐标小数点位数、状态字段是否符合预期。这套拆下来比你盯着Logcat一排排看有效得多。6.2 推荐你保留的一个习惯从那以后我每次做考勤或门禁类项目开工前都强制自己先写一份“数据链路检查单”把四个问题写清楚谁看守时间、谁核实定位、谁判断考勤状态、谁兜底异常记录。人脸识别考勤系统APP能不能上线90%的坑都出在这四个“谁”没定义清楚上时间没统一、定位没兜底、状态判断错层、离线记录丢失。如果你也准备用这套源码做二次开发建议先在README里补一张同样的检查单再开始改代码。希望这次拆解能帮到你少走几个我走过的弯路。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。