跳转到主要内容

Android SDK 集成指南

文档对应版本。Android SDK 1.0.11。本页依据更新日期为 2026 年 9 月 11 日的 WIDTH 集成指南。部署前请确认所选 SDK 构建版本及其目标环境。

概述

Width Identity SDK 为 Android 应用提供即插即用的身份验证流程。支持 KYC 和 KYB 工作流,包括证件采集、活体检测与审核。SDK 在独立的 Activity 中运行;你只需添加一个依赖、调用一个入口,SDK 即返回单一结果。

系统要求

要求 最低版本
Android SDK API 26 (Android 8.0)+
compileSdk 36 — SDK 发布产物的 AAR 元数据里写着 minCompileSdk=36,构建工具会强制校验,配得比它低会直接构建失败
Kotlin 1.9+
Gradle / AGP 8.0+
Java 源代码及目标兼容版本 11
宿主应用 Jetpack Compose 或传统 View 体系均可(SDK 在自己的 Activity 中运行)

安装

步骤 1:添加 Maven 仓库

在 settings.gradle.kts 中。Width 仓库提供 SDK 及其由 Width 托管的传递依赖(com.width.risksec、com.width.camera);其余依赖(AndroidX / Kotlin / Ktor / CameraX)从 Google + Maven Central 解析。

SDK 内置的证件采集引擎由各自的厂商托管,不在 Width 的 Maven 上,所以它们的仓库也要加上 — 构建期必须有,哪怕某个采集商在你的流程里用不到。下面两个厂商仓库块请原样照抄。

dependencyResolutionManagement {
    repositories {
        maven { url = uri("https://maven.width.com/repository/sdk-releases/") }
        google()
        mavenCentral()

        // Document-capture provider repository (bundled transitively; required to resolve)
        maven {
            url = uri("https://cdn.veriff.me/android/")
            content { includeGroup("com.veriff") }
        }
        // Document-reader provider repository (bundled transitively; required to resolve)
        maven {
            url = uri("https://maven-master.regulaforensics.com/RegulaDocumentReader/")
            content {
                includeGroup("com.regula.documentreader")
                includeGroup("com.regula.documentreader.core")
                includeGroup("com.regula.common")
            }
        }
    }
}

步骤 2:添加依赖

在应用级 build.gradle.kts 中。单个坐标即可传递引入相机、活体及配套库 — 无需 BOM,也无需额外坐标。

dependencies {
    implementation("com.width.identity:sdk-activity:1.0.11")
}
minSdk 要求:SDK 要求 minSdk 26。

步骤 3:必需的构建配置

下面两项配置加在你自己的 App 模块上,不是 SDK 侧的事。不加,构建直接失败 — 两项都不需要改你的任何源码。

必须开启 core library desugaring(把新版 Java 标准库的能力降级到旧 Android 版本上可用的编译期处理)。内置的 AWS Rekognition 活体引擎带的 Amplify AAR 在元数据里声明了 requires core library desugaring,AGP 会在 App 侧强制校验(checkReleaseAarMetadata / checkDebugAarMetadata),与你的 minSdk 是多少无关。在 App 模块加上:

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}

AGP 9 上必须加 android.uniquePackageNames=false。 AWS 活体引擎传递引入的两个产物(com.google.ai.edge.litert:litert-support 与 litert-support-api,都是 1.4.1)声明了同一个库命名空间 org.tensorflow.lite.support — 这是上游的打包缺陷。AGP 9 的清单合并会校验命名空间唯一性,冲突就以 Validation failed, exiting 中断构建。在项目的 gradle.properties 里加上:

# gradle.properties
android.uniquePackageNames=false
不再需要 BOM 与 UI 坐标 早期集成需要 sdk-bom 平台依赖加上单独的 sdk-ui-width-modern 坐标。现在不再需要 — sdk-activity 已聚合全部内容。
SDK Token 位置变更 SDK token 不再通过 AndroidManifest.xml 的 <meta-data> 节点 / strings.xml 注册。现在改为在运行时作为 CredentialsConfig 中的 sdkToken 字段传入(见快速开始)。

快速开始

SDK 提供两种等价的入口风格 — 按你的调用点选择即可。两者驱动完全相同的验证界面,并返回单一结果。

直接输入(ActivityResultContract)

import com.width.identity.activity.CredentialsConfig
import com.width.identity.activity.CustomerConfig
import com.width.identity.activity.ThemeConfig
import com.width.identity.activity.UILanguage
import com.width.identity.activity.Verification
import com.width.identity.activity.VerificationInput
import com.width.identity.activity.VerificationResult

class MainActivity : ComponentActivity() {

    private val launcher =
        registerForActivityResult(Verification()) { result ->
            when (result) {
                is VerificationResult.Completed ->   // .APPROVED / .REJECTED / .REVIEW
                    Log.i(TAG, "done: ${result.executionId} ${result.decision}")
                is VerificationResult.Waiting ->
                    Log.i(TAG, "waiting: ${result.executionId}")
                is VerificationResult.Dismissed ->
                    Log.i(TAG, "dismissed: ${result.executionId ?: "(no execution)"}")
                is VerificationResult.Cancelled ->
                    Log.i(TAG, "cancelled: ${result.executionId}")
                is VerificationResult.Failed ->
                    Log.w(TAG, "failed: ${result.code} ${result.message}")
            }
        }

    private fun startVerification() {
        val input = VerificationInput(
            credentials = CredentialsConfig(
                domain   = "YOUR_DOMAIN",
                sdkToken = "YOUR_SDK_TOKEN",
            ),
            customer = CustomerConfig(
                // Both customerId and email are required and must be non-blank.
                customerId = "user-123",
                email      = "user@example.com",
            ),
            theme = ThemeConfig(themeMode = "SYSTEM"),   // optional: "SYSTEM" / "LIGHT" / "DARK"
            eventType = "KYC_EVENT",   // optional: "KYC_EVENT" / "KYB_EVENT" to skip in-SDK selection; omit to let the user pick
            language = UILanguage.ZH,  // optional: UILanguage.EN (default) / .ZH; omit = English (does not follow device locale)
        )
        launcher.launch(input)
    }
}

Builder 入口(Compose)

链式构造器(fluent builder)把启动所需的输入串成一条链装配起来 — 依次给出 access token、凭据、申请人,再按需挂上 handler,最后 build 并启动,返回单一的 WIDTHCompletionResult。先用 WIDTHVerification 契约在 composition 中注册 launcher,再在调用点构建并启动。

import androidx.activity.compose.rememberLauncherForActivityResult
import com.width.identity.activity.WIDTHCompletionResult
import com.width.identity.activity.WIDTHDayNightMode
import com.width.identity.activity.WIDTHMobileSDK
import com.width.identity.activity.WIDTHVerification

@Composable
fun VerifyButton() {
    val launcher = rememberLauncherForActivityResult(WIDTHVerification()) { result ->
        when (result) {
            is WIDTHCompletionResult.SuccessTermination ->   // see status table
                Log.i(TAG, "done: ${result.status} ${result.executionId}")
            is WIDTHCompletionResult.AbnormalTermination ->
                Log.w(TAG, "failed: ${result.exception.code} ${result.exception.message}")
        }
    }

    Button(onClick = {
        WIDTHMobileSDK.Builder()
            .withAccessToken("YOUR_SDK_TOKEN")                 // RiskSec sdkToken (encodes appId/apiKey)
            .withCredentials(domain = "YOUR_DOMAIN")           // only domain is client-supplied
            .withApplicant(customerId = "user-123", email = "user@example.com")
            .withEventType("KYC_EVENT")                        // optional; "KYC_EVENT"/"KYB_EVENT" to skip in-SDK selection
            .withForcedDayNightMode(WIDTHDayNightMode.NIGHT)   // optional; omit to follow the system
            .withHandlers(onEvent = myLogger)                  // optional; see Event logging
            .withLauncher(launcher)
            .build()
            .launch()
    }) { Text("Verify") }
}

这只是另一种入口写法,不是另一条启动路径:builder 在底层装配出原生的 VerificationInput,跑的仍是同一套流程。withAccessToken 传的就是 sdkToken,租户的 appId / apiKey 已经编码在这个 token 里、由 SDK 在设备上解密;客户端只需要通过 withCredentials 传 domain,通过 withApplicant 传申请人身份。

withEventType 可选("KYC_EVENT" / "KYB_EVENT")— 传了就直接进入对应流程、跳过 SDK 内的选择页;不传则由用户自己选。

VerificationInput(...) 在下列情况抛出 IllegalArgumentException:domain / sdkToken 为空,customerId / email 为空,或 eventType 传了非空值但不是 "KYC_EVENT" / "KYB_EVENT";build() 则在必填的 builder 字段没设时抛出。请在调用点捕获。

事件日志(可选)

注册一个 SdkEventLogger 即可将 SDK 生命周期事件(INFO / WARN / ERROR)接入你自己的管道(Crashlytics、Timber、DataDog 等)。回调触发前已脱敏已知的 PII(个人身份信息)。SdkEventLogger 是函数式接口,任何需要它的位置都可以传入 lambda。

import com.width.identity.activity.SdkLogLevel
import com.width.identity.activity.SdkLogging

SdkLogging.setLogger { level, tag, message, throwable ->
    when (level) {
        SdkLogLevel.INFO  -> Log.i(tag, message)
        SdkLogLevel.WARN  -> Log.w(tag, message, throwable)
        SdkLogLevel.ERROR -> Log.e(tag, message, throwable)
    }
}

该 logger 是全局且可选的;调用 SdkLogging.setLogger(null) 即可停止转发。也可以通过 withHandlers(onEvent = ...) 在 builder 上就地注册同一个 logger。

配置参考

VerificationInput

字段 类型 必填 描述
credentials CredentialsConfig 是 SDK 认证配置
customer CustomerConfig 是 客户身份信息
theme ThemeConfig 否 UI 主题定制
eventType String 否 传 "KYC_EVENT" / "KYB_EVENT" 可跳过 SDK 内的流程选择页;不传则由用户自己选
language UILanguage 否 界面语言:UILanguage.EN(默认)/ .ZH

CredentialsConfig

字段 类型 必填 描述
domain String 是 多租户路由的租户标识符
sdkToken String 是 SDK token(以往通过 manifest <meta-data> 注册)

CustomerConfig

字段 类型 必填 描述
customerId String 是 唯一客户标识符(不可为空)
email String 是 客户邮箱地址(不可为空)
两个字段现在都必填 customerId 和 email 两者都必须提供且不可为空。任一为空,构造时即抛出 IllegalArgumentException。(此规则比以往更严格 — 以往只要求二者至少提供其一。)

公开 API

com.width.identity.activity 是唯一对客户开放的包 — 其余都是内部实现,在你的应用里引用会直接编译报错。

符号 用途
Verification ActivityResultContract 入口
VerificationInput / CredentialsConfig / CustomerConfig 启动参数
VerificationResult / VerificationDecision 直接输入入口的结果
WIDTHMobileSDK.Builder + WIDTHVerification Builder 入口(Compose)
WIDTHCompletionResult / WIDTHStatus / WIDTHException Builder 入口的结果类型
ThemeConfig / WIDTHDayNightMode 可选的主题 / 日夜模式覆盖
UILanguage 可选的界面语言(EN 为默认值 / ZH)
SdkLogging / SdkEventLogger / SdkLogLevel 可选的事件日志回调

UILanguage

取值 说明
EN 英文(默认)
ZH 简体中文

不传 language 就是英文 — SDK 不会跟随设备语言。第三方采集页面另有限制:Veriff 只有在设备联网、且租户的集成开启了中文时才显示中文,否则仍是英文;Regula 只支持简体中文。

结果处理

直接输入的 VerificationResult

VerificationResult 是一个密封类,包含五种变体:

结果 字段 描述
Completed executionId, decision 服务端返回决定:APPROVED / REJECTED / REVIEW(在带外进行人工审核)
Waiting executionId 等待服务端通知最终决定
Dismissed executionId? 用户退出但打算稍后返回(X → 保留进度,或在工作流开始前退出)。服务端执行被保留,下次启动即可恢复;若工作流尚未开始,executionId 为 null。可恢复。
Cancelled executionId 用户主动放弃验证(X → 放弃)。SDK 已等待服务端的 workflow.execution.cancel,执行已销毁,无法恢复。
Failed code, message, executionId? 内部错误
Dismissed 与 Cancelled 的区别 旧版只有一个 Cancelled 现在被拆分为两种:Dismissed 表示用户暂停、可恢复;Cancelled 表示用户放弃、服务端执行已销毁(不可恢复)。

Builder 的 WIDTHCompletionResult

变体 何时
SuccessTermination(status, executionId?) 流程正常结束;最终状态由 status 携带
AbnormalTermination(exception, executionId?) 技术性失败;exception.code / exception.message 携带细节

status 是 WIDTHStatus:Approved、FinallyRejected、Pending(人工审核)、Incomplete(可恢复 / 已 dismissed)、Cancelled(已销毁)。exception 是 WIDTHException:Init、Network、Api、Permission、Workflow、Unknown 之一 — 每个都暴露 code 与 message。

主题定制

ThemeMode

ThemeMode 行为
"SYSTEM" 跟随系统设置
"LIGHT" 强制浅色主题
"DARK" 强制深色主题

通过 ThemeConfig 设置模式,或在 Builder 入口覆盖日间/夜间。两者省略时均跟随系统设置。

ThemeConfig(themeMode = "SYSTEM")   // "SYSTEM" / "LIGHT" / "DARK"

// On the Builder entry point — omit to follow the system:
.withForcedDayNightMode(WIDTHDayNightMode.NIGHT)

恢复与生命周期

用户选择 行为 结果 可恢复?
暂停并退出(保留进度) 服务端执行被保留 Dismissed 是
结束验证(放弃) 服务端取消,执行已销毁 Cancelled 否

恢复验证时,使用相同的 customerId 调用 launcher.launch(...),SDK 会自动检测进行中的工作流。

权限

SDK 与其内置的采集引擎声明了所需的全部权限,并会自动合并进你的应用 — 你无需声明或申请它们:

<!-- WIDTH identity SDK core -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Added transitively by the capture / liveness engine -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- Added transitively by the document-reader engine -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
权限 类型 用途 / 来源
CAMERA 运行时 证件采集、活体检测(SDK 核心)
INTERNET 安装时 API 通信(SDK 核心)
ACCESS_NETWORK_STATE 安装时 网络连接检测(SDK 核心)
RECORD_AUDIO 运行时 由采集 / 活体引擎合并引入
NFC 安装时 由采集 / 活体引擎合并引入
WAKE_LOCK 安装时 由采集 / 活体引擎合并引入
VIBRATE 安装时 由采集 / 活体引擎合并引入
FLASHLIGHT 安装时 由证件阅读引擎合并引入
运行时(危险级)权限是 CAMERA 与 RECORD_AUDIO;由 SDK 及其采集引擎在各自的 Activity 内部申请,你的应用不会弹窗。上述每一项权限都会出现在应用的权限列表里,必须如实反映到你的 Google Play Data Safety(数据安全)申报中。SDK 不需要任何存储 / 媒体权限 — 选择证件图片走系统照片选择器。

ProGuard / R8:SDK 内置了混淆规则,无需额外配置。

故障排查

问题 解决方案
无法解析 com.width.identity:... 确保 Width Maven 仓库配置在 settings.gradle.kts 中(而非 build.gradle.kts),并使用单一坐标 com.width.identity:sdk-activity:1.0.11(不再用 BOM)。如果解析不到的是 com.veriff 或 com.regula.*,说明步骤 1 里那两个厂商仓库没加。
启动时抛出 IllegalArgumentException 检查 domain / sdkToken 以及 customerId 和 email 是否都非空。还要检查 eventType:一旦传了值,就必须正好是 "KYC_EVENT" 或 "KYB_EVENT"。
摄像头无法使用 检查合并后的 manifest 是否存在权限冲突;SDK 会从其库 manifest 自动合并 CAMERA。
主题颜色未生效 检查十六进制值(无需 # 前缀),令牌名称区分大小写
构建在 checkReleaseAarMetadata / checkDebugAarMetadata 上失败,提示 requires core library desugaring 在 App 模块开启 core library desugaring 并加上 desugar_jdk_libs 依赖 — 见「安装」的步骤 3。这项校验与你的 minSdk 是多少无关。
同一道 AAR 元数据校验失败,原因是模块的 compileSdk 低于 SDK 的要求 SDK 的几个 AAR 在元数据里写着 minCompileSdk=36,构建工具会强制校验,模块拿比它低的版本编译就会中断构建。在 App 模块把 compileSdk 设为 36 — 见「系统要求」。这与 minSdk 是两回事,后者仍是 26。
AGP 9 上清单合并失败,报 Validation failed, exiting 在项目的 gradle.properties 里加上 android.uniquePackageNames=false — 见「安装」的步骤 3。
升级:把 build.gradle.kts 里的版本号改成新版本,下次 sync 时 Gradle 会从 maven.width.com 拉取新产物。

集成说明

请按上文获取软件包访问权限并完成安装。SDK 构建版本及所用凭证须与目标 WIDTH 环境保持一致。