跳轉到主要內容

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 環境保持一致。