# 打印机 Android SDK 开发手册

> **文档版本**: V5.7.4  
> **适用 SDK**: `printersdkv5.7.3.jar`  
> **文档用途**: 供客户/集成商进行二次开发参考  
> **目标平台**: Android 5.0 (API 21) 及以上

---

## 目录

1. [概述](#1-概述)
2. [环境要求与 SDK 集成](#2-环境要求与-sdk-集成)
3. [项目结构说明](#3-项目结构说明)
4. [快速开始](#4-快速开始)
5. [打印机连接详解](#5-打印机连接详解)
    - 5.1 [蓝牙连接](#51-蓝牙连接)
    - 5.2 [USB 连接](#52-usb-连接)
    - 5.3 [WiFi 连接](#53-wifi-连接)
    - 5.4 [串口连接](#54-串口连接)
    - 5.5 [连接状态管理](#55-连接状态管理)
6. [打印功能开发](#6-打印功能开发)
    - 6.1 [文本打印](#61-文本打印)
    - 6.2 [图片打印](#62-图片打印)
    - 6.3 [条码/二维码打印](#63-条码二维码打印)
    - 6.4 [标签打印（CPCL/TSPL）](#64-标签打印cpcltspl)
    - 6.5 [表格打印](#65-表格打印)
    - 6.6 [PDF 打印](#66-pdf-打印)
    - 6.7 [Canvas 画布打印（PageMode）](#67-canvas-画布打印pagemode)
    - 6.8 [软流控打印（大数据量）](#68-软流控打印大数据量)
7. [核心 API 参考](#7-核心-api-参考)
8. [AndroidManifest 权限配置](#8-androidmanifest-权限配置)
9. [常见问题与注意事项](#9-常见问题与注意事项)
10. [附录](#10-附录)

---

## 1. 概述

本 SDK 为热敏/热转印打印机 Android 端开发套件，支持以下功能：

| 功能项 | 说明 |
|--------|------|
| **连接方式** | 蓝牙（经典蓝牙）、USB Host、WiFi（Socket）、串口（RS232/TTL） |
| **打印内容** | 纯文本、图片（相机/相册/Bitmap）、一维码、二维码、PDF、标签 |
| **指令集** | ESC/POS、CPCL、TSPL |
| **高级特性** | PageMode 画布绘制、图片二值化、软流控分块发送、表格转 Bitmap、多语言混排 |
| **纸宽支持** | 58mm（384px）、80mm（576px）、100mm（724px） |

---

## 2. 环境要求与 SDK 集成

### 2.1 开发环境

| 项目 | 要求 |
|------|------|
| Android Studio | 4.0 及以上 |
| Gradle Plugin | 7.0 及以上 |
| compileSdk | 32 |
| minSdkVersion | 21 (Android 5.0) |
| targetSdkVersion | 31 |

### 2.2 集成 SDK

1. 将 `printersdkv5.7.4.jar` 复制到您项目的 `libs/` 目录下。
2. **重要提示**: 如果您不需要使用串口通信和PDF打印功能，则无需迁移SO库文件。只有在需要使用串口通信或PDF打印时，才需要将对应的SO库文件复制到项目中。
3. 在模块级 `build.gradle` 中添加依赖：

```groovy
android {
    compileSdk 32
    defaultConfig {
        minSdk 21
        targetSdk 31
    }
}

dependencies {
    implementation files('libs/printersdkv5.7.4.jar')
    // 其他 AndroidX 依赖请自行补充
}
```

3. 同步项目（Sync Project with Gradle Files）。

---

## 3. 项目结构说明

Demo 项目采用经典 Android 工程结构，关键目录说明如下：

```
PrintDemoV5.7.1/
├── src/main/java/com/printer/demo/
│   ├── MainActivity.java              # 主入口，功能导航
│   ├── SettingActivity.java           # 【核心】打印机连接配置
│   ├── TextPrintActivity.java         # 文本打印示例
│   ├── PicturePrintActivity.java      # 图片打印示例
│   ├── BarcoePrintActivity.java       # 条码/二维码打印示例
│   ├── LablePrintActivity.java        # 标签打印示例（CPCL/TSPL）
│   ├── PdfPrintActivity.java          # PDF 打印示例
│   ├── BaseActivity.java              # Activity 基类（标题栏、状态显示）
│   ├── BluetoothDeviceList.java       # 蓝牙设备搜索列表
│   ├── UsbDeviceList.java             # USB 设备搜索列表
│   ├── SerialsDeviceList.java         # 串口设备搜索列表
│   ├── IpEditActivity.java            # WiFi IP 输入界面
│   └── utils/                         # 工具类集合
│       ├── XTUtils.java               # 核心打印工具（小票、表格、测试页）
│       ├── CanvasUtils.java           # 画布打印工具
│       ├── PrinterUtils.java          # 表格转 Bitmap 高级实现
│       ├── PrinterBlockSender.java    # 软流控分块发送器
│       ├── ImageBinarizationUtils.java# 图片二值化（Otsu/Floyd）
│       ├── Table2Bitmap.java          # 表格绘制为 Bitmap
│       ├── AlignedTable.java          # 对齐表格数据结构
│       └── PrintLabel58/80/100/*.java # 预设标签模板
├── src/main/res/layout/               # 界面布局文件
├── src/main/assets/                   # 字体文件（kaiti.ttf, songti.ttf, heiti.ttf）
└── libs/printersdkv5.7.4.jar          # SDK 核心包
```

**建议**: 客户开发时，重点参考 `SettingActivity.java`（连接逻辑）和 `XTUtils.java`（打印逻辑）。

---

## 4. 快速开始

以下代码展示最简集成流程：**初始化 → 连接蓝牙打印机 → 打印文本 → 断开连接**。

```java
import com.printer.sdk.PrinterInstance;
import com.printer.sdk.PrinterConstants.Connect;
import com.printer.sdk.PrinterConstants.Command;

public class QuickStartActivity extends AppCompatActivity {

    private PrinterInstance mPrinter;
    private Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Connect.SUCCESS:
                    Toast.makeText(context, "连接成功", Toast.LENGTH_SHORT).show();
                    // 连接成功后执行打印
                    printText();
                    break;
                case Connect.FAILED:
                    Toast.makeText(context, "连接失败", Toast.LENGTH_SHORT).show();
                    break;
                case Connect.CLOSED:
                    Toast.makeText(context, "连接已关闭", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    // 1. 通过蓝牙 MAC 地址连接
    private void connectBluetooth(String macAddress) {
        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
        mPrinter = PrinterInstance.getPrinterInstance(this, device, mHandler);
        mPrinter.openConnection();
    }

    // 2. 打印文本（必须在子线程执行）
    private void printText() {
        new Thread(() -> {
            mPrinter.initPrinter();
            mPrinter.setFont(0, 0, 0, 0, 0);
            mPrinter.setPrinter(Command.ALIGN, Command.ALIGN_CENTER);
            mPrinter.printText("欢迎使用打印机 SDK\n");
            mPrinter.setPrinter(Command.PRINT_AND_WAKE_PAPER_BY_LINE, 3); // 走纸3行
        }).start();
    }

    // 3. 断开连接
    private void disconnect() {
        if (mPrinter != null) {
            mPrinter.closeConnection();
            mPrinter = null;
        }
    }
}
```

---

## 5. 打印机连接详解

SDK 支持 **4 种连接方式**，通过 `PrinterInstance.getPrinterInstance(...)` 的不同重载方法实现。

### 5.1 蓝牙连接

#### 5.1.1 经典蓝牙搜索与连接

```java
// 从蓝牙设备列表返回 MAC 地址后连接
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
PrinterInstance myPrinter = PrinterInstance.getPrinterInstance(context, device, handler);
myPrinter.openConnection();
```

#### 5.1.2 扫描二维码连接（扫描 MAC 地址）

Demo 中提供了 `MipcaActivityCapture.java`，扫描包含 MAC 地址的二维码后自动解析并连接。

```java
// 解析扫描结果，提取 12 位十六进制 MAC
String rawAddress = input.replaceAll("[^0-9A-Fa-f]", "");
if (rawAddress.length() >= 12) {
    String mac = rawAddress.substring(0, 12);
    // 格式化为标准 MAC 地址 00:12:5B:01:F6:D2
    mac = mac.replaceAll("(.{2})(?=.)", "$1:").substring(0, 17);
}
```

#### 5.1.3 扫描名称+密码连接

适用于带配对密码的场景，Demo 中参考 `MipcaActivityCapture_GetBtName.java`。

#### 5.1.4 蓝牙断开监听

连接成功后注册广播，监听蓝牙断开事件：

```java
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
context.registerReceiver(disconnectReceiver, filter);
```

### 5.2 USB 连接

#### 5.2.1 搜索 USB 设备

Demo 中 `UsbDeviceList.java` 已封装搜索逻辑，过滤 VID/PID：
- VID: `0x0483`, PID: `0x5720`
- VID: `0x067B`, PID: `0x2305`
- VID: `0x6868`, PID: `0x0200`

#### 5.2.2 请求权限并连接

```java
UsbDevice usbDevice = ...; // 从 UsbDeviceList 返回
PrinterInstance myPrinter = PrinterInstance.getPrinterInstance(context, usbDevice, handler);

UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (usbManager.hasPermission(usbDevice)) {
    myPrinter.openConnection();
} else {
    PendingIntent pendingIntent = PendingIntent.getBroadcast(
        context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
    usbManager.requestPermission(usbDevice, pendingIntent);
}
```

#### 5.2.3 USB 拔插监听

```java
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
context.registerReceiver(usbDetachReceiver, filter);
```

### 5.3 WiFi 连接

WiFi 打印机通过 Socket 连接，默认端口 **9100**。

```java
String ipAddress = "192.168.0.100"; // 打印机 IP
PrinterInstance myPrinter = PrinterInstance.getPrinterInstance(ipAddress, 9100, handler);

// WiFi 连接必须在子线程执行
new Thread(() -> myPrinter.openConnection()).start();
```

### 5.4 串口连接

支持 `/dev/tty*` 设备节点，可配置波特率和流控。

```java
String path = "/dev/ttyS1";      // 串口设备路径
int baudrate = 9600;              // 波特率
int flags = 0;                    // 标志位
int flowControl = 1;              // 流控: 0=XON/XOFF, 1=RTS/CTS, 2=NONE

PrinterInstance myPrinter = PrinterInstance.getPrinterInstance(
    new File(path), baudrate, flags, flowControl, handler);
myPrinter.openConnection();
```

### 5.5 连接状态管理

通过 `Handler` 接收连接状态回调：

| 状态常量 | 值 | 说明 |
|----------|-----|------|
| `Connect.SUCCESS` | 0 | 连接成功 |
| `Connect.FAILED` | 1 | 连接失败 |
| `Connect.CLOSED` | 2 | 连接关闭 |
| `Connect.NODEVICE` | 3 | 无设备 |

```java
private Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case Connect.SUCCESS:
                // 保存连接状态，更新 UI
                break;
            case Connect.FAILED:
                // 提示用户连接失败
                break;
            case Connect.CLOSED:
                // 清理资源，更新 UI
                break;
        }
    }
};
```

---

## 6. 打印功能开发

**重要提示**: 除 WiFi 连接强烈建议在子线程执行外，所有打印操作均建议在子线程中调用，避免阻塞 UI。

### 6.1 文本打印

#### 基础文本打印

```java
mPrinter.initPrinter();                                   // 初始化打印机
mPrinter.setFont(0, 0, 0, 0, 0);                         // 恢复默认字体
mPrinter.setPrinter(Command.ALIGN, Command.ALIGN_CENTER); // 居中对齐
mPrinter.printText("欢迎光临\n");
mPrinter.setPrinter(Command.PRINT_AND_WAKE_PAPER_BY_LINE, 2); // 走纸2行
```

#### 字体设置

```java
// setFont(字符类型, 倍宽, 倍高, 加粗, 下划线)
mPrinter.setFont(0, 1, 1, 0, 0);  // 倍宽倍高
mPrinter.setFont(0, 0, 0, 1, 0);  // 加粗
mPrinter.setFont(0, 0, 0, 0, 1);  // 下划线
```

#### 小票模板打印

参考 `XTUtils.printNote(...)` 方法，已封装完整的小票格式（标题、商品、价格、尾部等）。

```java
XTUtils.printNote(getResources(), mPrinter);
```

#### 代码页设置

如需打印特殊语言（如老挝文、泰文），需先设置字符集：

```java
mPrinter.setCharsetName("UTF-8");
```

### 6.2 图片打印

#### 直接打印 Bitmap

```java
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_image);

// 如果图片宽度超过纸宽，先缩放
if (bitmap.getWidth() > PrinterConstants.paperWidth) {
    bitmap = Utils.zoomImage(bitmap, PrinterConstants.paperWidth, printerType);
}

// 打印图片
mPrinter.printImage(bitmap, PAlign.NONE, 0, false);
```

参数说明：
- `PAlign`: 对齐方式 (`NONE`, `START`, `CENTER`, `END`)
- `leftMargin`: 左边距（像素）
- `isCompressed`: 是否压缩（通常传 `false`）

#### 图片二值化后打印（推荐）

对于彩色/灰度图片，建议先二值化以获得更好的打印效果：

```java
// Otsu 大津法二值化
Bitmap otsu = ImageBinarizationUtils.binarizeImage(bitmap, "otsu", 0);
mPrinter.printImage(otsu, PAlign.NONE, 0, false);

// Floyd-Steinberg 抖动二值化
Bitmap floyd = ImageBinarizationUtils.binarizeImage(bitmap, "floyd", 0);
mPrinter.printImage(floyd, PAlign.NONE, 0, false);
```

#### 针式打印机图片

```java
mPrinter.printImageStylus(bitmap, 1);
```

### 6.3 条码/二维码打印

#### 一维码

```java
Barcode barcode = new Barcode(
    BarcodeType.CODE128,  // 类型: CODE128/CODE39/CODABAR/ITF/CODE93/UPC_A/UPC_E/JAN13/JAN8
    2,                    // 宽度（2~6）
    162,                  // 高度（1~255）
    2,                    // 文字位置（0=不显示, 1=上方, 2=下方, 3=上下方）
    "1234567890"          // 内容
);
mPrinter.printBarCode(barcode);
```

#### 二维码

```java
Barcode qrCode = new Barcode(
    BarcodeType.QRCODE,   // 或 PDF417 / DATAMATRIX
    2,                    // 版本/字符数（根据类型含义不同）
    3,                    // 纠错等级/高度
    6,                    // 放大倍数
    "https://www.example.com"
);
mPrinter.printBarCode(qrCode);
```

#### 扫码后打印

Demo 中 `MipcaActivityCapture.java` 封装了 ZXing 扫描模块，扫描结果可直接传入打印。

### 6.4 标签打印（CPCL/TSPL）

#### CPCL 指令标签打印

```java
// 以 80mm 标签纸为例
iPrinter.pageSetup(LablePaperType.Size_80mm, pageWidth, pageHeight);
iPrinter.drawBorder(3, x1, y1, x2, y2);
iPrinter.drawLine(width, x1, y1, x2, y2, true);
iPrinter.drawText(x1, y1, x2, y2, PAlign.CENTER, PAlign.CENTER,
    "标签内容", LableFontSize.Size_64, 0, 0, 0);
iPrinter.drawBarCode(...);
iPrinter.print(PRotate.Rotate_0, copies); // 打印份数
```

Demo 中已提供多种预设模板：
- `PrintLabel58.java` — 58mm 标签
- `PrintLabel80.java` — 80mm 标签
- `PrintLabel100.java` — 100mm 标签
- `PrintLabelDrink.java` — 饮品标签
- `PrintLabelExpress.java` — 快递标签

#### TSPL 指令设置

```java
mPrinter.setPrinterTSPL(CommandTSPL.SPEED, 12);      // 打印速度
mPrinter.setPrinterTSPL(CommandTSPL.DENSITY, 7);     // 打印浓度
mPrinter.setGAPTSPL(gapWidth, gapOffset);            // 标签间隙
```

### 6.5 表格打印

#### 简易表格（SDK 原生）

```java
String column = "Name;Price;Qty;Total";
Table table = new Table(column, ";", new int[]{18, 10, 10, 12});
table.addRow("Apple;5.00;2;10.00");
table.addRow("Banana;3.00;5;15.00");
mPrinter.printTable(table);
```

#### 高级对齐表格（转 Bitmap 打印）

支持中文、老挝文混排，超长自动换行，左/中/右对齐：

```java
String header = "品名|数量|单价|小计";
int[] widths = {10, 4, 4, 4};  // 列宽（字符数）
AlignedTable table = new AlignedTable(header, "\\|", widths, 0, 1);

table.setColumnAlign(0, AlignedTable.Align.LEFT);
table.setColumnAlign(1, AlignedTable.Align.CENTER);
table.setColumnAlign(2, AlignedTable.Align.RIGHT);
table.setColumnAlign(3, AlignedTable.Align.RIGHT);

table.addRow("矿泉水|2|5,000|10,000");
table.addRow("老挝文商品名超长测试|3|2,800|8,400");

// 转为 Bitmap 后打印
Bitmap bmp = Table2Bitmap.get().draw(table);
Bitmap bin = ImageBinarizationUtils.binarizeImage(bmp, "otsu", 0);
mPrinter.printImage(bin, PrinterConstants.PAlign.NONE, 0, false);
```

#### 权重列宽

```java
// 根据纸宽自动分配列宽比例
int[] widths = ColWidthUtil.weight(PrinterConstants.paperWidth, 16, 5, 2, 2, 2);
```

### 6.6 PDF 打印

参考 `PdfPrintActivity.java`，通过解析 PDF 页面转换为 Bitmap 后调用 `printImage` 打印。

### 6.7 Canvas 画布打印（PageMode）

适用于复杂排版场景，支持文本、线条、条码、二维码、图片的自由组合。

```java
CanvasDrawPrint cd = new CanvasDrawPrint();
cd.SetPageMode(576, 800);  // 设置画布尺寸：宽576px，高800px

// 绘制文本
cd.PrintText(10, 10, 0, 24, 1, 0, 1, "标题文字", context);

// 绘制直线
cd.PrintLine(0, 50, 576, 50, 2);

// 绘制二维码
cd.PrintQRCode(200, 100, 150, 150, 3, 0, 0, "https://example.com");

// 绘制一维码
cd.PrintCode(BarcodeType.CODE128, 50, 300, 300, 100, true, "123456");

// 获取 Bitmap 并打印
Bitmap canvasBitmap = cd.getCanvasImage();
mPrinter.printImage(canvasBitmap, PAlign.NONE, 0, false);
```

**回调监听**: 设置 `CanvasDrawBack` 可监听绘制错误。

```java
cd.setCanvasDrawBack(new CanvasDrawPrint.CanvasDrawBack() {
    @Override
    public void resultsBack(int type) {
        switch (type) {
            case CanvasDrawPrint.TEXT: Log.e("TAG", "绘制文字出错"); break;
            case CanvasDrawPrint.QRCODE_ORDINARY: Log.e("TAG", "绘制二维码出错"); break;
            // ... 其他类型
        }
    }
});
```

### 6.8 软流控打印（大数据量）

当打印大量文本+图片时，使用 `PrinterBlockSender` 分块发送，自动查询打印机缓冲区状态，防止数据丢失。

```java
// 1. 准备 Bitmap（先转为打印指令字节）
byte[] printBytes = Utils.originalBmpToPrintByte(bitmap, PAlign.NONE, 0, 128);

// 2. 封装打印机的读写接口
PrinterBlockSender.PrinterSendRead io = new PrinterBlockSender.PrinterSendRead() {
    public int sendBytesData(byte[] data) {
        return mPrinter.sendBytesData(data);
    }
    public int read(byte[] buffer) {
        return mPrinter.read(buffer);
    }
};

// 3. 创建发送器
PrinterBlockSender sender = new PrinterBlockSender(io);
sender.setTimeoutByCharCount(text.length()); // 根据字符数动态设置超时

// 4. 执行打印
int initCode = sender.initPrinter();
if (initCode == 0) {
    sender.printText(text);           // 软流控打印文本
    sender.lineFeed();
    sender.sendChunkWithCode(printBytes); // 软流控打印图片
}
```

错误码说明：
| 错误码 | 含义 |
|--------|------|
| 0 | 打印完成 |
| 2 | 缺纸 |
| 3 | 开盖 |
| 4 | 过热 |
| 6 | 超时 |

---

## 7. 核心 API 参考

### 7.1 PrinterInstance（打印机实例）

| 方法 | 说明 |
|------|------|
| `getPrinterInstance(Context, BluetoothDevice, Handler)` | 蓝牙连接 |
| `getPrinterInstance(Context, UsbDevice, Handler)` | USB 连接 |
| `getPrinterInstance(String ip, int port, Handler)` | WiFi 连接 |
| `getPrinterInstance(File path, int baudrate, int flags, int flow, Handler)` | 串口连接 |
| `openConnection()` | 打开连接 |
| `closeConnection()` | 关闭连接 |
| `initPrinter()` | 初始化打印机 |
| `printText(String text)` | 打印文本 |
| `printImage(Bitmap bitmap, PAlign align, int leftMargin, boolean isCompressed)` | 打印图片 |
| `printImage(Bitmap bitmap, PAlign align, int leftMargin, int threshold)` | 打印图片（带阈值） |
| `printImageStylus(Bitmap bitmap, int mode)` | 针式打印机图片 |
| `printBarCode(Barcode barcode)` | 打印条码 |
| `printTable(Table table)` | 打印表格 |
| `printSelfCheckingPage()` | 打印自检页 |
| `cutPaper(int mode, int distance)` | 切纸 |
| `getCurrentStatus()` | 获取打印机状态 |
| `sendBytesData(byte[] data)` | 发送原始字节数据 |
| `read(byte[] buffer)` | 读取打印机返回数据 |
| `setFont(int charset, int width, int height, int bold, int underline)` | 设置字体 |
| `setPrinter(Command cmd, int value)` | 设置打印机参数 |
| `setPrinterTSPL(CommandTSPL cmd, int value)` | TSPL 参数设置 |
| `setCharsetName(String charset)` | 设置字符编码 |
| `setPaperWidth(int width)` | 设置纸宽 |

### 7.2 关键常量

#### Command（ESC/POS 指令）

| 常量 | 说明 |
|------|------|
| `Command.ALIGN` | 对齐方式 |
| `Command.ALIGN_LEFT` | 左对齐 |
| `Command.ALIGN_CENTER` | 居中对齐 |
| `Command.ALIGN_RIGHT` | 右对齐 |
| `Command.PRINT_AND_WAKE_PAPER_BY_LINE` | 按行走纸 |

#### BarcodeType（条码类型）

| 常量 | 类型 |
|------|------|
| `BarcodeType.CODE128` | CODE128 一维码 |
| `BarcodeType.CODE39` | CODE39 一维码 |
| `BarcodeType.CODABAR` | CODABAR 一维码 |
| `BarcodeType.ITF` | ITF 一维码 |
| `BarcodeType.CODE93` | CODE93 一维码 |
| `BarcodeType.UPC_A` | UPC-A 一维码 |
| `BarcodeType.UPC_E` | UPC-E 一维码 |
| `BarcodeType.JAN13` | EAN13/JAN13 一维码 |
| `BarcodeType.JAN8` | EAN8/JAN8 一维码 |
| `BarcodeType.QRCODE` | QR 二维码 |
| `BarcodeType.PDF417` | PDF417 二维码 |
| `BarcodeType.DATAMATRIX` | DataMatrix 二维码 |

#### PAlign（图片对齐）

| 常量 | 说明 |
|------|------|
| `PAlign.NONE` | 不对齐，按 leftMargin 偏移 |
| `PAlign.START` | 左对齐 |
| `PAlign.CENTER` | 居中对齐 |
| `PAlign.END` | 右对齐 |

---

## 8. AndroidManifest 权限配置

在 `AndroidManifest.xml` 中必须声明以下权限：

```xml
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ 需要以下权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- WiFi/网络权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- USB 权限 -->
<uses-permission android:name="android.hardware.usb.host" />

<!-- 存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Android 11+ 如需访问所有文件 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

<!-- 相机/扫描权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

<!-- 位置权限（蓝牙扫描需要） -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

**Android 6.0+ 动态权限说明**：
- `BLUETOOTH_SCAN`、`BLUETOOTH_CONNECT`、`ACCESS_FINE_LOCATION`、`CAMERA`、`WRITE_EXTERNAL_STORAGE` 等需要在运行时动态申请。

---

## 9. 常见问题与注意事项

### 9.1 连接相关

| 问题 | 解决方案 |
|------|----------|
| 蓝牙搜索不到设备 | 确认打印机已开机且处于可发现模式；Android 6.0+ 需申请定位权限 |
| USB 连接无反应 | 确认 OTG 线正常；确认已授权 USB 权限；检查 VID/PID 是否匹配 |
| WiFi 连接超时 | 确认手机与打印机在同一局域网；确认 IP 地址和端口 9100 正确 |
| 串口连接失败 | 确认设备节点路径正确（如 `/dev/ttyS1`）；确认波特率匹配 |

### 9.2 打印相关

| 问题 | 解决方案 |
|------|----------|
| 打印乱码 | 确认字符编码设置正确（`setCharsetName`）；确认打印机代码页匹配 |
| 图片打印模糊 | 建议先使用 `ImageBinarizationUtils.binarizeImage` 二值化处理 |
| 表格对不齐 | 使用 `AlignedTable` + `Table2Bitmap` 方式转为 Bitmap 打印 |
| 老挝文/泰文显示异常 | 使用 UTF-8 编码，并通过 `Table2Bitmap` 方式渲染为 Bitmap 打印 |
| 大数据量打印丢内容 | 使用 `PrinterBlockSender` 软流控分块发送 |
| 打印内容偏移 | 调整 `PrinterConstants.paperWidth` 与实际纸宽一致（384/576/724） |

### 9.3 开发注意事项

1. **线程安全**: WiFi 连接和打印操作必须在子线程执行；蓝牙/USB/串口打印也建议在子线程执行。
2. **连接状态保持**: 建议在全局保存 `PrinterInstance` 引用和连接状态，避免重复连接。
3. **资源释放**: Activity 销毁时及时关闭连接、注销广播接收器，避免内存泄漏。
4. **纸宽设置**: 打印前确保 `PrinterConstants.paperWidth` 与实际纸张宽度一致，否则会导致排版错乱。
5. **字体文件**: 如需使用自定义字体（如楷体、宋体），将 `.ttf` 文件放入 `assets/` 目录，通过 `CanvasDrawPrint` 指定字体路径。

---

## 10. 附录

### 10.1 纸宽与像素对照表

| 纸宽 | 像素宽度 | 适用场景 |
|------|----------|----------|
| 58mm | 384px | 便携式小票 |
| 80mm | 576px | 标准收银小票 |
| 100mm | 724px | 大面单/标签 |

### 10.2 打印机状态码

```java
int status = mPrinter.getCurrentStatus();
```

| 返回值 | 状态说明 |
|--------|----------|
| 0 | 正常 |
| -1 | 读错误 |
| -2 | 缺纸 |
| -3 | 纸将尽 |
| -4 | 开盖 |
| -5 | 写错误 |

### 10.3 串口流控类型

| 值 | 类型 | 说明 |
|-----|------|------|
| 0 | XON/XOFF | 软件流控 |
| 1 | RTS/CTS | 硬件流控 |
| 2 | NONE | 无流控 |

### 10.4 联系与支持

如您在使用 SDK 过程中遇到无法解决的问题，请联系厂家技术支持，并提供以下信息：
- SDK 版本号（如 V5.7.4）
- 打印机型号
- 连接方式（蓝牙/USB/WiFi/串口）
- Android 设备型号及系统版本
- 问题复现步骤及相关日志（Logcat）

---

> **文档结束**
