今天咱们来聊聊 HarmonyOS 里一个超实用的功能 —— 扫码 API。不管你是想开发扫码支付、商品溯源还是名片识别应用,这套 API 都能帮你轻松实现。我会用最通俗的话来讲,每个知识点都配上代码示例,保证你看完就能上手写!

一、扫码 API 能干啥?先搞懂核心能力

扫码 API 就像一个 "码类全能识别器",主要帮我们解决三个核心问题:

  1. 支持多种码类型识别:从常见的 QR 码到专业的 PDF417 码,几乎涵盖所有主流码制
  2. 提供统一扫码界面:无需自己开发扫码 UI,直接调用系统原生界面
  3. 错误处理与结果解析:自动处理扫码过程中的异常,并返回结构化识别结果

举个例子:当你在购物 App 里点击 "扫码查价格" 按钮时,弹出的扫码界面和背后的码识别逻辑,就是通过这个 API 实现的。HarmonyOS 把复杂的扫码底层逻辑封装成了简单的接口,咱们直接调用就行!

重点总结:扫码 API = 多码制支持 + 系统扫码界面 + 结果解析处理

// 先导入核心模块,这是所有操作的基础
import { scanCore } from '@kit.ScanKit';

二、核心概念:码类型与错误码

在开始写代码前,得先认识两个关键的 "工具零件":

1. ScanType:码类型的 "百科全书"

这是个枚举,定义了 HarmonyOS 扫码 API 支持的所有码类型,就像一本码制百科全书:

  • QR_CODE(11):最常见的二维码,支持网址、文本、联系方式等多种内容
  • EAN13_CODE(8):商品条码,常见于超市商品包装上的 13 位条码
  • PDF417_CODE(10):工业常用的堆叠式二维码,能存储大量信息
  • ONE_D_CODE(100):条形码合集,包含 CODABAR、CODE39 等一维码
  • TWO_D_CODE(101):二维码合集,包含 AZTEC、DATA MATRIX 等
  • ALL(1001):识别所有支持的码类型,适合不知道具体码制的场景

重点代码:查询支持的码类型并显示

import { hilog } from '@kit.PerformanceAnalysisKit';

// 定义日志标签和域
const APP_TAG = "ScanDemo";
const DOMAIN = 0x0001;

@Entry
@Component
struct ScanTypeDemo {
  @State supportedTypes: string = '';

  build() {
    Column() {
      Text("支持的码类型列表")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      Button("查询所有支持的码类型")
        .width(240)
        .height(50)
        .onClick(() => {
          this.querySupportedTypes();
        })
        .margin(10)
      
      Text(this.supportedTypes)
        .fontSize(14)
        .margin(10)
        .width('90%')
        .textAlign(TextAlign.Start)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }

  // 查询系统支持的码类型并格式化显示
  querySupportedTypes() {
    let typeList = '';
    for (let i in scanCore.ScanType) {
      // 跳过非数值属性
      if (isNaN(Number(i))) continue;
      
      const typeName = i;
      const typeValue = scanCore.ScanType[typeName];
      // 过滤掉不可用于生成码图的类型
      if ([typeValue.FORMAT_UNKNOWN, typeValue.ONE_D_CODE, 
           typeValue.TWO_D_CODE, typeValue.ALL].includes(typeValue)) {
        continue;
      }
      
      typeList += `\n- ${typeName} (值: ${typeValue})`;
    }
    
    this.supportedTypes = `以下是可识别的码类型:${typeList}`;
    hilog.info(DOMAIN, APP_TAG, `支持的码类型: ${typeList}`);
  }
}

2. ScanErrorCode:错误处理的 "诊断工具"

当扫码操作出错时,会返回这个枚举里的错误码,帮我们快速定位问题:

  • INTERNAL_ERROR(1000500001):内部错误,可能是扫码服务异常
  • SCAN_SERVICE_CANCELED(1000500002):用户取消扫码,比如点击了取消按钮

三、功能详解:从基础扫码到高级应用

1. 基础扫码功能:调用系统界面识别指定码类型

最常用的扫码方式,调用系统默认界面,支持指定要识别的码类型。

重点代码:扫码识别 QR 码并处理结果

import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct BasicScanDemo {
  @State scanResult: string = '点击扫码';
  private context: common.UIAbilityContext | null = null;

  aboutToAppear() {
    // 获取应用上下文
    this.context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  }

  build() {
    Column() {
      Text("基础扫码功能")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      Button(this.scanResult)
        .width(240)
        .height(50)
        .onClick(async () => {
          this.scanResult = "扫码中...";
          await this.startScan(scanCore.ScanType.QR_CODE);
        })
        .margin(10)
      
      Text(`扫码结果: ${this.scanResult}`)
        .fontSize(14)
        .margin(10)
        .width('90%')
        .textAlign(TextAlign.Start)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }

  // 启动扫码并指定识别QR码
  async startScan(scanType: scanCore.ScanType) {
    try {
      // 调用扫码API,Promise方式
      const result = await scanCore.scanBarcode({
        scanType: scanType,
        context: this.context!
      });
      
      if (result) {
        this.scanResult = `识别成功: ${result}`;
        hilog.info(DOMAIN, APP_TAG, `QR码内容: ${result}`);
      } else {
        this.scanResult = "未识别到有效内容";
      }
    } catch (error) {
      // 错误处理
      this.handleScanError(error, "扫描QR码");
    }
  }

  // 统一处理扫码错误
  handleScanError(error: any, operation: string) {
    let errorMsg = "操作失败";
    if (error.code) {
      switch (error.code) {
        case scanCore.ScanErrorCode.INTERNAL_ERROR:
          errorMsg = "扫码服务内部错误,请重试";
          break;
        case scanCore.ScanErrorCode.SCAN_SERVICE_CANCELED:
          errorMsg = "你取消了扫码";
          break;
        default:
          errorMsg = `错误码: ${error.code}, 信息: ${error.message}`;
      }
    } else {
      errorMsg = `未知错误: ${error.message}`;
    }
    
    this.scanResult = errorMsg;
    hilog.error(DOMAIN, APP_TAG, `${operation}失败: ${errorMsg}`);
  }
}

2. 多码类型识别:同时支持多种码制扫描

有时候我们不知道要扫的是什么码,这时候可以使用 ALL 类型,让系统自动识别所有支持的码制。

重点代码:扫描所有类型码并处理结果

@Entry
@Component
struct MultiTypeScanDemo {
  @State scanResult: string = '点击扫描所有类型码';

  build() {
    Column() {
      Text("多类型码扫描")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      Button(this.scanResult)
        .width(240)
        .height(50)
        .onClick(async () => {
          this.scanResult = "正在扫描...";
          await this.scanAllTypes();
        })
        .margin(10)
      
      Text(`扫描结果: ${this.scanResult}`)
        .fontSize(14)
        .margin(10)
        .width('90%')
        .textAlign(TextAlign.Start)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }

  // 扫描所有支持的码类型
  async scanAllTypes() {
    try {
      // 使用ALL类型,识别所有码制
      const result = await scanCore.scanBarcode({
        scanType: scanCore.ScanType.ALL,
        // 可以添加更多配置项,比如界面标题
        title: "全能扫码器"
      });
      
      if (result) {
        this.scanResult = `识别成功: ${result}`;
        hilog.info(DOMAIN, APP_TAG, `识别内容: ${result}`);
      } else {
        this.scanResult = "未识别到有效码";
      }
    } catch (error) {
      this.handleScanError(error, "扫描所有类型码");
    }
  }

  // 错误处理
  handleScanError(error: any, operation: string) {
    // 与前面示例相同的错误处理逻辑,此处省略重复代码
    // 可参考BasicScanDemo中的handleScanError方法
  }
}

3. 自定义扫码配置:设置扫码界面标题和码类型

可以自定义扫码界面的标题,并指定要识别的码类型,提升用户体验。

重点代码:自定义扫码界面并指定识别 EAN13 商品条码

@Entry
@Component
struct CustomScanDemo {
  @State scanResult: string = '点击扫描商品条码';

  build() {
    Column() {
      Text("商品条码扫描")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      Button(this.scanResult)
        .width(240)
        .height(50)
        .onClick(async () => {
          this.scanResult = "正在扫描商品...";
          await this.scanProductCode();
        })
        .margin(10)
      
      Text(`扫描结果: ${this.scanResult}`)
        .fontSize(14)
        .margin(10)
        .width('90%')
        .textAlign(TextAlign.Start)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }

  // 扫描EAN13商品条码
  async scanProductCode() {
    try {
      const result = await scanCore.scanBarcode({
        scanType: scanCore.ScanType.EAN13_CODE,
        context: this.context!,
        // 自定义扫码界面标题
        title: "商品条码扫描",
        // 可以添加提示文字
        prompt: "将商品条码对准扫描框"
      });
      
      if (result) {
        this.scanResult = `商品条码: ${result}`;
        hilog.info(DOMAIN, APP_TAG, `EAN13码: ${result}`);
        // 这里可以添加查询商品信息的逻辑
      } else {
        this.scanResult = "未识别到商品条码";
      }
    } catch (error) {
      this.handleScanError(error, "扫描商品条码");
    }
  }

  // 错误处理
  handleScanError(error: any, operation: string) {
    // 与前面示例相同的错误处理逻辑
    let errorMsg = "扫码失败";
    if (error.code === scanCore.ScanErrorCode.SCAN_SERVICE_CANCELED) {
      errorMsg = "你取消了扫描";
    } else if (error.code === scanCore.ScanErrorCode.INTERNAL_ERROR) {
      errorMsg = "扫码服务异常,请稍后再试";
    } else {
      errorMsg = `错误: ${error.message}`;
    }
    
    this.scanResult = errorMsg;
    hilog.error(DOMAIN, APP_TAG, `${operation}${errorMsg}`);
  }
}

四、实战案例:打造一个全能扫码工具

现在把前面的知识点串起来,看看怎么实现一个完整的扫码工具,包含以下功能:

  1. 支持选择不同码类型进行扫描
  2. 显示扫码历史记录
  3. 处理不同码类型的识别结果
  4. 友好的错误提示和用户引导

完整代码实现:

import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct ScanTool {
  @State selectedType: scanCore.ScanType = scanCore.ScanType.ALL;
  @State scanResult: string = '点击开始扫描';
  @State historyList: Array<string> = [];
  private context: common.UIAbilityContext | null = null;

  aboutToAppear() {
    this.context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  }

  build() {
    Column() {
      Text("HarmonyOS全能扫码工具")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin(10)
      
      // 码类型选择下拉框
      DropdownMenu({
        options: this.getScanTypeOptions(),
        selectedOption: this.getSelectedTypeName(),
        onSelect: (index: number) => {
          this.selectedType = this.getScanTypeValues()[index];
        }
      })
      .width(280)
      .margin(10)
      
      Button(this.scanResult)
        .width(240)
        .height(60)
        .fontSize(16)
        .onClick(async () => {
          this.scanResult = "正在扫描...";
          await this.startScan();
        })
        .margin(10)
      
      Text("扫描结果:")
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 10, bottom: 5 })
      
      Text(this.getResultDisplay())
        .fontSize(16)
        .margin(10)
        .width('90%')
        .textAlign(TextAlign.Start)
        .minHeight(80)
        .backgroundColor(this.scanResult.includes("成功") ? '#E8F5E9' : '#FFF0F0')
        .padding(10)
      
      Text("扫码历史:")
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 20, bottom: 5 })
      
      // 历史记录列表
      if (this.historyList.length > 0) {
        List({ space: 5 }) {
          ForEach(this.historyList.reverse(), (item, index) => {
            ListItem() {
              Text(item)
                .fontSize(14)
                .padding(8)
                .backgroundColor(index % 2 === 0 ? '#F5F5F5' : '#E8F5E9')
            }
          })
        }
        .width('90%')
        .marginBottom(10)
      } else {
        Text("暂无扫码历史")
          .fontSize(14)
          .color('#9E9E9E')
          .margin(10)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding(10)
  }

  // 获取码类型选项列表
  getScanTypeOptions(): Array<string> {
    const options = [];
    for (let i in scanCore.ScanType) {
      if (isNaN(Number(i))) continue;
      
      const typeName = i;
      const typeValue = scanCore.ScanType[typeName];
      // 过滤掉不可用于扫描的类型
      if ([typeValue.FORMAT_UNKNOWN, typeValue.ONE_D_CODE, 
           typeValue.TWO_D_CODE].includes(typeValue)) {
        continue;
      }
      
      options.push(typeName);
    }
    return options;
  }

  // 获取码类型值列表
  getScanTypeValues(): Array<scanCore.ScanType> {
    const values = [];
    for (let i in scanCore.ScanType) {
      if (isNaN(Number(i))) continue;
      
      const typeValue = scanCore.ScanType[i];
      if ([typeValue.FORMAT_UNKNOWN, typeValue.ONE_D_CODE, 
           typeValue.TWO_D_CODE].includes(typeValue)) {
        continue;
      }
      
      values.push(typeValue);
    }
    return values;
  }

  // 获取选中的类型名称
  getSelectedTypeName(): string {
    for (let i in scanCore.ScanType) {
      if (scanCore.ScanType[i] === this.selectedType) {
        return i;
      }
    }
    return "ALL";
  }

  // 启动扫码
  async startScan() {
    try {
      const result = await scanCore.scanBarcode({
        scanType: this.selectedType,
        context: this.context!,
        title: `扫描${this.getSelectedTypeName()}`,
        prompt: "将码对准扫描框"
      });
      
      this.processScanResult(result);
    } catch (error) {
      this.handleScanError(error, "扫码");
    }
  }

  // 处理扫码结果
  processScanResult(result: string | null) {
    if (result) {
      this.scanResult = "扫描成功";
      const typeName = this.getSelectedTypeName();
      const displayText = `${typeName}内容: ${result}`;
      
      // 添加到历史记录
      this.historyList.unshift(displayText);
      if (this.historyList.length > 10) {
        this.historyList.pop(); // 最多保存10条记录
      }
      
      hilog.info(DOMAIN, APP_TAG, displayText);
    } else {
      this.scanResult = "未识别到有效码";
      hilog.info(DOMAIN, APP_TAG, "扫码失败: 未识别到内容");
    }
  }

  // 获取结果显示文本
  getResultDisplay(): string {
    if (this.scanResult.includes("成功")) {
      const latestResult = this.historyList[0];
      return latestResult || "扫描成功,暂无结果";
    } else {
      return this.scanResult;
    }
  }

  // 错误处理
  handleScanError(error: any, operation: string) {
    let errorMsg = "操作失败";
    if (error.code === scanCore.ScanErrorCode.SCAN_SERVICE_CANCELED) {
      errorMsg = "你取消了" + operation;
    } else if (error.code === scanCore.ScanErrorCode.INTERNAL_ERROR) {
      errorMsg = operation + "服务异常,请稍后再试";
    } else {
      errorMsg = operation + "错误: " + (error.message || "未知错误");
    }
    
    this.scanResult = errorMsg;
    hilog.error(DOMAIN, APP_TAG, errorMsg);
  }
}

五、错误处理:遇到问题怎么办?

在使用扫码 API 时,可能会遇到各种错误,咱们来看看常见问题及解决办法:

1. 内部错误(1000500001)

  • 可能原因:扫码服务内部异常,可能是系统问题或临时故障
  • 解决办法:提示用户 "扫码服务异常,请稍后再试",并在代码中添加重试逻辑

2. 用户取消(1000500002)

  • 可能原因:用户在扫码界面点击了取消按钮
  • 解决办法:不需要特殊处理,提示用户 "你取消了扫码" 即可

3. 未识别到码

  • 可能原因:扫描框内没有有效码,或码制不在指定的 ScanType 范围内
  • 解决办法:提示用户 "未识别到有效码,请调整角度重试",并检查 ScanType 设置

4. 权限问题

  • 可能原因:应用未获取相机权限
  • 解决办法:在 config.json 中添加相机权限,并在代码中请求权限

统一错误处理代码示例:

// 封装错误处理函数
function handleScanError(error: any, operation: string) {
  console.error(`[${operation}] 失败`, error);
  let userMsg = "操作失败";
  
  if (error.code === scanCore.ScanErrorCode.INTERNAL_ERROR) {
    userMsg = "扫码服务异常,请稍后再试";
  } else if (error.code === scanCore.ScanErrorCode.SCAN_SERVICE_CANCELED) {
    userMsg = "你取消了" + operation;
  } else if (error.message.includes("permission")) {
    userMsg = "请授予相机权限以使用扫码功能";
  } else {
    userMsg = "扫码失败,请重试";
  }
  
  // 在UI上显示错误信息
  this.scanResult = userMsg;
  console.log(userMsg);
}

六、总结:扫码 API 的三大优势与使用建议

1. 三大核心优势

  • 多码制支持:几乎涵盖所有主流一维码和二维码,满足各种业务场景
  • 简单易用:一行代码启动系统扫码界面,无需自行开发复杂扫码逻辑
  • 灵活配置:可自定义扫码类型、界面标题和提示语,提升用户体验

2. 开发建议

  • 明确码类型:如果知道具体码制,尽量指定 ScanType,提高识别效率
  • 处理取消场景:用户取消扫码是正常操作,无需过度处理
  • 添加权限检查:在调用扫码前检查相机权限,避免运行时错误
  • 优化用户引导:在扫码界面添加提示文字,引导用户正确操作

通过 HarmonyOS 扫码 API,我们可以轻松在应用中实现强大的扫码功能,从简单的 QR 码识别到专业的商品条码扫描,都能轻松搞定。记住这些常用接口和错误处理方法,结合实际场景灵活运用,就能开发出实用的扫码功能啦!快去试试吧,有问题咱们可以一起讨论~

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐