打印机封装

This commit is contained in:
2026-06-24 20:53:35 +08:00
parent de270fe1ac
commit 5a35ef059c
7 changed files with 564 additions and 250 deletions
+34 -11
View File
@@ -1,25 +1,47 @@
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Properties
import java.io.File
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
}
// 自动版本号管理
val versionPropsFile = file("version.properties")
fun getVersionCode(): Int {
val props = Properties()
if (versionPropsFile.exists()) {
props.load(versionPropsFile.inputStream())
}
val code = props.getProperty("VERSION_CODE", "1").toInt()
props.setProperty("VERSION_CODE", (code + 1).toString())
props.store(versionPropsFile.outputStream(), null)
return code
}
fun getVersionName(): String {
val props = Properties()
if (versionPropsFile.exists()) {
props.load(versionPropsFile.inputStream())
}
return props.getProperty("VERSION_NAME", "1.2")
}
val autoVersionCode = getVersionCode()
val autoVersionName = getVersionName()
android {
namespace = "com.example.ops_android"
compileSdk {
version = release(36) {
minorApiLevel = 1
}
}
compileSdk = 36
defaultConfig {
applicationId = "com.example.ops_android"
minSdk = 24
targetSdk = 36
versionCode = 2
versionName = "1.1"
versionCode = autoVersionCode
versionName = autoVersionName
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +72,6 @@ android {
}
buildFeatures {
compose = true
aidl = true
buildConfig = true
}
}
@@ -58,11 +79,9 @@ android {
androidComponents {
onVariants { variant ->
variant.outputs.forEach { output ->
val versionName = "1.1"
val versionCode = "2"
val buildType = variant.buildType
val date = SimpleDateFormat("yyyyMMdd").format(Date())
output.outputFileName.set("PDA_Printer_Test_v${versionName}_${versionCode}_${buildType}_${date}.apk")
output.outputFileName.set("PDA_Printer_Test_v${autoVersionName}_${autoVersionCode}_${buildType}_${date}.apk")
}
}
}
@@ -76,6 +95,10 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
// 打印SDK
implementation(files("libs/lcprintsdk-release.aar"))
testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
Binary file not shown.
@@ -1,21 +0,0 @@
package android.bld;
interface IPrintManager {
void initPrinter();
void closePrinter();
void printText(String text);
void printText2(String text);
void printQR(String content, int size);
void printBarcode(String content, int type, int width, int height);
void printBitmap(in byte[] bitmap, int width, int height);
void printLine(int lineCount);
void start();
void setFontSize(int size);
void setTextBold(boolean bold);
void setUnderLine(boolean underline);
void setConcentration(int level);
void setFeedPaperSpace(int space);
void setUnwindPaperLen(int length);
void printEnableMark(boolean enable);
void printGoToNextMark();
}
@@ -35,6 +35,11 @@ class MainActivity : ComponentActivity() {
}
}
override fun onStart() {
super.onStart()
printerManager.bindService()
}
override fun onDestroy() {
super.onDestroy()
printerManager.unbindService()
@@ -49,6 +54,14 @@ fun PrinterTestScreen(
var statusMessage by remember { mutableStateOf("未连接打印服务") }
var isConnected by remember { mutableStateOf(false) }
DisposableEffect(printerManager) {
printerManager.setConnectionCallback { connected, message ->
isConnected = connected
statusMessage = message
}
onDispose { }
}
Column(
modifier = modifier
.fillMaxSize()
@@ -56,14 +69,12 @@ fun PrinterTestScreen(
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// 标题
Text(
text = "PDA 打印机测试",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
// 版本信息
Text(
text = "v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) - ${BuildConfig.AUTHOR}",
style = MaterialTheme.typography.bodySmall,
@@ -71,7 +82,7 @@ fun PrinterTestScreen(
modifier = Modifier.padding(bottom = 8.dp)
)
// 状态显示
// ── 状态卡片 ──────────────────────────────────────
Card(
colors = CardDefaults.cardColors(
containerColor = if (isConnected)
@@ -87,48 +98,40 @@ fun PrinterTestScreen(
)
}
Divider()
HorizontalDivider()
// 连接控制区
// ══════════════════════════════════════════════════
// 连接控制
// ══════════════════════════════════════════════════
Text("连接控制", style = MaterialTheme.typography.titleMedium)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
val success = printerManager.bindService()
isConnected = success
statusMessage = if (success) "正在连接打印服务..." else "连接失败"
},
onClick = { printerManager.bindService() },
modifier = Modifier.weight(1f)
) {
Text("绑定服务")
}
) { Text("绑定服务") }
Button(
onClick = {
printerManager.initPrinter().fold(
printerManager.initDevice().fold(
onSuccess = { statusMessage = "打印机初始化成功" },
onFailure = { statusMessage = "初始化失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("初始化")
}
) { Text("初始化") }
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.closePrinter().fold(
printerManager.closeDevice().fold(
onSuccess = { statusMessage = "打印机已关闭" },
onFailure = { statusMessage = "关闭失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("关闭打印机")
}
) { Text("关闭打印机") }
Button(
onClick = {
@@ -137,186 +140,248 @@ fun PrinterTestScreen(
statusMessage = "服务已解绑"
},
modifier = Modifier.weight(1f)
) {
Text("解绑服务")
}
) { Text("解绑服务") }
}
Divider()
HorizontalDivider()
// 基础打印测试
Text("基础打印测试", style = MaterialTheme.typography.titleMedium)
// ══════════════════════════════════════════════════
// 快速打印
// ══════════════════════════════════════════════════
Text("快速打印", style = MaterialTheme.typography.titleMedium)
Button(
onClick = {
printerManager.quickPrintText("Hello Printer!\n测试打印\n").fold(
printerManager.quickText("Hello Printer!\n测试打印\n").fold(
onSuccess = { statusMessage = "快速打印成功" },
onFailure = { statusMessage = "打印失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) {
Text("快速打印测试")
) { Text("快速打印测试") }
Button(
onClick = {
printerManager.buildJob()
.text("工单号: WO-001\n操作员: 张三\n")
.build()
.fold(
onSuccess = { statusMessage = "PrintJob 打印成功" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) { Text("PrintJob 构建器测试") }
HorizontalDivider()
// ══════════════════════════════════════════════════
// 单步打印(手动组合)
// ══════════════════════════════════════════════════
Text("单步打印", style = MaterialTheme.typography.titleMedium)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.addText("手动 text\n").fold(
onSuccess = { statusMessage = "文字已加入缓冲区" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) { Text("添加文字") }
Button(
onClick = {
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.addQR("https://example.com").fold(
onSuccess = { statusMessage = "QR 已加入缓冲区" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) { Text("添加QR") }
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.printText("使用 printText\n").fold(
onSuccess = { statusMessage = "printText 调用成功" },
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.addBarcode("1234567890").fold(
onSuccess = { statusMessage = "条码已加入缓冲区" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("printText")
}
) { Text("添加条码") }
Button(
onClick = {
printerManager.printText2("使用 printText2\n").fold(
onSuccess = { statusMessage = "printText2 调用成功" },
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.addLineFeed(3).fold(
onSuccess = { statusMessage = "空行已加入缓冲区" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("printText2")
}
) { Text("添加空行") }
}
Button(
onClick = {
printerManager.start().fold(
onSuccess = { statusMessage = "开始打印(出纸)" },
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.commit().fold(
onSuccess = { statusMessage = "已提交打印" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) {
Text("start() - 提交打印")
}
) { Text("提交打印 (start)") }
Divider()
HorizontalDivider()
// 样式测试
// ══════════════════════════════════════════════════
// 样式设置
// ══════════════════════════════════════════════════
Text("样式设置", style = MaterialTheme.typography.titleMedium)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.setFontSize(24).fold(
onSuccess = { statusMessage = "字体大小已设置" },
printerManager.ensureOpen().fold(
onSuccess = { statusMessage = "设备已打开,可设置样式" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
printerManager.setFontSize(32).fold(
onSuccess = { statusMessage = "大字体已设置" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("大字体")
}
) { Text("大字体") }
Button(
onClick = {
printerManager.setTextBold(true).fold(
onSuccess = { statusMessage = "已设置粗体" },
printerManager.ensureOpen().fold(
onSuccess = { statusMessage = "设备已打开" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
printerManager.setBold(true).fold(
onSuccess = { statusMessage = "粗体已设置" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("粗体")
}
) { Text("粗体") }
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.setUnderLine(true).fold(
onSuccess = { statusMessage = "已设置下划线" },
printerManager.setUnderline(true).fold(
onSuccess = { statusMessage = "下划线已设置" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("下划线")
}
) { Text("下划线") }
Button(
onClick = {
printerManager.setConcentration(5).fold(
printerManager.setDensity(5).fold(
onSuccess = { statusMessage = "浓度已设置" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("浓度")
}
) { Text("浓度") }
}
Divider()
HorizontalDivider()
// 二维码/条形码测试
Text("二维码与条形码", style = MaterialTheme.typography.titleMedium)
// ══════════════════════════════════════════════════
// 标签打印
// ══════════════════════════════════════════════════
Text("标签打印(黑标对齐)", style = MaterialTheme.typography.titleMedium)
Button(
onClick = {
printerManager.printQR("https://example.com", 6).fold(
onSuccess = { statusMessage = "二维码已加入队列" },
onFailure = { statusMessage = "失败: ${it.message}" }
printerManager.quickLabel("标签打印测试\n工单号: WO-001\n").fold(
onSuccess = { statusMessage = "标签打印成功" },
onFailure = { statusMessage = "标签打印失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) {
Text("打印二维码")
}
Button(
onClick = {
printerManager.printBarcode("1234567890", 0, 2, 60).fold(
onSuccess = { statusMessage = "条形码已加入队列" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) {
Text("打印条形码")
}
Divider()
// 走纸控制
Text("走纸控制", style = MaterialTheme.typography.titleMedium)
) { Text("快速标签打印") }
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = {
printerManager.printLine(3).fold(
onSuccess = { statusMessage = "打印空行" },
printerManager.enableMarkDetection(true).fold(
onSuccess = { statusMessage = "已开启黑标检测" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("打印3空行")
}
) { Text("开启黑标") }
Button(
onClick = {
printerManager.setFeedPaperSpace(100).fold(
onSuccess = { statusMessage = "走纸间距已设置" },
printerManager.enableMarkDetection(false).fold(
onSuccess = { statusMessage = "已关闭黑标检测" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.weight(1f)
) {
Text("走纸间距")
}
) { Text("关闭黑标") }
}
Divider()
Button(
onClick = {
printerManager.ensureOpen().fold(
onSuccess = {
printerManager.enableMarkDetection(true).fold(
onSuccess = {
printerManager.gotoNextMark().fold(
onSuccess = { statusMessage = "已移动到下一标签" },
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
onFailure = { statusMessage = "失败: ${it.message}" }
)
},
modifier = Modifier.fillMaxWidth()
) { Text("下一标签") }
// 综合测试
HorizontalDivider()
// ══════════════════════════════════════════════════
// 综合测试
// ══════════════════════════════════════════════════
Text("综合测试", style = MaterialTheme.typography.titleMedium)
Button(
@@ -327,9 +392,7 @@ fun PrinterTestScreen(
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondary
)
) {
Text("运行完整测试")
}
) { Text("运行完整测试") }
}
}
@@ -339,54 +402,47 @@ private fun runComprehensiveTest(
) {
onStatus("开始综合测试...")
printerManager.initPrinter().onFailure {
printerManager.initDevice().onFailure {
onStatus("初始化失败: ${it.message}")
return
}
// 标题
printerManager.setFontSize(32)
printerManager.setTextBold(true)
printerManager.printText2("=== 打印测试 ===\n")
printerManager.setBold(true)
printerManager.addText("=== 打印测试 ===\n")
// 普通文本
printerManager.setFontSize(24)
printerManager.setTextBold(false)
printerManager.printText2("普通文本测试\n")
printerManager.setBold(false)
printerManager.addText("普通文本测试\n")
// 粗体文本
printerManager.setTextBold(true)
printerManager.printText2("粗体文本测试\n")
printerManager.setTextBold(false)
printerManager.setBold(true)
printerManager.addText("粗体文本测试\n")
printerManager.setBold(false)
// 下划线文本
printerManager.setUnderLine(true)
printerManager.printText2("下划线文本测试\n")
printerManager.setUnderLine(false)
printerManager.setUnderline(true)
printerManager.addText("下划线文本测试\n")
printerManager.setUnderline(false)
// 空行
printerManager.printLine(1)
printerManager.addLineFeed(1)
// 二维码
printerManager.printText2("二维码测试:\n")
printerManager.printQR("https://example.com", 6)
printerManager.printLine(1)
printerManager.addText("二维码测试:\n")
printerManager.addQR("https://example.com")
printerManager.addLineFeed(1)
// 条形码
printerManager.printText2("条形码测试:\n")
printerManager.printBarcode("123456789012", 0, 2, 60)
printerManager.printLine(3)
printerManager.addText("条形码测试:\n")
printerManager.addBarcode("123456789012")
printerManager.addLineFeed(3)
// 分隔线
printerManager.printText2("--------------------------------\n")
printerManager.printText2("测试完成\n")
printerManager.printLine(2)
printerManager.addText("--------------------------------\n")
printerManager.addText("测试完成\n")
printerManager.addLineFeed(2)
// 提交打印
printerManager.start().fold(
onSuccess = { onStatus("综合测试完成,开始打印") },
printerManager.commit().fold(
onSuccess = {
onStatus("综合测试完成,开始打印")
Thread.sleep(1500)
printerManager.closeDevice()
},
onFailure = { onStatus("提交打印失败: ${it.message}") }
)
printerManager.closePrinter()
}
@@ -0,0 +1,67 @@
package com.example.ops_android.printer
class PrintJob(private val manager: PrinterManager) {
private var textContent: String? = null
private var qrContent: String? = null
private var barcodeContent: String? = null
private var lineFeeds: Int = 0
private var useLabelMode: Boolean = false
fun text(text: String): PrintJob {
textContent = text
return this
}
fun qr(content: String): PrintJob {
qrContent = content
return this
}
fun barcode(content: String): PrintJob {
barcodeContent = content
return this
}
fun lineFeed(count: Int = 1): PrintJob {
lineFeeds = count
return this
}
fun labelMode(): PrintJob {
useLabelMode = true
return this
}
/** 初始化 → 添加内容 → 提交 → 等待 → 关闭 */
fun build(): Result<Unit> = runCatching {
manager.initDevice().getOrThrow()
if (useLabelMode)
manager.enableMarkDetection(true).getOrThrow()
textContent?.let { manager.addText(it).getOrThrow() }
qrContent?.let { manager.addQR(it).getOrThrow() }
barcodeContent?.let { manager.addBarcode(it).getOrThrow() }
if (lineFeeds > 0) manager.addLineFeed(lineFeeds).getOrThrow()
manager.commit().getOrThrow()
Thread.sleep(1500)
manager.closeDevice().getOrThrow()
}
/** 初始化 → 添加内容 → 提交(不关闭) */
fun print(): Result<Unit> = runCatching {
manager.ensureOpen().getOrThrow()
if (useLabelMode)
manager.enableMarkDetection(true).getOrThrow()
textContent?.let { manager.addText(it).getOrThrow() }
qrContent?.let { manager.addQR(it).getOrThrow() }
barcodeContent?.let { manager.addBarcode(it).getOrThrow() }
if (lineFeeds > 0) manager.addLineFeed(lineFeeds).getOrThrow()
manager.commit().getOrThrow()
}
}
@@ -1,136 +1,322 @@
package com.example.ops_android.printer
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.graphics.Bitmap
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.bld.IPrintManager
import android.bld.PrintManager
import android.bld.print.aidl.PrinterBinderListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
class PrinterManager(private val context: Context) {
private var printService: IPrintManager? = null
private var isBound = false
// ── 内部状态 ────────────────────────────────────────────
internal var printManager: PrintManager? = null
private set
@Volatile internal var isOpen = false
private set
@Volatile private var intentionalClose = false
private var connectionCallback: ((Boolean, String) -> Unit)? = null
private val mainHandler = Handler(Looper.getMainLooper())
companion object {
private const val TAG = "PrinterManager"
private const val PRINT_SERVICE_ACTION = "android.bld.PrintManager"
private const val PRINT_SERVICE_PACKAGE = "android.bld"
}
private const val CONNECT_TIMEOUT_MS = 10_000L
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
printService = IPrintManager.Stub.asInterface(service)
isBound = true
Log.d(TAG, "Print service connected")
}
// 错误码
internal const val ERROR_NO_ERROR = 0
internal const val ERROR_DEV_IS_BUSY = 1
internal const val ERROR_PRINT_HOT = 2
internal const val ERROR_PRINT_NOPAPER = 3
internal const val ERROR_DEV_NO_BATTERY = 4
internal const val ERROR_DEV_FEED = 5
internal const val ERROR_DEV_PRINT = 6
internal const val ERROR_DEV_BMARK = 7
internal const val ERROR_DEV_NOT_OPEN = 16
internal const val ERROR_NO_DATA = 17
internal const val ERROR_DATA_INVALID = 18
internal const val ERROR_CMD = 19
internal const val ERROR_GRAY_INVALID = 20
override fun onServiceDisconnected(name: ComponentName?) {
printService = null
isBound = false
Log.d(TAG, "Print service disconnected")
fun getErrorMessage(errorCode: Int): String = when (errorCode) {
ERROR_NO_ERROR -> "正常"
ERROR_DEV_IS_BUSY -> "设备忙"
ERROR_PRINT_HOT -> "打印头过热"
ERROR_PRINT_NOPAPER -> "缺纸"
ERROR_DEV_NO_BATTERY -> "电量不足"
ERROR_DEV_FEED -> "走纸错误"
ERROR_DEV_PRINT -> "打印错误"
ERROR_DEV_BMARK -> "黑标错误"
ERROR_DEV_NOT_OPEN -> "设备未打开"
ERROR_NO_DATA -> "无数据"
ERROR_DATA_INVALID -> "数据无效"
ERROR_CMD -> "指令错误"
ERROR_GRAY_INVALID -> "浓度无效"
165 -> "数据输入错误"
else -> "未知错误($errorCode)"
}
}
fun bindService(): Boolean {
return try {
val intent = Intent(PRINT_SERVICE_ACTION).apply {
setPackage(PRINT_SERVICE_PACKAGE)
// ── 连接回调 ───(运行在 Binder 线程,需切换到主线程)──────
private val printerListener = object : PrinterBinderListener {
override fun onPrintCallback(status: Int) {
Log.d(TAG, "Printer callback: status=$status (${getErrorMessage(status)})")
mainHandler.post {
when (status) {
ERROR_NO_ERROR -> {
isOpen = true
connectionCallback?.invoke(true, "打印机已就绪")
}
ERROR_DEV_NOT_OPEN -> {
if (intentionalClose) return@post
isOpen = false
connectionCallback?.invoke(false, "设备未打开,请重新连接")
}
else -> connectionCallback?.invoke(false, getErrorMessage(status))
}
}
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
} catch (e: Exception) {
Log.e(TAG, "Failed to bind print service", e)
false
}
}
fun setConnectionCallback(callback: (Boolean, String) -> Unit) {
connectionCallback = callback
}
// ══════════════════════════════════════════════════════════
// 连接管理
// ══════════════════════════════════════════════════════════
fun bindService() {
notifyConnection(false, "正在连接打印服务...")
Thread {
try {
kotlinx.coroutines.runBlocking {
withTimeout(CONNECT_TIMEOUT_MS) {
withContext(Dispatchers.IO) {
intentionalClose = false
Log.d(TAG, "Attempting to get PrintManager instance...")
val manager = PrintManager.getDefaultInstance(context)
if (manager == null) {
notifyConnection(false, "设备不支持打印功能")
return@withContext
}
printManager = manager
manager.addPrintListener(printerListener)
Log.d(TAG, "Opening printer...")
val success = manager.open()
Log.d(TAG, "open() returned: $success")
if (success) {
isOpen = true
val version = manager.getPrinterVer()
if (version != null) {
notifyConnection(true, "打印机已连接 (固件: $version)")
} else {
notifyConnection(true, "打印机已连接")
}
} else {
isOpen = false
notifyConnection(false, "打印机打开失败,请检查设备")
}
}
}
}
} catch (e: TimeoutCancellationException) {
Log.e(TAG, "bindService timeout after ${CONNECT_TIMEOUT_MS}ms")
notifyConnection(false, "连接超时,请检查打印服务是否运行")
} catch (e: Exception) {
Log.e(TAG, "Failed to open print service", e)
notifyConnection(false, "连接异常: ${e.message}")
}
}.start()
}
fun unbindService() {
if (isBound) {
context.unbindService(serviceConnection)
isBound = false
Thread {
try {
if (isOpen) {
printManager?.removePrintListener(printerListener)
printManager?.close()
isOpen = false
notifyConnection(false, "服务已断开")
}
} catch (e: Exception) {
Log.e(TAG, "Failed to close print service", e)
}
}.start()
}
fun isConnected(): Boolean = isOpen && printManager != null
fun getFirmwareVersion(): String? {
return try {
printManager?.getPrinterVer()
} catch (e: Exception) {
Log.e(TAG, "Failed to get printer version", e)
null
}
}
// 基础连接方法
fun initPrinter(): Result<Unit> = runCatching {
printService?.initPrinter() ?: throw Exception("Print service not connected")
// ══════════════════════════════════════════════════════════
// 设备控制
// ══════════════════════════════════════════════════════════
fun ensureOpen(): Result<Unit> = runCatching {
checkPrinter()
if (!isOpen) {
intentionalClose = false
val opened = printManager?.open() ?: false
if (!opened) throw Exception("设备未打开,请重新绑定服务")
isOpen = true
}
}
fun closePrinter(): Result<Unit> = runCatching {
printService?.closePrinter() ?: throw Exception("Print service not connected")
fun initDevice(): Result<Unit> = runCatching {
ensureOpen().getOrThrow()
val result = printManager!!.reset()
if (result != ERROR_NO_ERROR) throw Exception("初始化失败: ${getErrorMessage(result)}")
}
// 打印方法
fun printText(text: String): Result<Unit> = runCatching {
printService?.printText(text) ?: throw Exception("Print service not connected")
fun closeDevice(): Result<Unit> = runCatching {
checkOpen()
isOpen = false
intentionalClose = true
printManager?.close()
}
fun printText2(text: String): Result<Unit> = runCatching {
printService?.printText2(text) ?: throw Exception("Print service not connected")
fun commit(): Result<Unit> = runCatching {
checkOpen()
printManager!!.start()
}
fun printQR(content: String, size: Int = 6): Result<Unit> = runCatching {
printService?.printQR(content, size) ?: throw Exception("Print service not connected")
// ══════════════════════════════════════════════════════════
// 内容添加(供 PrintJob 使用)
// ══════════════════════════════════════════════════════════
internal fun addText(text: String): Result<Unit> = runCatching {
checkOpen()
val pm = printManager!!
pm.addText(0, pm.getFontSize() ?: 24, pm.isFontBold() ?: false, pm.isUnderLine() ?: false, text)
}
fun printBarcode(content: String, type: Int = 0, width: Int = 2, height: Int = 60): Result<Unit> = runCatching {
printService?.printBarcode(content, type, width, height) ?: throw Exception("Print service not connected")
internal fun addQR(content: String): Result<Unit> = runCatching {
checkOpen()
printManager!!.addQRCode(2, 384, content)
}
fun printBitmap(bitmap: ByteArray, width: Int, height: Int): Result<Unit> = runCatching {
printService?.printBitmap(bitmap, width, height) ?: throw Exception("Print service not connected")
internal fun addBarcode(content: String): Result<Unit> = runCatching {
checkOpen()
printManager!!.addBarcode(2, 60, content, 73, 3)
}
fun printLine(lineCount: Int = 1): Result<Unit> = runCatching {
printService?.printLine(lineCount) ?: throw Exception("Print service not connected")
internal fun addLineFeed(count: Int = 1): Result<Unit> = runCatching {
checkOpen()
printManager!!.addLineFeed(count)
}
fun start(): Result<Unit> = runCatching {
printService?.start() ?: throw Exception("Print service not connected")
}
// ══════════════════════════════════════════════════════════
// 样式设置
// ══════════════════════════════════════════════════════════
// 样式设置方法
fun setFontSize(size: Int): Result<Unit> = runCatching {
printService?.setFontSize(size) ?: throw Exception("Print service not connected")
checkOpen()
printManager!!.setFontSize(size)
}
fun setTextBold(bold: Boolean): Result<Unit> = runCatching {
printService?.setTextBold(bold) ?: throw Exception("Print service not connected")
fun setBold(bold: Boolean): Result<Unit> = runCatching {
checkOpen()
printManager!!.setFontBold(bold)
}
fun setUnderLine(underline: Boolean): Result<Unit> = runCatching {
printService?.setUnderLine(underline) ?: throw Exception("Print service not connected")
fun setUnderline(underline: Boolean): Result<Unit> = runCatching {
checkOpen()
printManager!!.setUnderLine(underline)
}
fun setConcentration(level: Int): Result<Unit> = runCatching {
printService?.setConcentration(level) ?: throw Exception("Print service not connected")
fun setDensity(level: Int): Result<Unit> = runCatching {
checkOpen()
printManager!!.setDensity(level)
}
// 走纸控制
fun setFeedPaperSpace(space: Int): Result<Unit> = runCatching {
printService?.setFeedPaperSpace(space) ?: throw Exception("Print service not connected")
// ══════════════════════════════════════════════════════════
// 走纸控制
// ══════════════════════════════════════════════════════════
fun setPaperGap(dots: Int): Result<Unit> = runCatching {
checkOpen()
printManager!!.setFeedPaperSpace(dots)
}
fun setUnwindPaperLen(length: Int): Result<Unit> = runCatching {
printService?.setUnwindPaperLen(length) ?: throw Exception("Print service not connected")
fun setUnwindLength(len: Int): Result<Unit> = runCatching {
checkOpen()
printManager!!.setUnwindPaperLen(len)
}
// 标签纸控制
fun printEnableMark(enable: Boolean): Result<Unit> = runCatching {
printService?.printEnableMark(enable) ?: throw Exception("Print service not connected")
// ══════════════════════════════════════════════════════════
// 黑标 / 标签
// ══════════════════════════════════════════════════════════
fun enableMarkDetection(enable: Boolean): Result<Unit> = runCatching {
checkOpen()
printManager!!.setBlackLabel(enable)
}
fun printGoToNextMark(): Result<Unit> = runCatching {
printService?.printGoToNextMark() ?: throw Exception("Print service not connected")
fun gotoNextMark(): Result<Unit> = runCatching {
checkOpen()
printManager!!.addLineFeed(1)
printManager!!.start()
}
// 便捷组合方法
fun quickPrintText(text: String): Result<Unit> = runCatching {
initPrinter().getOrThrow()
printText2(text).getOrThrow()
start().getOrThrow()
closePrinter().getOrThrow()
// ══════════════════════════════════════════════════════════
// 图片
// ══════════════════════════════════════════════════════════
fun printImage(bitmap: Bitmap, align: Int = 1): Result<Unit> = runCatching {
checkOpen()
printManager!!.addImage(align, bitmap)
}
fun isServiceConnected(): Boolean = isBound && printService != null
// ══════════════════════════════════════════════════════════
// 便捷方法
// ══════════════════════════════════════════════════════════
fun quickText(text: String): Result<Unit> = runCatching {
initDevice().getOrThrow()
addText(text).getOrThrow()
commit().getOrThrow()
Thread.sleep(1500)
closeDevice().getOrThrow()
}
fun quickLabel(text: String): Result<Unit> = runCatching {
initDevice().getOrThrow()
enableMarkDetection(true).getOrThrow()
addText(text).getOrThrow()
commit().getOrThrow()
Thread.sleep(1500)
closeDevice().getOrThrow()
}
/** 创建链式打印任务 */
fun buildJob(): PrintJob = PrintJob(this)
// ── 内部工具 ────────────────────────────────────────────
internal fun checkPrinter() {
if (printManager == null)
throw Exception("打印服务未初始化,请先点击'绑定服务'")
}
private fun checkOpen() {
checkPrinter()
if (!isOpen)
throw Exception("打印服务未连接,请先点击'绑定服务'")
}
private fun notifyConnection(connected: Boolean, message: String) {
mainHandler.post { connectionCallback?.invoke(connected, message) }
}
}
+3
View File
@@ -0,0 +1,3 @@
#Wed Jun 24 20:47:38 CST 2026
VERSION_CODE=23
VERSION_NAME=1.2