在仓储管理、门店盘点、图书整理等场景中,条码识别加本地数据存储是最常见的组合需求。Android官方提供的Barcode Detection API(ML Kit条码识别的前身)可以快速从相机画面中解析出条码内容,而SQLite作为轻量级嵌入式数据库,恰好承担识别结果的持久化工作。本文将两者串联起来,从数据库设计到扫码入库,完整实现一个离线可用的条码管理系统。

一、项目整体架构与数据库设计
整个项目的数据流并不复杂:相机持续采集画面,Barcode Detection对每一帧进行分析,识别成功后回调结果,业务层先查询SQLite中是否已存在该条码,不存在则插入新记录,存在则更新扫描次数与最后扫描时间。这种"识别即入库"的模式在盘点类应用中非常实用。
数据库设计上,建议创建一张t_barcode表,包含条码值、条码格式、扫描次数、首次扫描时间、最后扫描时间等字段。条码值设置为主键或唯一索引,可以有效防止重复数据,也为后续的查重更新提供性能保障。下面是建库的完整代码:
public class BarcodeDbHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "barcode.db";
private static final int DB_VERSION = 1;
public BarcodeDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE t_barcode (" +
"barcode TEXT PRIMARY KEY, " +
"format TEXT NOT NULL, " +
"scan_count INTEGER DEFAULT 1, " +
"first_time INTEGER NOT NULL, " +
"last_time INTEGER NOT NULL)";
db.execSQL(sql);
// 为格式字段建索引,方便按格式统计
db.execSQL("CREATE INDEX idx_format ON t_barcode(format)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS t_barcode");
onCreate(db);
}
}
这里有个容易被忽略的细节:first_time和last_time存储的是毫秒时间戳(long类型),比存格式化字符串节省空间,排序比较也更快。另外主键直接用barcode TEXT,如果业务中出现同一码值不同含义的情况,再考虑用自增id加唯一索引的组合方案。
二、接入Barcode Detection实现扫码识别
识别部分推荐使用Google Play Services提供的Barcode Detection API,它支持EAN-13、QR码、Code 128等主流格式,且不需要额外申请模型文件。核心思路是构建一个BarcodeDetector对象,配合CameraSource把识别流程挂到相机预览上,识别结果通过Detector.Processor回调返回。
BarcodeDetector detector = new BarcodeDetector.Builder(context)
.setBarcodeFormats(Barcode.ALL_FORMATS) // 或只指定需要的格式提升速度
.build();
detector.setProcessor(new Detector.Processor<Barcode>() {
@Override
public void release() { }
@Override
public void receiveDetections(Detector.Detections<Barcode> detections) {
SparseArray<Barcode> barcodes = detections.getDetectedItems();
if (barcodes.size() > 0) {
Barcode barcode = barcodes.valueAt(0);
String value = barcode.rawValue;
String format = nameFormat(barcode.format);
// 交给数据库层处理,注意这里是后台线程回调
dbHelper.saveOrIncrease(value, format);
}
}
});
需要特别注意的是,receiveDetections回调非常频繁,同一条码在镜头前停留一秒可能触发十几次回调。如果不做去重处理,扫描次数会被刷得很离谱。常见的解决办法有两种:一是记录上次识别的码值和时间戳,相同码值在两秒内只处理一次;二是依赖数据库层的唯一约束,用INSERT OR REPLACE配合查询判断。前者节省数据库开销,是更推荐的方案。
三、扫码结果的入库与查重更新策略
数据库操作层建议封装一个saveOrIncrease方法,用INSERT OR IGNORE先尝试插入,若条码已存在则通过UPDATE将扫描次数加一并刷新最后扫描时间。这种写法比"先SELECT再判断插入还是更新"少一次交互,逻辑也更简洁。
public void saveOrIncrease(String barcode, String format) {
SQLiteDatabase db = getWritableDatabase();
long now = System.currentTimeMillis();
db.beginTransaction();
try {
// 已存在则忽略,不存在则插入
db.execSQL("INSERT OR IGNORE INTO t_barcode " +
"(barcode, format, scan_count, first_time, last_time) " +
"VALUES (?, ?, 1, ?, ?)",
new Object[]{barcode, format, now, now});
// 无论插入与否,都更新次数和时间
db.execSQL("UPDATE t_barcode SET scan_count = scan_count + 1, " +
"last_time = ? WHERE barcode = ?",
new Object[]{now, barcode});
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
细心的话会发现上面插入时次数写的是1,而UPDATE又无条件加1,这样新记录的次数会变成2。修正方式是把UPDATE放在插入失败分支,或者把INSERT的初始值改为0。这个例子也说明了为什么数据库操作要配合单元测试验证,看似顺手的写法往往埋着逻辑漏洞。
四、性能优化与常见坑点
批量导入场景下(比如把历史扫码记录一次性入库),务必使用事务包裹。SQLite默认每条SQL都是一个隐式事务,逐条插入一万条数据可能需要十几秒,而放在一个显式事务里通常一秒内完成,差距非常明显。另外所有数据库操作都应放在工作线程,扫码回调本身就运行在后台,直接写库一般没问题,但如果是UI触发的历史查询,就要用AsyncTask或线程池避免卡顿。
还有几个实际项目中容易踩的坑:第一,CameraSource需要在生命周期结束时调用release(),否则相机被占用导致下次黑屏;第二,Barcode Detection依赖Google Play Services,国内无服务的设备上初始化会失败,商用项目建议评估ML Kit的离线打包方案;第三,数据库升级时不要简单DROP重建,线上用户的数据会丢失,正确做法是用ALTER TABLE做增量迁移。
完成以上模块后,一个基础的扫码管理系统就跑通了。后续可以在此基础上扩展导出CSV、按格式统计、与服务端同步等功能,SQLite的稳定和零运维特性足以支撑这类中小型应用长期运行。
SQLite条码识别Barcode Detection修改时间:2026-09-01 09:30:59