Android10.0 人脸解锁流程分析

人脸解锁概述

人脸解锁即用户通过注视设备的正面方便地解锁手机或平板。Android 10 为支持人脸解锁的设备在人脸认证期间添加了一个新的可以安全处理相机帧、保持隐私与安全的人脸认证栈的支持,也为安全合规地启用集成交易的应用(网上银行或其他服务)提供了一种容易实现的方式。
Android 原生的人脸认证栈在 Android 10 是一种新的实现,与 Android P 不一样了。新增 IBiometricsFace.hal ,IBiometricsFaceClientCallback.hal 和 types.hal 这些接口。例如:我这边的源码都是 extends 以上接口,进行了些扩展后再实现的。

底层 Face HIDL简单认识

为了实现 Face HIDL,必须在供应商 (vendor) 指定的库 (library) 里实现 IBiometricsFace.hal 的所有方法。接下来我们就来看看 hardware/interfaces/biometrics/face/1.0 目录下的源代码。
hardware/interfaces/biometrics/face/1.0/IBiometricsFace.hal

package android.hardware.biometrics.face@1.0;
import IBiometricsFaceClientCallback;
/*** 用于人脸认证的 HAL 接口*/
interface IBiometricsFace {/*** 设置当前的客户端回调*/@callflow(next={"setActiveUser"})@entrysetCallback(IBiometricsFaceClientCallback clientCallback)generates (OptionalUint64 result);/*** 设置所有随后的 HAL 操作作用于上面的活跃用户*/@callflow(next={"authenticate", "generateChallenge", "enumerate", "remove"})setActiveUser(int32_t userId, string storePath) generates (Status status);/*** 生成随机数,用于 token 校验*/@callflow(next={"enroll", "revokeChallenge", "setFeature"})generateChallenge(uint32_t challengeTimeoutSec)generates (OptionalUint64 result);/*** 录入一张用户的人脸*/@callflow(next={"cancel", "enroll", "revokeChallenge", "remove"})enroll(vec<uint8_t> hat, uint32_t timeoutSec, vec<Feature> disabledFeatures)generates (Status status);/*** 撤销随机数*/@callflow(next={"authenticate", "setActiveUser", "enumerate", "remove"})revokeChallenge() generates (Status status);setFeature(Feature feature, bool enabled, vec<uint8_t> hat, uint32_t faceId)generates(Status status);getFeature(Feature feature, uint32_t faceId) generates (OptionalBool result);/*** 返回和当前人脸集关联的标识符 (ID),认证者 ID*/@callflow(next={"authenticate"})getAuthenticatorId() generates (OptionalUint64 result);/*** 取消当前的录入、认证、删除人脸或枚举人脸的操作*/@callflow(next={"authenticate", "enroll", "enumerate", "remove","setActiveUser"})cancel() generates (Status status);/*** 枚举正在使用系统的用户的所有人脸模板*/@callflow(next={"remove", "enroll", "authenticate", "setActiveUser"})enumerate() generates (Status status);/*** 删除正在使用系统的用户的一个或所有人脸模板*/@callflow(next={"enumerate", "authenticate", "cancel", "getAuthenticatorId","setActiveUser"})remove(uint32_t faceId) generates (Status status);/*** 认证当前用户是否登录系统的用户*/@callflow(next={"cancel", "generateChallenge", "remove"})authenticate(uint64_t operationId) generates (Status status);userActivity() generates (Status status);/*** 为当前用户重置禁用状态*/resetLockout(vec<uint8_t> hat) generates (Status status);
};

hardware/interfaces/biometrics/face/1.0/IBiometricsFaceClientCallback.hal

package android.hardware.biometrics.face@1.0;
/*** 这个回调接口被客户端用来接收人脸 HAL 的(状态)更新*/
interface IBiometricsFaceClientCallback {/*** 当录入的步骤完成时被回调*/oneway onEnrollResult(uint64_t deviceId, uint32_t faceId, int32_t userId,uint32_t remaining);/*** 当一张人脸被成功认证时被回调*/oneway onAuthenticated(uint64_t deviceId, uint32_t faceId, int32_t userId,vec<uint8_t> token);/*** 当底层获得一张人脸时被回调*/oneway onAcquired(uint64_t deviceId, int32_t userId,FaceAcquiredInfo acquiredInfo, int32_t vendorCode);/*** 当错误发生时被回调*/oneway onError(uint64_t deviceId, int32_t userId, FaceError error,int32_t vendorCode);/*** 当人脸模板被删除时被回调*/oneway onRemoved(uint64_t deviceId, vec<uint32_t> removed, int32_t userId);/*** 枚举所有人脸模板的回调*/oneway onEnumerate(uint64_t deviceId, vec<uint32_t> faceIds,int32_t userId);/*** 当禁用状态改变时被回调*/oneway onLockoutChanged(uint64_t duration);
};

供应商(主要是手机厂商)需要实现上述接口的方法并集成人脸识别算法,完成录入和认证等的底层实现。
hardware/interfaces/biometrics/face/1.0/types.hal

package android.hardware.biometrics.face@1.0;
/** 在这里 setActiveUser 不会被调用,所有错误消息会返回这个用户 ID*/
enum UserHandle : int32_t {NONE = -1
};
/*** 状态码*/
enum Status : uint32_t {/*** 方法被成功调用*/OK = 0,/*** 方法调用的参数之一无效*/ILLEGAL_ARGUMENT = 1,/*** 人脸 HAL 不支持这个操作*/OPERATION_NOT_SUPPORTED = 2,/***  HAL 遭遇内部错误,不能完成请求*/INTERNAL_ERROR = 3,/*** 没有录入人脸*/NOT_ENROLLED = 4
};
enum Feature : uint32_t {/*** 要求注视*/REQUIRE_ATTENTION = 1,/*** 要求录入时姿势多样(有变化) */REQUIRE_DIVERSITY = 2
};
/*** onError 回调的人脸错误消息*/
enum FaceError : int32_t {/*** 不能被解析的硬件错误*/HW_UNAVAILABLE = 1,/*** 不能处理当前操作*/UNABLE_TO_PROCESS = 2,/*** 超时*/TIMEOUT = 3,/*** 没有足够的存储空间去完成当前的操作*/NO_SPACE = 4,/*** 被取消*/CANCELED = 5,/*** 无法删除*/UNABLE_TO_REMOVE = 6,/*** 30s 禁用*/LOCKOUT = 7,/*** 用来开启供应商指定的错误消息*/VENDOR = 8,/*** 禁用直到使用主身份认证*/LOCKOUT_PERMANENT = 9
};
/*** 向客户端反馈获取人脸的消息(质量),以便用户做出相应的改变*/
enum FaceAcquiredInfo : int32_t {GOOD = 0,/*** 无效人脸*/INSUFFICIENT = 1,/*** 人脸太亮*/TOO_BRIGHT = 2,/*** 人脸太暗*/TOO_DARK = 3,/*** 人脸太近*/TOO_CLOSE = 4,/*** 人脸太远*/TOO_FAR = 5,/*** 人脸太高,只有下半部分*/FACE_TOO_HIGH = 6,/*** 人脸太低*/FACE_TOO_LOW = 7,/*** 人脸偏右*/FACE_TOO_RIGHT = 8,/*** 人脸偏左*/FACE_TOO_LEFT = 9,/*** 凝视不佳*/POOR_GAZE = 10,/*** 未检测到人脸*/NOT_DETECTED = 11,/*** 检测到运动过多*/TOO_MUCH_MOTION = 12,/*** 重新校正*/RECALIBRATE = 13,/*** 和前一帧差异太大*/TOO_DIFFERENT = 14,/*** 和前一帧太相似*/TOO_SIMILAR = 15,/*** 摇射角度太大,直面相机角度为 0*/PAN_TOO_EXTREME = 16,/*** 倾斜角度太大*/TILT_TOO_EXTREME = 17,/*** 侧倾角幅度太大*/ROLL_TOO_EXTREME = 18,/*** 人脸被遮挡*/FACE_OBSCURED = 19,START = 20,/*** 传感器(摄像头)脏了*/SENSOR_DIRTY = 21,/*** 用于开启供应商指定的获取人脸的消息*/VENDOR = 22
};
/*** 结果*/
struct OptionalUint64 {/*** 返回的状态*/Status status;/*** 只意味着状态是 OK 的*/uint64_t value;
};
/*** 结果*/
struct OptionalBool {/*** 返回的状态*/Status status;/*** 只意味着状态是 OK 的*/bool value;
};

人脸识别调用流程(注册监听、捕获人脸、比对)

人脸解锁的入口在Keyguard中,但息屏的处理是从PowerManager开始,最终到锁屏的核心类KeyguardViewMediator,息屏处理的大致流程如下:

前面几步就跳过,直接从PhoneWindowManager开始分析。灭屏之后会调用PhoneWindowManager的startedGoingToSleep方法:
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    // Called on the PowerManager's Notifier thread.@Overridepublic void startedGoingToSleep(int why) {if (DEBUG_WAKEUP) {Slog.i(TAG, "Started going to sleep... (why="+ WindowManagerPolicyConstants.offReasonToString(why) + ")");}mGoingToSleep = true;mRequestedOrGoingToSleep = true;if (mKeyguardDelegate != null) {mKeyguardDelegate.onStartedGoingToSleep(why);}}

在该方法中又调用了KeyguardServiceDelegate类的onStartedGoingToSleep方法。
KeyguardServiceDelegate#onStartedGoingToSleep →KeyguardServiceWrapper#onStartedGoingToSleep → KeyguardService#onStartedGoingToSleep → KeyguardViewMediator#onStartedGoingToSleep,最终会调用到KeyguardViewMediator锁屏核心类。
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    public void onStartedGoingToSleep(int why) {if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");synchronized (this) {mDeviceInteractive = false;mGoingToSleep = true;// 这位置的代码作用具体不知,但放在前面可以解决息屏后又立马使用指纹解锁时:出现1.2s内没反应的问题。mUpdateMonitor.dispatchKeyguardGoingAway(false);// Lock immediately based on setting if secure (user has a pin/pattern/password).// This also "locks" the device when not secure to provide easy access to the// camera while preventing unwanted input.int currentUser = KeyguardUpdateMonitor.getCurrentUser();final boolean lockImmediately =mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)|| !mLockPatternUtils.isSecure(currentUser);long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());mLockLater = false;// 省略部分代码......//判断是否需要播放锁屏音if (mPendingLock) {playSounds(true);}}// 使得KeyguardUpdateMonitor可以监听到GoingToSleep// KeyguardUpdateMonitor 是Keyguard更新监视器mUpdateMonitor.dispatchStartedGoingToSleep(why);//通知开始息屏notifyStartedGoingToSleep();}

这里主要分析的是屏幕自己息屏,则重点关注mUpdateMonitor.dispatchStartedGoingToSleep(why)。
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    // 等待屏幕超时息屏,handler会发送 MSG_STARTED_GOING_TO_SLEEPpublic void dispatchStartedGoingToSleep(int why) {mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_GOING_TO_SLEEP, why, 0));}// 注意:如果说按电源键息屏,handler会发送 MSG_STARTED_WAKING_UPpublic void dispatchStartedWakingUp() {synchronized (this) {mDeviceInteractive = true;}mHandler.sendEmptyMessage(MSG_STARTED_WAKING_UP);}

屏幕超时息屏堆栈:

12-10 09:43:41.437  1468  1468 D updateFaceListeningState: java.lang.Throwable
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.updateFaceListeningState(KeyguardUpdateMonitor.java:2128)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.updateBiometricListeningState(KeyguardUpdateMonitor.java:2053)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.setKeyguardGoingAway(KeyguardUpdateMonitor.java:575)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.handleKeyguardGoingAway(KeyguardUpdateMonitor.java:1727)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.access$5000(KeyguardUpdateMonitor.java:143)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor$16.handleMessage(KeyguardUpdateMonitor.java:1872)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.os.Handler.dispatchMessage(Handler.java:106)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.os.Looper.loop(Looper.java:223)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.app.ActivityThread.main(ActivityThread.java:7945)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at java.lang.reflect.Method.invoke(Native Method)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
12-10 09:43:41.437  1468  1468 V KeyguardUpdateMonitor:         at com.android.keyguard.KeyguardUpdateMonitor.updateFaceListeningState(KeyguardUpdateMonitor.java:2129)

电源键息屏堆栈:


12-10 09:43:41.437  1468  1468 D updateFaceListeningState: java.lang.Throwable
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.updateFaceListeningState(KeyguardUpdateMonitor.java:2128)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.updateBiometricListeningState(KeyguardUpdateMonitor.java:2053)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.setKeyguardGoingAway(KeyguardUpdateMonitor.java:575)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.handleKeyguardGoingAway(KeyguardUpdateMonitor.java:1727)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor.access$5000(KeyguardUpdateMonitor.java:143)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.keyguard.KeyguardUpdateMonitor$16.handleMessage(KeyguardUpdateMonitor.java:1872)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.os.Handler.dispatchMessage(Handler.java:106)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.os.Looper.loop(Looper.java:223)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at android.app.ActivityThread.main(ActivityThread.java:7945)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at java.lang.reflect.Method.invoke(Native Method)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
12-10 09:43:41.437  1468  1468 D updateFaceListeningState:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
12-10 09:43:41.437  1468  1468 V KeyguardUpdateMonitor:         at com.android.keyguard.KeyguardUpdateMonitor.updateFaceListeningState(KeyguardUpdateMonitor.java:2129)

这里通过handler发送消息让:handleStartedGoingToSleep处理
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    protected void handleStartedGoingToSleep(int arg1) {checkIsHandlerThread();mLockIconPressed = false;clearBiometricRecognized();for (int i = 0; i < mCallbacks.size(); i++) {KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();if (cb != null) {cb.onStartedGoingToSleep(arg1);}}mGoingToSleep = true;// 更新生物识别(指纹、人脸)updateBiometricListeningState();}private void updateBiometricListeningState() {updateFingerprintListeningState();updateFaceListeningState();}

updateFaceListeningState(),更新人脸状态。
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    private void updateFaceListeningState() {// 如果此消息存在,我们不应再次进行身份验证if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {return;}mHandler.removeCallbacks(mRetryFaceAuthentication);boolean shouldListenForFace = shouldListenForFace();if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {stopListeningForFace();} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING &&  shouldListenForFace) {// 在这里开始监听人脸/*重点关注*/startListeningForFace();}}

startListeningForFace()
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    private void startListeningForFace() {if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);return;}if (DEBUG) Log.v(TAG, "startListeningForFace()");int userId = getCurrentUser();if (isUnlockWithFacePossible(userId)) {if (mFaceCancelSignal != null) {mFaceCancelSignal.cancel();}mFaceCancelSignal = new CancellationSignal();/*重点关注*/mFaceManager.authenticate(null, mFaceCancelSignal, 0,mFaceAuthenticationCallback, null, userId);setFaceRunningState(BIOMETRIC_STATE_RUNNING);}}

FaceManager#authenticate()
frameworks/base/core/java/android/hardware/face/FaceManager.java

    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler,int userId) {if (callback == null) {throw new IllegalArgumentException("Must supply an authentication callback");}if(mPendingFaceAuth != null) {Log.w(TAG, "authentication too frequent");}if(mAuthenticationCallback != null) {mPendingFaceAuth = new PendingFaceAuth(crypto, cancel, flags, callback, handler, userId);Log.w(TAG, "pengding face auth");return;} else {/*重点关注*/authenticateInternel(crypto, cancel, flags, callback, handler, userId);}}void authenticateInternel(CryptoObject crypto, CancellationSignal cancel,int flags, AuthenticationCallback callback, Handler handler, int userId) {if (cancel != null) {if (cancel.isCanceled()) {Log.w(TAG, "authentication already canceled");return;} else {cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));}}//mSurface = null;//onFaceidStarted();if (mService != null) {try {useHandler(handler);mAuthenticationCallback = callback;mCryptoObject = crypto;long sessionId = crypto != null ? crypto.getOpId() : 0;Trace.beginSection("FaceManager#authenticate");/*重点关注*/    // 进行人脸认证  mService.authenticate(mToken, sessionId, userId, mServiceReceiver,flags, mContext.getOpPackageName());/* UNISOC: Modify for bug1374210 {@ */if (callback != null) {callback.onAuthenticationStarted();}/* @} */} catch (RemoteException e) {// 省略部分代码......} finally {Trace.endSection();}}}

FaceService#authenticate()
frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

        @Override // Binder callpublic void authenticate(final IBinder token, final long opId, int userId,final IFaceServiceReceiver receiver, final int flags,final String opPackageName) {checkPermission(USE_BIOMETRIC_INTERNAL);updateActiveGroup(userId, opPackageName);final boolean restricted = isRestricted();final AuthenticationClientImpl client = new FaceAuthClient(getContext(),mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,0 /* cookie */, false /* requireConfirmation */);/*重点关注*/authenticateInternal(client, opId, opPackageName);}

BiometricServiceBase#authenticateInternal()

protected void authenticateInternal(AuthenticationClientImpl client, long opId,String opPackageName) {final int callingUid = Binder.getCallingUid();final int callingPid = Binder.getCallingPid();final int callingUserId = UserHandle.getCallingUserId();authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId);}protected void authenticateInternal(AuthenticationClientImpl client, long opId,String opPackageName, int callingUid, int callingPid, int callingUserId) {if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,callingUserId)) {if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);return;}mHandler.post(() -> {mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0);// Get performance stats object for this user.HashMap<Integer, PerformanceStats> pmap= (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;PerformanceStats stats = pmap.get(mCurrentUserId);if (stats == null) {stats = new PerformanceStats();pmap.put(mCurrentUserId, stats);}mPerformanceStats = stats;mIsCrypto = (opId != 0);/*重点关注*/startAuthentication(client, opPackageName);});}private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");int lockoutMode = getLockoutMode();// getLockoutMode() 判断是否锁定,会返回一个 int 值if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;if (!client.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */)) {Slog.w(getTag(), "Cannot send permanent lockout message to client");}return;}/*重点关注*/startClient(client, true /* initiatedByClient */);//这里将AuthenticationClient传递进去}    private void startClient(ClientMonitor newClient, boolean initiatedByClient) {ClientMonitor currentClient = mCurrentClient;if (currentClient != null) {if (DEBUG) Slog.v(getTag(), "request stop current client " +currentClient.getOwnerString());if (currentClient instanceof InternalEnumerateClient|| currentClient instanceof InternalRemovalClient) {// 省略部分代码......} else {currentClient.stop(initiatedByClient);mHandler.removeCallbacks(mResetClientState);mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);}mPendingClient = newClient;} else if (newClient != null) {// 省略部分代码......// We are not a BiometricPrompt client, start the client immediatelymCurrentClient = newClient;/*重点关注*/startCurrentClient(mCurrentClient.getCookie());//这里继续将AuthenticationClient传递进去}}protected void startCurrentClient(int cookie) {// 省略部分代码....../*重点关注*///这里调用的是AuthenticationClient的start方法int status = mCurrentClient.start();if (status == 0) {notifyClientActiveCallbacks(true);}// ... ...}

mCurrentClient是ClientMonitor的对象,而AuthenticationClient继承了ClientMonitor类;
AuthenticationClient#start()
frameworks/base/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java

// 开始验证public int start() {mStarted = true;onStart();try {/*重点关注*/// 获取 DaemonWrappe 对象开始鉴权,这里如果鉴权完成会回调注册的 ClientMonito r的 onAuthenticated 接口//到这一步 DaemonWrappe 对象 进入等待捕获人脸信息,摄像头会给到DaemonWrappe对象人脸信息。// 这里对调用到 DaemonWrapper 在 FaceService 里有实现,在那里会直接调用到 HAL 层final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());if (result != 0) {Slog.w(getLogTag(), "startAuthentication failed, result=" + result);mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,0 /* vendorCode */);return result;}if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");} catch (RemoteException e) {Slog.e(getLogTag(), "startAuthentication failed", e);return ERROR_ESRCH;}return 0; // success}

start方法会调用faced,调用底层的人脸库,底层库返回结果后会调用onAuthenticated来反馈结果给receiver,在往上层反馈。
补充:IExtBiometricsFace.hal 这个接口在 ExtBiometricsFace.cpp中实现。
frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

    @Overridepublic void onStart() {super.onStart();// 在初始化后会建立和HAL层的通信,即连接到 FaceService,//并通过getFaceDaemon()拿到用于通信的 IExtBiometricsFace对象(binder)publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon),TAG + ".onStart");}

屏幕解锁(结果回调、移除锁)

底层库回调onAuthenticated堆栈:

12-10 16:33:49.998  1017  1017 D longzhiye  : longzhiye:FaceService.java ServiceListenerImpl onAuthenticationSucceeded()
12-10 16:33:49.998  1017  1017 D longzhiye  : java.lang.Throwable
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.face.FaceService$ServiceListenerImpl.onAuthenticationSucceeded(FaceService.java:918)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.AuthenticationClient.onAuthenticated(AuthenticationClient.java:235)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.face.FaceService$FaceAuthClient.onAuthenticated(FaceService.java:297)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.BiometricServiceBase.handleAuthenticated(BiometricServiceBase.java:729)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.face.FaceService.access$11801(FaceService.java:110)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.face.FaceService$1.lambda$onAuthenticated$2$FaceService$1(FaceService.java:1040)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.biometrics.face.-$$Lambda$FaceService$1$GcU4ZG1fdDLhKvSxuMwfPargEnI.run(Unknown Source:8)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at android.os.Handler.handleCallback(Handler.java:938)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at android.os.Handler.dispatchMessage(Handler.java:99)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at android.os.Looper.loop(Looper.java:223)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.SystemServer.run(SystemServer.java:647)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.server.SystemServer.main(SystemServer.java:431)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at java.lang.reflect.Method.invoke(Native Method)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
12-10 16:33:49.998  1017  1017 D longzhiye  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:925)

根据前面的讲的 底层 Face HIDL 可以知道 IExtBiometricsFaceClientCallback 是回调人脸识别结果的。onAuthenticated()是当一张人脸被成功认证时被回调。
frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

    /*** Receives callbacks from the HAL.*/private IExtBiometricsFaceClientCallback mDaemonCallback =new IExtBiometricsFaceClientCallback.Stub() {// 省略部分代码 ......@Overridepublic void onAuthenticated(final long deviceId, final int faceId, final int userId,ArrayList<Byte> token) {mHandler.post(() -> {final Face face = new Face("", faceId, deviceId);final boolean authenticated = faceId != 0;/*重点在这里*/FaceService.super.handleAuthenticated(authenticated, face, token);});}// 省略部分代码 ......};

通过上面 FaceService.super.handleAuthenticated(authenticated, face, token) 的调用。将会调用到:
BiometricServiceBase#handleAuthenticated()

// BiometricServiceBase.javaprotected void handleAuthenticated(boolean authenticated,BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {Log.d("longzhiye","longzhiye:AuthenticationClient.java ----------------2 ");ClientMonitor client = mCurrentClient;// 重点在后半句判断,通过前面的分析可以知道 client 其实是 FaceAuthClient 的对象if (client != null && client.onAuthenticated(identifier, authenticated, token)) {removeClient(client);}if (authenticated) {mPerformanceStats.accept++;} else {mPerformanceStats.reject++;}}

通过前面的分析可以知道 client 其实是 FaceAuthClient 的对象,在FaceService.java 的内部类FaceServiceWrapper的authenticate()方法进行实例化传过去的。反正最终将会回调到FaceService.java 的内部类FaceAuthClient的onAuthenticated()方法

        @Overridepublic boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,boolean authenticated, ArrayList<Byte> token) {Log.d("longzhiye","longzhiye onAuthenticated ",new Throwable());// 重点关注superfinal boolean result = super.onAuthenticated(identifier, authenticated, token);mUsageStats.addEvent(new AuthenticationEvent(getStartTimeMs(),System.currentTimeMillis() - getStartTimeMs() /* latency */,authenticated,0 /* error */,0 /* vendorError */,getTargetUserId()));// For face, the authentication lifecycle ends either when// 1) Authenticated == true// 2) Error occurred// 3) Authenticated == false// Fingerprint currently does not end when the third condition is met which is a bug,// but let's leave it as-is for now.return result || !authenticated;}

这里的super将会调到父类AuthenticationClient中的onAuthenticated()。
AuthenticationClient#onAuthenticated()
frameworks/base/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java

    public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,boolean authenticated, ArrayList<Byte> token) {super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,getTargetUserId(), isBiometricPrompt());// 省略部分代码 ......try {if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"+ ", ID:" + identifier.getBiometricId()+ ", Owner: " + getOwnerString()+ ", isBP: " + isBiometricPrompt()+ ", listener: " + listener+ ", requireConfirmation: " + mRequireConfirmation+ ", user: " + getTargetUserId());if (authenticated) { // 省略部分代码 ......try {// Explicitly have if/else here to make it super obvious in case the code is// touched in the future.if (!getIsRestricted()) {/*重点关注*/ // getIsRestricted() 获取有没有权限登录,说白了就是验证是否成功listener.onAuthenticationSucceeded(getHalDeviceId(), identifier, getTargetUserId());} else {listener.onAuthenticationSucceeded(getHalDeviceId(), null, getTargetUserId());}} catch (RemoteException e) {Slog.e(getLogTag(), "Remote exception", e);}} else {// Client not listeningSlog.w(getLogTag(), "Client not listening");result = true;}} else {// 省略部分代码 ......}} catch (RemoteException e) {Slog.e(getLogTag(), "Remote exception", e);result = true;}return result;}

这里的 listener 其实是 BiometricServiceBase.ServiceListener 接口的回调,BiometricServiceBase的内部类BiometricServiceListener也实现了该接口,但是没有实现onAuthenticationSucceeded() 方法,而该ServiceListener 接口在FaceService中的内部类ServiceListenerImpl 也有实现,并且实现了onAuthenticationSucceeded() 方法。所以将会回调到FaceService内部类的 ServiceListenerImpl#onAuthenticationSucceeded()。
ServiceListenerImpl#onAuthenticationSucceeded()

/*** 从 ClientMonitor 实现接收回调。结果被转发到 FaceManager*/private class ServiceListenerImpl implements ServiceListener {private IFaceServiceReceiver mFaceServiceReceiver;public ServiceListenerImpl(IFaceServiceReceiver receiver) {mFaceServiceReceiver = receiver;}// 省略部分代码 ......@Overridepublic void onAuthenticationSucceeded(long deviceId,BiometricAuthenticator.Identifier biometric, int userId)throws RemoteException {if (mFaceServiceReceiver != null) {if (biometric == null || biometric instanceof Face) {// 重点关注这里mFaceServiceReceiver.onAuthenticationSucceeded(deviceId, (Face) biometric,userId, isStrongBiometric());} else {Slog.e(TAG, "onAuthenticationSucceeded received non-face biometric");}}}// 省略部分代码 ......}

ServiceListenerImpl 这个类是负责将回调结果,转发到 FaceManager 中的。通过 IFaceServiceReceiver 的对象,回调 FaceManager 中的 onAuthenticationSucceeded() 方法。
FaceManager#onAuthenticationSucceeded()
frameworks/base/core/java/android/hardware/face/FaceManager.java

    private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {// 省略部分代码 ......@Override // binder callpublic void onAuthenticationSucceeded(long deviceId, Face face, int userId,boolean isStrongBiometric) {mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,face).sendToTarget();//onFaceidStopped();}// 省略部分代码 ......};

在这里通过 mHandler 发送了 MSG_AUTHENTICATION_SUCCEEDED 消息,在 handleMessage 中将会执行 sendAuthenticatedSucceeded() 方法。
frameworks/base/core/java/android/hardware/face/FaceManager.java

    private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {if (mAuthenticationCallback != null) {final AuthenticationResult result =new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);// 主要关注这里mAuthenticationCallback.onAuthenticationSucceeded(result); mAuthenticationCallback = null;if(mPendingFaceAuth != null) {authenticateInternel(mPendingFaceAuth.mCrypto, mPendingFaceAuth.mCancel, mPendingFaceAuth.mFlags, mPendingFaceAuth.mCallback, mPendingFaceAuth.mHandler, mPendingFaceAuth.mUserId);mPendingFaceAuth = null;}}}

在 sendAuthenticatedSucceeded() 方法中将会执行 BiometricAuthenticator.AuthenticationCallback 的接口的回调,将会把结果回调到 KeyguardUpdateMonitor 中FaceManager.AuthenticationCallback 的onAuthenticationSucceeded() 方法。
FaceManager.AuthenticationCallback#onAuthenticationSucceeded()
可以看一个堆栈图:

12-10 16:33:50.024  1414  1414 D longzhiye  : java.lang.Throwable
12-10 16:33:50.024  1414  1414 D longzhiye  :      at com.android.keyguard.KeyguardUpdateMonitor$15.onAuthenticationSucceeded(KeyguardUpdateMonitor.java:1427)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.hardware.face.FaceManager.sendAuthenticatedSucceeded(FaceManager.java:1212)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.hardware.face.FaceManager.access$1300(FaceManager.java:63)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.hardware.face.FaceManager$MyHandler.handleMessage(FaceManager.java:1120)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.os.Handler.dispatchMessage(Handler.java:106)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.os.Looper.loop(Looper.java:223)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at android.app.ActivityThread.main(ActivityThread.java:7945)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at java.lang.reflect.Method.invoke(Native Method)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
12-10 16:33:50.024  1414  1414 D longzhiye  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    @VisibleForTestingFaceManager.AuthenticationCallback mFaceAuthenticationCallback= new FaceManager.AuthenticationCallback() {@Overridepublic void onAuthenticationFailed() {// 身份验证失败handleFaceAuthFailed();}/* UNISOC: Modify for bug1374210 {@ */@Overridepublic void onAuthenticationStarted() {handleFaceAuthStarted();}/* @} */@Overridepublic void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {Log.d("longzhiye","longzhiye",new Throwable());Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");// 重点关注handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());Trace.endSection();}@Overridepublic void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {handleFaceHelp(helpMsgId, helpString.toString());}@Overridepublic void onAuthenticationError(int errMsgId, CharSequence errString) {// 人脸处理操作已取消或未识别到handleFaceError(errMsgId, errString.toString());}@Overridepublic void onAuthenticationAcquired(int acquireInfo) {handleFaceAcquired(acquireInfo);}};

KeyguardUpdateMonitor#handleFaceAuthenticated()
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    private void handleFaceAuthenticated(int authUserId) {Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");try {final int userId;try {userId = ActivityManager.getService().getCurrentUser().id;} catch (RemoteException e) {Log.e(TAG, "Failed to get current user id: ", e);return;}if (userId != authUserId) {Log.d(TAG, "Face authenticated for wrong user: " + authUserId);return;}if (isFaceDisabled(userId)) {Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);return;}/*重点关注*/onFaceAuthenticated(userId);} finally {setFaceRunningState(BIOMETRIC_STATE_STOPPED);}Trace.endSection();}

handleFaceAuthenticated#onFaceAuthenticated
frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    protected void onFaceAuthenticated(int userId) {Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");mUserFaceAuthenticated.put(userId, true);// Update/refresh trust state only if user can skip bouncerif (getUserCanSkipBouncer(userId)) {mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);}// Don't send cancel if authentication succeedsmFaceCancelSignal = null;for (int i = 0; i < mCallbacks.size(); i++) {/*重点关注*/KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();if (cb != null) {/*重点关注*/cb.onBiometricAuthenticated(userId,BiometricSourceType.FACE);}}mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),BIOMETRIC_CONTINUE_DELAY_MS);// Only authenticate face once when assistant is visiblemAssistantVisible = false;Trace.endSection();}

这里开始调用接口将解锁成功消息层层传递直至keyguard解锁,与指纹解锁逻辑一致
可以看到在 onFaceAuthenticated(userId) 方法中调用了 KeyguardUpdateMonitorCallback 这个抽象类的 onBiometricAuthenticated() 抽象方法,而 BiometricUnlockController extends KeyguardUpdateMonitorCallback,并且注册了回调 mUpdateMonitor.registerCallback(this)。
BiometricUnlockController #onBiometricAuthenticated()
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java

    @Overridepublic void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,boolean isStrongBiometric) {// 省略部分代码......if (unlockAllowed) {mKeyguardViewMediator.userActivity();/*重点关注*/// 开始唤醒和解锁startWakeAndUnlock(biometricSourceType, isStrongBiometric);} else {Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");}}

BiometricUnlockController#startWakeAndUnlock
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java

    public void startWakeAndUnlock(int mode) {// 省略部分代码......Runnable wakeUp = ()-> {if (!wasDeviceInteractive) {if (DEBUG_BIO_WAKELOCK) {Log.i(TAG, "bio wakelock: Authenticated, waking up...");}mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,"android.policy:BIOMETRIC");}if (delayWakeUp) {/*重点关注*/mKeyguardViewMediator.onWakeAndUnlocking();}Trace.beginSection("release wake-and-unlock");releaseBiometricWakeLock();Trace.endSection();};// 省略部分代码......mStatusBar.notifyBiometricAuthModeChanged();Trace.endSection();}

KeyguardViewMediator#onWakeAndUnlocking()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    public void onWakeAndUnlocking() {Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");mWakeAndUnlocking = true;/*重点关注*/keyguardDone();Trace.endSection();}

KeyguardViewMediator#keyguardDone()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    public void keyguardDone() {Trace.beginSection("KeyguardViewMediator#keyguardDone");if (DEBUG) Log.d(TAG, "keyguardDone()");userActivity();EventLog.writeEvent(70000, 2);/*重点关注*/Message msg = mHandler.obtainMessage(KEYGUARD_DONE);mHandler.sendMessage(msg);Trace.endSection();}

keyguardDone()该方法发送了一条 KEYGUARD_DONE 消息,在 handleMessage 中将会执行 handleKeyguardDone() 方法。
KeyguardViewMediator#handleKeyguardDone()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    private void handleKeyguardDone() {Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");final int currentUser = KeyguardUpdateMonitor.getCurrentUser();// 省略部分代码....../** 重点关注* 处理隐藏**/handleHide();Trace.endSection();}

KeyguardViewMediator# handleHide()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    private void handleHide() {Trace.beginSection("KeyguardViewMediator#handleHide");// It's possible that the device was unlocked in a dream state. It's time to wake up.if (mAodShowing) {PowerManager pm = mContext.getSystemService(PowerManager.class);pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,"com.android.systemui:BOUNCER_DOZING");}synchronized (KeyguardViewMediator.this) {if (DEBUG) Log.d(TAG, "handleHide");if (mustNotUnlockCurrentUser()) {if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");return;}mHiding = true;if (mShowing && !mOccluded) {mKeyguardGoingAwayRunnable.run();} else {/*重点关注*/// 处理开始键盘保护退出动画handleStartKeyguardExitAnimation(SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),mHideAnimation.getDuration());}}Trace.endSection();}

KeyguardViewMediator#handleStartKeyguardExitAnimation()
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

    private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");// 省略部分代码......mWakeAndUnlocking = false;setShowingLocked(false, mAodShowing);mDismissCallbackRegistry.notifyDismissSucceeded();/*重点关注*/mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);resetKeyguardDonePendingLocked();mHideAnimationRun = false;adjustStatusBarLocked();sendUserPresentBroadcast();}Trace.endSection();}

下面就不详细分析了,将会按如下顺序执行:StatusBarKeyguardViewManager#hide()→StatusBarKeyguardViewManager#hideBouncer()→KeyguardBouncer#hide()→KeyguardBouncer#mRemoveViewRunnable→KeyguardBouncer#removeView()。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/406472.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TikTok本土店海外仓发货总超时?EasyBoss ERP支持提前申请面单助力解决

近期有部分通过海外仓自发货的TikTok本土卖家表示&#xff1a;通过ERP推送订单至海外仓却无法立即出库&#xff0c;导致超出平台规定发货时间被平台处罚。 而出现这样的原因在于&#xff1a;通过ERP处理的TikTok订单&#xff0c;在使用认证的海外仓发货时&#xff0c;订单会先…

【C++ Primer Plus习题】2.2

问题: 解答: #include <iostream> using namespace std;#define LONG_TO_MA 220int main() {double distance 0;cout << "请输入距离(单位为long):";while (true){cin >> distance;if (cin.fail()){cout << "输入有误!请输入数字:&qu…

Redis 集群三主三从配置

1&#xff1a;安装 Redis安装Linux ubuntu_ubuntu离线安装redis7.2.5-CSDN博客 2&#xff1a;主从复制配置 参考 Redis主从同步配置-CSDN博客 3&#xff1a;哨兵配置 参考 Redis 哨兵模式配置-CSDN博客 4&#xff1a;集群配置 Redis 集群三主三从配置-CSDN博客 5&…

OpenCV与AI深度学习 | 使用OpenCV图像修复技术去除眩光

本文来源公众号“OpenCV与AI深度学习”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;使用OpenCV图像修复技术去除眩光 眩光是一种因过度和不受控制的亮度而引起的视觉感觉。眩光可能会使人丧失能力或只是让人感到不舒服。眩光是一…

【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)

文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布&#xff0c;2.0版本迎来第一个稳定版本&#xff0c;同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架&#xff0c;可以让你使用前端技术来构建桌面…

Redis7基础篇(九)

springboot集成redis 目录 springboot集成redis 总体概述 java连接redis常见问题 集成jedis 集成lettuce 集成redistemplate 连接单机 ​编辑​编辑​编辑redis集群 总体概述 java要想连接mysql的话需要jdbc java想要连接redis也需要中间件 jedis是第一代 lettuce第…

前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第三篇:登录功能优化

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

钓鱼的常见几种方式

钓鱼的多种方式 office钓鱼攻击 宏与宏病毒 # 宏 宏是office自带的一种高级脚本特性&#xff0c;通过VBA代码&#xff0c;可以在office中去完成某项特定的任务&#xff0c;而不必再重复相同的动作&#xff0c;目的是让用户文档中一些任务自动化# 宏病毒 宏病毒是一种寄存在文…

linux笔记1

命令格式 命令行界面的提示符解析&#xff1a; [rootlocalhost ~]# root位置&#xff1a; 登录用户名 &#xff1a; 连接符号 localhost位置: 本机的主机名 ~位置: 当前的所在位置 #位置&#xff1a; 表示是超级管理员还是普通用户 超级管…

RPA自动化流程机器人助力企业财务数字化转型

在数字经济时代&#xff0c;企业需要快速响应市场变化&#xff0c;而财务数字化转型是企业适应现代商业环境、提升竞争力的必要步骤。财务数字化转型不仅涉及企业财务能力的提升&#xff0c;推动了财务管理与决策模式的转变。RPA自动化流程机器人因其能通过自动化技术帮助企业实…

超声波水表是什么?量程比又是什么?

一、超声波水表概述 1.定义&#xff1a; 超声波水表是一种利用超声波技术来测量水流速度&#xff0c;进而计算出流经管道的水体积流量的计量设备。它通过发送和接收超声波信号的时间差来确定水流的速度&#xff0c;从而精确地计量水的流量。 2.工作原理&#xff1a; 超声波…

Android 架构模式之 MVC

目录 架构设计的目的对 MVC 的理解Android 中 MVC 的问题试吃个小李子ModelViewController 大家好&#xff01; 作为 Android 程序猿&#xff0c;MVC 应该是我们第一个接触的架构吧&#xff0c;从开始接触 Android 那一刻起&#xff0c;我们就开始接触它&#xff0c;可还记得我…

企业入驻西安国际数字媒体产业园的十大好处

在当今数字化飞速发展的时代&#xff0c;企业的发展需要依托创新的平台和资源的整合。西安国际数字影像产业园&#xff0c;作为数字产业的引领者&#xff0c;为入驻企业提供了众多独特的优势和机遇。 好处一&#xff1a;产业集聚效应。西安国际数字影像产业园汇聚了众多数字媒体…

【Unity】通用GM QA工具 运行时数值修改 命令行 测试工具

GM工具使用: GM工具通常用于游戏运行时修改数值(加钱/血量)、解锁关卡等&#xff0c;用于快速无死角测试游戏。一个通用型GM工具对于游戏项目是非常实用且必要的&#xff0c;但通用不能向易用妥协&#xff0c;纯命令行GM门槛太高&#xff0c;对QA不友好。 这类运行时命令行工具…

【蓝桥杯冲刺省一,省一看这些就够了-C++版本】蓝桥杯STL模板及相关练习题

蓝桥杯历年省赛真题 点击链接免费加入题单 STL map及其函数 map<key,value> 提供一对一的数据处理能力&#xff0c;由于这个特性&#xff0c;它完成有可能在我们处理一对一数据的时候&#xff0c;在编程上提供快速通道。map 中的第一个值称为关键字(key)&#xff0c;…

以前嗤之以鼻,现在逐字学习!缠论量化代码大公开!|邢不行

这是邢不行第 113 期量化小讲堂的分享 作者 | 邢不行、密斯锌硒 一千个人眼中有一千个哈姆雷特&#xff0c;我们只是尽可能的去量化我们理解的部分缠论的思路。 我们过往在文章中多次聊过技术指标&#xff0c;如MACD、KDJ等等&#xff0c;也聊过一些K线形态&#xff0c;如跳…

C语言 | Leetcode C语言题解之第354题俄罗斯套娃信封问题

题目&#xff1a; 题解&#xff1a; int cmp(int** a, int** b) {return (*a)[0] (*b)[0] ? (*b)[1] - (*a)[1] : (*a)[0] - (*b)[0]; }int maxEnvelopes(int** envelopes, int envelopesSize, int* envelopesColSize) {if (envelopesSize 0) {return 0;}qsort(envelopes, …

简历系统

TOC springboot0745简历系统 第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对简历系统方面的要求也在不断提高&#xff0c;需要工作的人数更是不断增加&#xff0c;使得简历系统…

论文解读:LONGWRITER: UNLEASHING 10,000+ WORD GENERATION FROM LONG CONTEXT LLMS

摘要 现象&#xff1a;当前的大预言模型可以接受超过100,000个tokens的输入&#xff0c;但是却难以生成超过2000个token的输出。 原因&#xff1a;监督微调过程(SFT)中看到的样本没有足够长的样本。 解决方法&#xff1a; Agent Write&#xff0c;可以将长任务分解为子任务&a…

Java CompletableFuture:你真的了解它吗?

文章目录 1 什么是 CompletableFuture&#xff1f;2 如何正确使用 CompletableFuture 对象&#xff1f;3 如何结合回调函数处理异步任务结果&#xff1f;4 如何组合并处理多个 CompletableFuture&#xff1f; 1 什么是 CompletableFuture&#xff1f; CompletableFuture 是 Ja…