local_auth 平台接口深度解析:local_auth_platform_interface 的架构、实现与扩展指南
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
本篇文章围绕 Flutter 官方local_auth生态中的local_auth_platform_interface包展开,它是local_auth插件的公共平台接口层,负责统一各端(Android、iOS、macOS、Windows、Web 等)本地生物识别与设备认证能力的抽象契约。读完本文,你将掌握平台接口(Platform Interface)的职责边界、LocalAuthPlatform的完整 API 面、如何为local_auth编写一个新的平台实现(自定义实现类并注册为默认实例),以及该包对破坏性变更的特殊约束与背后的设计哲学。
local_auth_platform_interface的官方说明非常精炼,核心只有三件事:它是local_auth插件的公共接口、新平台实现需要继承LocalAuthPlatform并注册实例、该包强烈偏好非破坏性变更。本文将以此为骨架,结合仓库源码(接口定义、方法通道实现、类型定义、测试用例)逐层展开,让读者既能按图索骥完成自定义实现,也能理解这套抽象背后的工程取舍。
一、包定位:为什么需要一层“平台接口”
在 Flutter 插件生态中,local_auth是一个典型的 federated plugin(联邦式插件):最上层是面向开发者的 API 包local_auth,中间是公共平台接口local_auth_platform_interface,底层则是各平台的具体实现包,如local_auth_android、local_auth_darwin、local_auth_windows等。
local_auth_platform_interface处于承上启下的位置。它定义了平台实现必须遵守的契约,从而保证:
- 每个平台实现(无论 Android、iOS 还是 Windows)与
local_auth插件本体调用的是同一套接口,不会出现各端行为分裂; - 开发者在不改动上层 API 的前提下,可以替换或新增平台实现;
- 上层代码与底层原生代码解耦,便于长期演进与社区扩展。
该包本身不包含任何原生代码,pubspec.yaml中仅声明了flutterSDK 与plugin_platform_interface: ^2.1.7两个依赖,dev_dependencies 也只有flutter_test与mockito。它对外暴露的全部能力集中在lib/目录下:
lib/local_auth_platform_interface.dart—— 核心抽象类LocalAuthPlatform(入口文件);lib/default_method_channel_platform.dart—— 默认的 MethodChannel 实现DefaultLocalAuthPlatform;lib/types/—— 配套类型定义(auth_options.dart、auth_messages.dart、auth_exception.dart、biometric_type.dart),由types/types.dart统一导出。
二、核心接口:LocalAuthPlatform 的五个方法
LocalAuthPlatform继承自plugin_platform_interface包中的PlatformInterface,是所有平台实现的基类。接口定义于 local_auth_platform_interface.dart,共包含 5 个异步方法:
1.authenticate(...)—— 发起认证
Future<bool> authenticate({ required String localizedReason, required Iterable<AuthMessages> authMessages, AuthenticationOptions options = const AuthenticationOptions(), }) async { throw UnimplementedError('authenticate() has not been implemented.'); }这是最核心的方法,用于触发设备上的生物识别或设备认证(PIN、图案、密码)。其语义约定非常明确:
- 返回
true:用户认证成功; - 返回
false:认证流程完成但用户挑战失败,且没有进一步后果; - 抛出
LocalAuthException:其他所有结果,包括错误、用户取消、锁定(lockout)。这也意味着某些平台上实现可能永远不会返回false(例如唯一标准结果是成功、取消、或多次重试后的临时锁定)。
参数说明:
localizedReason:展示给用户的认证提示文案(如 "Please scan your finger to access MyApp."),不允许为空,接口的默认实现中通过assert(localizedReason.isNotEmpty)强制校验;authMessages:可选的对话框文案定制集合,用于替换各平台的默认提示语;options:AuthenticationOptions配置对象,控制认证行为细节(见下文第三节)。
2.deviceSupportsBiometrics()—— 设备是否具备生物识别能力
Future<bool> deviceSupportsBiometrics() async { throw UnimplementedError('canCheckBiometrics() has not been implemented.'); }返回true表示设备具备生物识别检测能力,即使当前没有任何已录入的生物特征也返回true。注意抛出的 UnimplementedError 文案沿用了旧名canCheckBiometrics(),属于历史命名遗留。
3.getEnrolledBiometrics()—— 获取已录入的生物特征列表
Future<List<BiometricType>> getEnrolledBiometrics() async { throw UnimplementedError('getAvailableBiometrics() has not been implemented.'); }返回设备上已录入的生物特征类型列表,可能的值包括BiometricType.face、BiometricType.fingerprint、BiometricType.iris(尚未实现)、BiometricType.strong、BiometricType.weak。
4.isDeviceSupported()—— 设备是否支持认证
Future<bool> isDeviceSupported() async { throw UnimplementedError('isDeviceSupported() has not been implemented.'); }返回true表示设备具备生物识别能力,或可以回退到设备凭据(锁屏密码等)认证。常用于在认证前做能力探测。
5.stopAuthentication()—— 取消进行中的认证
Future<bool> stopAuthentication() async { throw UnimplementedError('stopAuthentication() has not been implemented.'); }取消当前进行中的认证流程。返回true表示成功取消;返回false表示当前没有进行中的认证,或取消过程中出错。
继承规则:用extends而非implements
接口注释与 README 都特别强调:平台实现应当extends LocalAuthPlatform,而不是implements LocalAuthPlatform。
原因在于接口演进策略——本包将来新增方法时不视为破坏性变更。使用extends的子类会自动获得基类的默认实现(即各方法默认抛出UnimplementedError),不会被新方法打破;而使用implements的实现类一旦接口新增方法就会立即编译失败,被迫跟进。这是 Flutter 官方平台接口生态的通用约定,直接体现在源码注释中(local_auth_platform_interface.dart)。
三、认证配置:AuthenticationOptions 的四个开关
AuthenticationOptions定义于 auth_options.dart,是@immutable的不可变配置类,构造时所有参数均有默认值:
const AuthenticationOptions({ this.useErrorDialogs = true, this.stickyAuth = false, this.sensitiveTransaction = true, this.biometricOnly = false, });| 参数 | 默认值 | 含义 |
|---|---|---|
useErrorDialogs | true | 系统是否尝试处理用户可修复的问题(如设备有指纹传感器但未录入指纹时,引导用户去设置页添加)。不可修复的问题(如设备根本没有生物传感器)仍会抛出PlatformException。注意:源码注释明确指出,该参数仅为兼容local_auth2.x 而保留,面向 3.x 及以后的实现应忽略它(该值恒为false) |
stickyAuth | false | 认证进行中 App 进入后台时,出于安全原因认证必须停止。若为true,App 恢复前台时自动继续认证;若为false(默认),App 一暂停就立刻向 Dart 端返回失败消息,由客户端决定是否重新发起认证 |
sensitiveTransaction | true | 是否启用平台特定的安全防护。例如在 Android 上,人脸解锁成功后系统会弹出确认对话框,确保用户确实有意解锁设备 |
biometricOnly | false | 是否禁止使用非生物识别的本地认证方式(如 PIN、密码、图案)。置为true时只能通过生物特征认证 |
这些字段直接参与了默认实现的参数透传(见第五节)。类的相等性判断(==与hashCode)基于全部四个字段实现,因此相同配置的实例可以安全地进行相等性比较。
四、配套类型:BiometricType、AuthMessages 与 LocalAuthException
BiometricType:生物特征类型枚举
定义于 biometric_type.dart,取值如下:
face:人脸认证;fingerprint:指纹认证;iris:虹膜认证(尚未实现);strong:平台 API 认定的强生物特征。例如 Android 上对应 Class 3;weak:平台 API 认定的弱生物特征。例如 Android 上对应 Class 2。
源码注释特别说明:不同平台的报告粒度不同,有的平台只上报具体类型(face/fingerprint),有的只上报 strong/weak 这样的强度分类。
AuthMessages:平台文案抽象基类
定义于 auth_messages.dart,是一个抽象类,用于承载平台相关的提示字符串:
abstract class AuthMessages { const AuthMessages(); Map<String, String> get args; }自定义平台实现可以派生自己的文案类,通过args以键值对形式返回所有平台专属文案;authenticate()接收一个Iterable<AuthMessages>,默认实现会将每个消息对象的args合并进方法通道参数。
LocalAuthException 与 LocalAuthExceptionCode:统一错误契约
定义于 auth_exception.dart。LocalAuthException实现Exception,携带code(LocalAuthExceptionCode枚举)、可读的description与附加的details。
LocalAuthExceptionCode完整枚举了认证失败场景,是各端实现统一的错误分类依据:
| 枚举值 | 触发场景 |
|---|---|
authInProgress | 已有认证正在进行且未完成,前一个 Future 未结束时不能开启新认证 |
uiUnavailable | 需要展示 UI 但无法展示(如 Android 上没有可用 Activity 时尝试弹窗) |
userCanceled | 用户主动取消操作 |
timeout | 设备相关的超时导致操作取消 |
systemCanceled | 系统事件导致取消(如认证期间 App 被切到后台) |
noCredentialsSet | 设备未配置任何凭据(无已录入生物特征,也无 PIN/密码/图案等回退机制) |
noBiometricsEnrolled | 设备支持生物识别,但未录入任何生物特征 |
noBiometricHardware | 设备没有生物识别硬件 |
biometricHardwareTemporarilyUnavailable | 硬件存在(或可存在)但当前不可用(如被其他应用占用、蓝牙生物硬件未配对) |
temporaryLockout | 认证被临时锁定(如失败次数过多),应稍后重试 |
biometricLockout | 生物认证被锁定,直到其他认证成功;不强制要求生物认证的应用应回退到非生物认证重试 |
userRequestedFallback | 用户在系统 UI 中选择使用回退认证方式 |
deviceError | 设备级错误,description应包含更多细节 |
unknownError | 未知或意外错误,description应包含更多细节 |
枚举注释还特别提醒:未来向该枚举新增值不视为破坏性变更,因此客户端不应假设能穷举所有错误码,务必在switch中提供default或其他兜底分支。
五、默认实现:DefaultLocalAuthPlatform 与方法通道
虽然local_auth生态中实际的平台实现(如local_auth_android、local_auth_darwin)并不会使用它,但DefaultLocalAuthPlatform依然承载着重要的兼容性职责。
定义于 default_method_channel_platform.dart,它使用固定的方法通道名:
const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth');源码注释说明:该默认实现仅用于向后兼容——在插件联邦化(federated plugin)之前,客户端若依赖了方法通道内部细节,这一实现可保证其行为不被破坏。从实现可以看到各方法的实际透传逻辑:
authenticate:将localizedReason及AuthenticationOptions的四个字段(useErrorDialogs、stickyAuth、sensitiveTransaction、biometricOnly)打包进参数 Map,再合并所有AuthMessages.args,最后调用通道方法authenticate;getEnrolledBiometrics:调用通道方法getAvailableBiometrics,把字符串结果映射为BiometricType枚举,并处理'undefined'哨兵值(表示硬件支持生物识别但无已录入项,映射时跳过);deviceSupportsBiometrics:同样调用getAvailableBiometrics,只要返回列表非空(包括仅含'undefined'哨兵值)即视为设备支持生物识别;isDeviceSupported:调用通道方法isDeviceSupported;stopAuthentication:调用通道方法stopAuthentication。
上述行为均有对应的单元测试覆盖,见 default_method_channel_platform_test.dart:
DefaultLocalAuthPlatform is registered as the default platform implementation验证LocalAuthPlatform.instance默认为DefaultLocalAuthPlatform;getAvailableBiometrics验证方法名与参数;deviceSupportsBiometrics handles special sentinal value验证'undefined'哨兵值场景(返回['undefined']时deviceSupportsBiometrics应为true);- 其余测试覆盖
isDeviceSupported、stopAuthentication、authenticate等方法的通道调用(通过TestDefaultBinaryMessengerBinding的 mock handler 记录MethodCall断言)。
六、实战:如何实现一个新的平台实现
这是 README 的核心使用场景,官方给出的步骤非常简洁,结合源码可以拆解为三步:
1. 继承 LocalAuthPlatform
自定义实现类必须extends LocalAuthPlatform(再次强调:不要implements,原因见第二节),并为需要支持的方法提供平台专属行为。最少也要实现authenticate,其余方法可暂时依赖基类的UnimplementedError默认实现,或按平台能力逐项覆盖:
import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; class MyLocalAuthPlatform extends LocalAuthPlatform { @override Future<bool> authenticate({ required String localizedReason, required Iterable<AuthMessages> authMessages, AuthenticationOptions options = const AuthenticationOptions(), }) async { // 在此调用平台原生认证能力,并按统一契约返回 true/false 或抛出 LocalAuthException。 return true; } @override Future<bool> deviceSupportsBiometrics() async { // 探测平台生物识别能力。 return true; } @override Future<List<BiometricType>> getEnrolledBiometrics() async { // 返回设备已录入的生物特征。 return <BiometricType>[BiometricType.fingerprint]; } @override Future<bool> isDeviceSupported() async { return true; } @override Future<bool> stopAuthentication() async { return true; } }2. 注册为默认实例
在插件注册流程中,将LocalAuthPlatform.instance设为自定义实例:
LocalAuthPlatform.instance = MyLocalAuthPlatform();这一步背后有plugin_platform_interface的 token 校验机制保障安全:LocalAuthPlatform构造时通过super(token: _token)持有私有 token,而instance的 setter 会调用PlatformInterface.verifyToken(instance, _token)校验(见 local_auth_platform_interface.dart)。只有真正继承自LocalAuthPlatform的实例才能通过校验并被设置为默认实例,从而防止第三方绕过继承关系随意替换平台实现。
3. 在 pubspec 中正确声明
与local_auth生态中的现有实现(local_auth_android、local_auth_darwin、local_auth_windows等)保持一致,自定义平台包应:
- 在依赖中声明
local_auth_platform_interface(版本约束参考当前 pubspec.yaml 中声明的plugin_platform_interface: ^2.1.7,本包版本为1.1.0,要求 Dart SDK^3.10.0、Flutter>=3.38.0); - 遵循各平台的插件注册约定(Android 在
MainActivity或PluginRegistry中注册,iOS/macOS 在插件类register(with:)中注册,Windows 在RegisterWithRegistrar中注册等)。
七、设计约束:为什么强烈偏好非破坏性变更
README 的 "Note on breaking changes" 部分是理解本包演进策略的关键:强烈倾向于非破坏性变更(如向接口新增方法),即使这意味着接口不够“干净”,也不要做破坏性变更。
这一策略并非偶然,而是 Flutter 平台接口生态的通用约定,pubspec.yaml 中的注释也引用了同一决策文档。背后的工程理由可以总结为三点:
- 下游实现数量不可控:
local_auth的平台实现分布在大量终端设备厂商与社区项目中,一次破坏性变更会强制所有第三方实现同步修改,成本极高; - 默认实现兜底:由于所有实现都
extends基类,新增方法会自动获得基类提供的UnimplementedError默认行为。旧实现不会被编译期打断,只是新方法暂时不可用,演进是渐进的、平滑的; - 契约稳定性优先:对认证这类安全敏感的能力,接口的稳定性直接关系到上层业务的可预期性。以
useErrorDialogs参数为例,即使该参数已因 2.x 兼容而“名存实亡”(面向 3.x 的实现恒为false),它依然被保留在AuthenticationOptions中,而非直接删除——这正是“宁可保留不干净接口,也不破坏兼容”的直观体现。
同理,LocalAuthExceptionCode新增枚举值也不视为破坏性变更,因此客户端代码必须为错误码处理保留兜底分支(如default:),这在第五节已强调。
八、在仓库中进一步探索
如果你希望深入理解这套平台接口在实际插件中的落地方式,可以在当前仓库中按以下路径继续阅读:
- 上层 API 与示例:local_auth/README.md 与 local_auth/example 展示了开发者的实际调用方式;
- 各平台实现:
packages/local_auth/local_auth_android/、packages/local_auth/local_auth_darwin/、packages/local_auth/local_auth_windows/分别实现了本接口的 Android、Apple 系与 Windows 版本,它们都遵循“继承LocalAuthPlatform+ 注册LocalAuthPlatform.instance”的同一模式; - 基础机制:plugin_platform_interface/lib/plugin_platform_interface.dart 提供了
PlatformInterface与 token 校验的底层实现; - 测试范式:default_method_channel_platform_test.dart 演示了如何用
TestDefaultBinaryMessengerBindingmock 方法通道来验证实现行为。
总结
local_auth_platform_interface虽然是一个体积小巧的接口包,却承载了 Flutter 联邦式插件中最关键的设计思想:用稳定的抽象契约隔离上层 API 与底层原生实现,用“继承优先、默认实现兜底、非破坏性演进”的策略保障生态长期可维护。无论是只想使用local_auth的开发者在遇到错误时对照LocalAuthExceptionCode定位问题,还是想要为特定平台定制认证能力的开发者动手实现LocalAuthPlatform,理解本包都是必不可少的一步。
【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考