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.mzph.cn/news/580054.shtml

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

相关文章

Java Web基础详解

回顾 之前的两篇的文章已经大概的带我们了解了tomcat的一些基本的操作&#xff0c;比如从零搭建我们自己的调试环境以及官方文档构建的方式&#xff0c;接下来的话&#xff0c;我将带大家来了解一下tomcat的一些基础知识&#xff0c;这些基础知识将以问题的方式抛出&#xff0…

【SpringCloud笔记】(11)消息驱动之Stream

Stream 技术背景 底层不同模块可能使用不同的消息中间件&#xff0c;这就导致技术的切换&#xff0c;微服务的维护及开发变得麻烦起来 概述 官网&#xff1a; https://spring.io/projects/spring-cloud-stream#overview https://cloud.spring.io/spring-cloud-static/spring…

最小覆盖子串(LeetCode 76)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路参考文献 1.问题描述 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff…

git 常用基本命令, reset 回退撤销commit,解决gitignore无效,忽略记录或未记录远程仓库的文件,删除远程仓库文件

git 基本命令 reset 撤销commit https://blog.csdn.net/a704397849/article/details/135220091 idea 中 rest 撤销commit过程如下&#xff1a; Git -> Rest Head… 在To Commit中的HEAD后面加上^&#xff0c;点击Reset即可撤回最近一次的尚未push的commit Reset Type 有三…

Flink Has Become the De-facto Standard of Streaming Compute

摘要&#xff1a;本文整理自 Apache Flink 中文社区发起人、阿里巴巴开源大数据平台负责人王峰&#xff08;莫问&#xff09;&#xff0c;在 Flink Forward Asia 2023 主会场的分享。Flink 从 2014 年诞生之后&#xff0c;已经发展了将近 10 年&#xff0c;尤其是最近这些年得到…

爬虫系列----Python解析Json网页并保存到本地csv

Python解析JSON 1 知识小课堂1.1 爬虫1.2 JSON1.3 Python1.4 前言技术1.4.1 range1.4.2 random1.4.3 time.sleep1.4.4 with open() as f: 2 解析过程2.1 简介2.2 打开调试工具2.3 分析网址2.3.1 网址的规律2.3.2 网址的参数 2.4 爬取第一页内容2.5 存入字典并获取2.6 循环主体数…

7-2 设计一元二次方程求解类(高教社,《Python编程基础及应用》习题9-4)——python

设计一个类Root来计算ax2bxc0的根。该类包括&#xff1a;a、b、c共3个属性表示方程的3个系数&#xff0c;getDiscriminant()方法返回b2-4ac, getRoot1()和getRoot2()返回方程的两个根。 其中&#xff0c;getRoot1()返回的根对应&#xff1a; getRoot2()返回的根对应&#xff1a…

百度沧海文件存储CFS推出新一代Namespace架构

随着移动互联网、物联网、AI 计算等技术和市场的迅速发展&#xff0c;数据规模指数级膨胀&#xff0c;对于分布式文件系统作为大规模数据场景的存储底座提出了更高的要求。已有分布式文件系统解决方案存在着短板&#xff0c;只能适应有限的场景&#xff1a; >> 新型分布式…

格密码:傅里叶矩阵

目录 一. 铺垫性介绍 1.1 傅里叶级数 1.2 傅里叶矩阵的来源 二. 格基与傅里叶矩阵 2.1 傅里叶矩阵详细解释 2.2 格基与傅里叶矩阵 写在前面&#xff1a;有关傅里叶变换的解释太多了&#xff0c;这篇博客主要总结傅里叶矩阵在格密码中的运用。对于有一定傅里叶变换基础的同…

IntelliJ IDEA [设置] 隐藏 .idea 等 .XXX 文件夹

文章目录 1. 问题描述2. 解决办法3. 最后效果4. 特殊处理&#xff08;正常不需要此步骤&#xff09;总结 我们使用 IntelliJ IDEA 导入项目的时候&#xff0c;经常会看到一些 .XXX 的文件夹&#xff08;例如&#xff1a;.idea&#xff0c;.mvn&#xff0c;.gradle 等&#xff0…

支付宝、学习强国小程序input、textarea数据双向绑定

前言 和 vue 的绑定有些区别&#xff0c;需要注意。直接 value"{{inputValue}}" 是无法双向绑定的。 正确思路 文档说的比较详细&#xff0c;不过没有组合使用的案例&#xff0c;需要自行理解。这里正确的方法是先用 value 绑定数据&#xff0c;再使用 onInput 事件…

鸿蒙的基本项目_tabbar,首页,购物车,我的

以上效果&#xff0c;由四个ets文件实现&#xff0c;分别是容器页面。首页&#xff0c;购物车&#xff0c;我的。 页面里的数据&#xff0c;我是用json-server进行模拟的数据。 一、容器页面 使用组件Tabs和Tabcontent结合。 import Home from "./Home"; import …

短剧付费变现小程序源码系统:开通会员+在线充值+风口项目,变现利器+完整的代码包 附带部署安装教程

在当今数字化时代&#xff0c;短剧付费变现小程序源码系统已经成为了一个热门的风口项目。它以开通会员、在线充值、完整的代码包等特色功能&#xff0c;成为了一种有效的变现利器&#xff0c;受到了广泛的关注和应用。本文将详细介绍这个源码系统的背景和特色功能&#xff0c;…

实现阿里云oss云存储,简单几步

一、前言 虽然平常学习用的不多&#xff0c;但是用的时候再去找官方文档&#xff0c;也很繁琐&#xff0c;不如直接整理以下&#xff0c;方便粘贴复制&#xff0c;本文介绍两种图片上传方式①普通上传②服务端签名直传 1.普通上传 加载maven依赖 <dependency><grou…

centos 安装oracle 11.2.04 并配置数据库自启动操作记录,一次完成

环境&#xff1a; centos版本7.3&#xff0c;安装的有图形化界面 Oracle11.2.04&#xff0c;之所以选择这个版本是因为网上有人说11其他版本的在安装的过程中会出现这样或那样的问题&#xff0c;下载地址放到文章下面 步骤&#xff0c;按顺序&#xff1a; 1、创建安装Oracle…

万用表测接地电阻方法

万用表测接地电阻方法 用万用表在不同土质的土壤对接地电阻进行了实验&#xff0c;并将万用表所测数据和专用接地电阻测试仪所测数据进行了比较&#xff0c;两者十分接近。具体测量方法如下&#xff1a; 找两根8mm、1m长的圆钢&#xff0c;将其一端磨尖作为辅助测试棒&#x…

Mysql之视图

Mysql之视图 常见的数据库对象视图概述为什么使用视图视图的理解创建视图创建单表视图别名的运用 创建多表联合视图利用视图对数据进行格式化contact 函数以视图为基&#xff0c;再创建新的视图 查看视图更新视图的数据一般情况不可更新的视图 修改和删除视图修改视图删除视图注…

【C#】Visual Studio 2022 远程调试配置教程

在某些特殊的情况下&#xff0c;开发机和调试机可能不是同一台设备&#xff0c;此时就需要远程调试了。 开发机配置 首先需要确保两台机器在同一局域网下。 创建共享文件夹 随便找个地方新建一个文件夹&#xff0c;用来放编译结果。例如我这里是 D:\DebuggingWorkspace\。 …

什么是阿里云负载均衡SLB?

目录 硬件或软件负载均衡的区别是什么&#xff1f; 什么是阿里云负载均衡SLB&#xff1f; 阿里云传统型负载均衡CLB 硬件或软件负载均衡的区别是什么&#xff1f; 通过专用硬件实现负载均衡&#xff0c;那么整体成本会较高&#xff0c;而且设备容易出现单点故障&#xff0c;…

【MySQL】InnoDB和MyISAM区别

文章目录 一、索引不同1 InnoDB聚簇索引&#xff0c;MyISAM非聚簇索引1 InnoDB聚簇索引2 MyISAM非聚簇索引 2 InnoDB必须要有主键&#xff0c;MyISAM允许没有主键3 InnoDB支持外键4 InnoDB不支持全文索引5 索引保存位置不同 二、对事物的支持三、存储结构不同四、存储空间不同五…