23、人脸识别与后台服务:前台服务中使用生物识别、Service与Activity通信、后台认证限制
说实话,人脸识别做到后台服务里,是很多开发者容易踩坑的地方。
我刚开始做这个功能时,天真地以为在Service里直接调BiometricManager就行。结果呢?要么闪退,要么认证结果根本回不来。后来翻源码才发现,生物识别API的设计初衷就是跟Activity生命周期绑定的。
这一章,我就把我在项目中趟过的坑,一个一个说清楚。
为什么不能在后台Service直接调生物识别?
先看一个典型的错误写法:
// 错误示例:在Service中直接调用
public class AuthService extends Service {
public void startAuth() {
BiometricPrompt prompt = new BiometricPrompt(this, executor, callback);
prompt.authenticate(new BiometricPrompt.PromptInfo.Builder()
.setTitle("验证")
.build());
}
}
这段代码跑起来,大概率会报错:BiometricPrompt requires a valid Activity context。
为什么?因为系统需要Activity来展示那个认证对话框。Service没有UI,系统没法弹窗。
前台服务 + 生物识别的正确姿势
我个人的习惯是:用前台Service保活,但把认证逻辑委托给Activity。
说白了,Service负责后台任务,Activity负责弹认证框。两者通过Binder通信。
第一步:创建前台Service
public class FaceAuthService extends Service {
private final IBinder binder = new LocalBinder();
private AuthCallback callback;
public class LocalBinder extends Binder {
FaceAuthService getService() {
return FaceAuthService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
// 启动前台通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("人脸认证服务运行中")
.setSmallIcon(R.drawable.ic_face)
.build();
startForeground(1, notification);
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void setCallback(AuthCallback callback) {
this.callback = callback;
}
public void onAuthResult(boolean success) {
if (callback != null) {
callback.onResult(success);
}
}
public interface AuthCallback {
void onResult(boolean success);
}
}
第二步:Activity绑定Service并触发认证
public class MainActivity extends AppCompatActivity {
private FaceAuthService authService;
private boolean bound = false;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
FaceAuthService.LocalBinder binder = (FaceAuthService.LocalBinder) service;
authService = binder.getService();
bound = true;
// 设置回调
authService.setCallback(result -> {
runOnUiThread(() -> {
if (result) {
// 认证成功,通知Service继续后台任务
} else {
// 认证失败
}
});
});
// 触发认证
startBiometricAuth();
}
@Override
public void onServiceDisconnected(ComponentName name) {
bound = false;
}
};
private void startBiometricAuth() {
BiometricPrompt prompt = new BiometricPrompt(this,
ContextCompat.getMainExecutor(this),
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
// 把结果传给Service
authService.onAuthResult(true);
}
@Override
public void onAuthenticationFailed() {
authService.onAuthResult(false);
}
});
prompt.authenticate(new BiometricPrompt.PromptInfo.Builder()
.setTitle("验证身份")
.setSubtitle("继续操作需要验证身份")
.setNegativeButtonText("取消")
.build());
}
}
Service与Activity通信的三种方式
嗯,这里我整理了一张图,把常用的通信方式画出来了:
后台认证限制:Android 10+ 的硬性要求
从Android 10开始,Google对后台生物识别做了严格限制。说白了,就是不让App在用户不知情的情况下偷偷扫脸。
| Android版本 | 后台认证限制 | 我的建议 |
|---|---|---|
| Android 9 (API 28) | 无明确限制 | 但建议提前适配前台Service |
| Android 10 (API 29) | 后台App无法启动生物识别 | 必须使用前台Service + Activity组合 |
| Android 11+ (API 30+) | 加强限制,后台完全禁止 | 强制要求用户可见的UI |
避坑指南:我踩过的三个大坑
坑一:认证成功后Service被系统杀死
我曾经遇到一个情况:人脸认证成功了,但Service因为内存不足被系统回收了。结果用户白扫了一次脸。
解决方案:在onStartCommand里返回START_STICKY,并且把Service优先级调高。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 返回START_STICKY,让系统在杀死后尝试重启
return START_STICKY;
}
坑二:认证回调在后台线程,直接更新UI崩溃
BiometricPrompt的回调默认在后台线程。如果你直接在里面更新TextView,妥妥的崩溃。
记得用runOnUiThread或者Handler切到主线程。
坑三:多个Activity绑定同一个Service
如果你的App有多个界面都需要人脸认证,别让每个Activity都单独绑定Service。我建议用一个单例的ServiceManager来管理绑定状态,避免重复创建。
总结一下
人脸识别跟后台服务配合,说白了就三句话:
- 认证必须在Activity里做,Service别掺和
- 用Binder或Broadcast把认证结果传给Service
- 前台Service保活,但别忘了通知栏提示
我个人习惯是把认证结果存在Service的成员变量里,Activity通过Binder随时查询。这样即使Activity重建了,认证状态也不会丢。
嗯,这一章的内容就到这儿。代码不多,但都是实战中提炼出来的经验。你照着这个思路做,应该能少走不少弯路。