Android 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
sdk-bom 平臺依賴加上單獨的
sdk-ui-width-modern 座標。現在不再需要 —
sdk-activity 已聚合全部內容。
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?
|
內部錯誤 |
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 環境保持一致。