在Android P版本中加入Face Unlock相关功能的讲解
随着时代的发展,指纹已经不能满足当前人们的需要,特别是在iphone X推出以后,各大手机厂商争相模仿,都会在手机中加入人脸解锁功能,我们今天就拿出一例讲解一下,如何在Android手机中加入face Unlock功能
一 . Face Unlock主要流程概括
首先,按照用户的使用角度,先进入Settings中录入人脸,此时Settings会优先启动FaceUnlockService将Camera获取到的人脸数据传入FaceUnlockService再由FaceUnlockService操纵Faceunlock引擎将Camera数据转换为SDK可以识别的数据传入SDK识别,当SDK返回数据验证OK的result后,我们将该数据存入本地,并将初始值设定为已录入状态,这样FaceUnlockService就会在每次灭屏和亮屏的时候去监听这些值的设定状态从而决定是否去启动人脸解锁功能,说到这里就不得不说一下FaceUnlockService的启动流程,因为FaceUnlockService需要启动的更早并且需要在任何合适的时机去刷新录入状态的设值。
二 . FaceUnlockService启动流程
1.我们的FaceUnlockService与FUS引擎被集成在SystemUI中,用户在开机后会调用KeyguardViewMediator方法,我们的FaceUnlockService也就是在setupLocked()绑定开始启动的,我们在这里使用bindService方法启动
private void setupLocked() {
mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
......
bindFaceUnlockService();//启动FaceUnlockService
......
}
/**
*FaceUnlockService启动方法
*/
private void bindFaceUnlockService() {
Intent intent = new Intent(mContext, FaceUnlockService.class);
mContext.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected, call stack: "+android.os.Debug.getCallers(10));
mUpdateMonitor.createFaceUnlockManager(IFaceUnlockEngineManager.Stub.asInterface(service));
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected, ComponentName="+name);
}
}, Context.BIND_AUTO_CREATE);
}
2.在FaceUnlockService我们主要做一件事,就是启动FUS引擎
public class FaceUnlockService extends Service {
public static final String TAG = "FaceUnlockService";
private static final String MEGVII_SOURCE_DIR = "/storage/emulated/0/Android/data/com.android.settings/files/megvii";
private static final int PANORAMA_MGB_RES_ID = R.raw.panorama_mgb;
private static final int MEGVIIFACEPP_MODEL_RES_ID = R.raw.megviifacepp_model;
private static FaceUnlockEngineManagerService sEngineService;
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
// create engine is necessary
getEngineInstance(this);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return getEngineInstance(this);
}
public static String getMegviiSourceDir() {
int userId = ActivityManager.getCurrentUser();
String megviiSourceDir = MEGVII_SOURCE_DIR;
if (userId != UserHandle.USER_SYSTEM) {
megviiSourceDir = "/storage/emulated/" + userId + "/Android/data/face_data/files/megvii";
}
return megviiSourceDir;
}
private static synchronized FaceUnlockEngineManagerService getEngineInstance(Service service) {
if (sEngineService == null) {
Context appCtx = service.getApplication();
sEngineService = new FaceUnlockEngineManagerService(appCtx,
getMegviiSourceDir(), PANORAMA_MGB_RES_ID, MEGVIIFACEPP_MODEL_RES_ID);
}
return sEngineService;
}
}
3.FUS引擎作为FACE++ SDK的管理类被启动后,开始处理各个接口传递过来的数据并返回相应的结果, FUS引擎主要实现了IFaceUnlockEngineManager中的接口,所以FUS引擎功能分为init(),saveFeature,compare, prepare,reset(),release(),deleteFeature等以下几种,其接口定义主要是根据SDK的使用场景来定义的,因为SDK在使用前需要进行init初始化,prepare预热,而后才能进行saveFeature录入人脸或是compare比对人脸的一些操作,而在录入和比对或是deleteFeature删除人脸后需要进行重置SDK reset()的操作,而FUS引擎作为FACEUNlock的心脏部分,处理着所有的faceunlock数据
interface IFaceUnlockEngineManager {
void init();
void saveFeature(in ParcelFileDescriptor pfd, int dataLength, int width, int height, int angle, boolean useLive, in IFaceUnlockEngineSaveFeatureResult result);
void compare(in ParcelFileDescriptor pfd, int dataLength, int width, int height, int angle, boolean useLive, boolean strictMode, in IFaceUnlockEngineCompareResult result);
void prepare();
void reset();
void release();
/**
* delete a feature
* @param id the id of feature that we want to delete
*/
void deleteFeature(int id);
void setDetectArea(int left, int top, int right, int bottom);
/**
* this method should only be called after Engine state changes to STATE_INITIALIZED
*/
int getFeatureCount();
/**
* get Engine state
* The return state can be one of:
* 0 - STATE_UNINITIALIZED
* 1 - STATE_INITIALIZING
* 2 - STATE_INITIALIZED
* 3 - STATE_INITIALIZATION_FAILED
*/
int getState();
void registerStateListener(in IFaceUnlockEngineStateListener listener);
void unregisterStateListener(in IFaceUnlockEngineStateListener listener);
/**
* get the maximum size of data that can be transported by saveFeature()
*/
//int getEnrollSharedMemorySize();
/**
* get the maximum size of data that can be transported by compare()
*/
//int getAuthenticationSharedMemorySize();
//ParcelFileDescriptor getFileDescriptorForEnroll();
//ParcelFileDescriptor getFileDescriptorForAuthentication();
}
这个就是FaceUnlockService启动流程以及基本的工作原理,而FUS引擎作为主干部分功能承担着处理录入比对删除的三大重要工作,接下来为了方便理解,按照录入和比对两大功能模块分开讲解。
三 . FaceUnlock录入流程讲解
1.人脸录入入口被集成在Settings中,但是整个录入过程并不全都在Settings中,正如前面所说,整个FaceUnlockService和FUS引擎都被集成在SystemUI中,而我们的FaceUnlock在Settings的SecuritySettings中。
首先,我们需要找到SecuritySettings对应的xml加入我们需要的Preference
Settings / res/xml/security_dashboard_settings.xml
<Preference
android:key="facelock_settings"
android:title="@string/security_settings_faceid_preference_title"
android:summary="@string/face_not_set_summary"/>
再在SecuritySettings.java中加载出来
Settings / src/com/android/settings/security/SecuritySettings.java
import com.android.settings.faceid.FaceLockPreferenceController;
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle, SecuritySettings host) {
......
securityPreferenceControllers.add(new FaceLockPreferenceController(context));
......
}
2.人脸录入处理类在FaceIdEnrollPreview.java中
public class FaceIdEnrollPreview extends Activity implements Camera.PreviewCallback
, FaceCircleProgressbar.FaceSuccessListener, FaceCircleProgressbar.ProgressListener
, View.OnClickListener {
......
@Override
public void onPreviewFrame
(final byte[] bytes, final Camera camera) {
Log.d(TAG, "onPreviewFrame");
if (!isDetect) {
isDetect = true;
handler.post(new Runnable() {
@Override
public void run() {
//byte[] data = PdUtils.getPdData(camera);
//if (data == null) {
Log.d(TAG, "handleData1");
handleData(bytes, width, height);
//}
}
});
}
}
private void handleData(byte[] pdData, int width, int height) {
//这里就运用了aidl原理将视频流获取到的数据传给FUS引擎
mFaceUnlockAdapter.saveFeature(pdData, width, height, angle, true,
new IFaceUnlockEngineSaveFeatureResult.Stub() {
@Override
public void onSaveFeatureCompleted(boolean succeeded, int result, int id) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (succeeded) {
Log.d(TAG, "saveFeature success! return feature id = " + id);
handleRegisterSuccess();
insertFaceName(id);
mFaceUnlockAdapter.backupFaceInfo();
if (lastResult == result) {
successTimes++;
} else {
lastResult = result;
successTimes = 0;
}
if (successTimes >= MAX_SUCCESS_TIME) {
Log.d(TAG, "face register success!");
} else {
isDetect = false;
//faildMsg.setText(R.string.unlock_camera_steady);
}
} else {
successTimes = 0;
Log.d(TAG, "result ======" + result);
isDetect = false;
}
}
});
}
});
}
......
}
FaceUnlockAdapter.java
public class FaceUnlockAdapter {
......
public void saveFeature(byte[] imageData, int width, int height, int angle, boolean useLive, IFaceUnlockEngineSaveFeatureResult result) {
if (!mServiceBound) {
return;
}
int dataLength = imageData.length;
ParcelFileDescriptor pfd = null;
try {
pfd = ParcelFileDescriptor.fromData(imageData, "face_unlock_enroll");
} catch (IOException ex) {
Log.d(TAG, "ParcelFileDescriptor.fromData(), ex: "+ex);
}
try {
mFaceEngine.saveFeature(pfd, dataLength, width, height, angle, useLive, result);
//录入结果通过参数result的变化而得知
} catch (RemoteException ex) {
Log.d(TAG, "saveFeature: "+ex);
}
}
......
}
四 . FaceUnlock比对流程讲解
1.人脸比对入口被集成在SystemUI中,底层算法使用Face++的SDK,首先要将SDK的so库集成进SystemUI
Android.mk
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := faceunlock:libs/UnlockSdk.aar
include $(BUILD_MULTI_PREBUILT)
include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES += faceunlock
LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.megvii.facepp.sdk
LOCAL_PREBUILT_JNI_LIBS := jni/armeabi-v7a/libMegviiUnlock-jni-1.2.so \
jni/armeabi-v7a/libMegviiUnlock.so \
jni/armeabi-v7a/libFaceDetectCA.so \
jni/armeabi-v7a/libmegface_meglive.so \
jni/armeabi-v7a/libgnustl_shared.so \
jni/armeabi-v7a/libqspower-1.0.0.so \
jni/armeabi-v7a/libCallaSC.so
2.用户在亮屏后会调用KeyguardUpdateMonitor.java中的handleStartedWakingUp()方法,我们在此方法中去刷新监听faceunlock的状态
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
protected void handleStartedWakingUp() {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
resetFaceUnlockUpdatedForbidden();
updateFingerprintListeningState();
//我们仿照指纹解锁监听同样去刷新一下人脸解锁状态
updateFaceUnlockListeningState();
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onStartedWakingUp();
}
}
Trace.endSection();
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
public void updateFaceUnlockListeningState() {
if (mFaceUnlockUpdateForbidden) {
return;
}
boolean shouldListen = shouldListenForFaceUnlock();
Log.d(TAG, "updateFaceUnlockListeningState;shouldListen : "+shouldListen+ " mFaceUnlockRunningState = "+mFaceUnlockRunningState);
if ((mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_RUNNING ||
mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_PAUSED ||
mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_STOPPED_AND_WAIT_FOR_USER_RESTART)
&& !shouldListen) {
stopListeningForFaceUnlock();
} else if (mFaceUnlockRunningState == FaceUnlockManager.FACEUNLOCK_STATE_STOPPED && shouldListen) {
//如果条件合适启动人脸解锁
startListeningForFaceUnlock();
}
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void startListeningForFaceUnlock() {
Log.d(TAG, "----------- startListeningForFaceUnlock");
if (mFaceUnlockManager != null) {
resetFaceUnlockRetryCount();
mFaceUnlockManager.start();
}
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
public void start() {
handler.sendEmptyMessage(FACE_UNLOCK_MSG_HANDLE_START);
}
private void handleStart() {
boolean shouldListenForFaceUnlock = mkeyguardUpdateMonitor.shouldListenForFaceUnlock();
if (isCameraNeedToStop || !shouldListenForFaceUnlock) {
Log.d(TAG, "faceunlock start is not allow, isCameraNeedToStop = " + isCameraNeedToStop
+ " shouldListenForFaceUnlock = " + shouldListenForFaceUnlock);
return;
}
if (!isFaceEngineReady()) {
Log.d(TAG, "start() failed, Face engine is not ready.");
sendPendingStartMessage();
return;
}
if (mState == FACEUNLOCK_STATE_RUNNING) {
return;
}
Log.d(TAG, "start");
if (!getIsDisableFaceUnlock()){
notifyUIThreadCompareMessage(mContext.getResources().
getString(R.string.keyguard_face_unlock_failed_too_many_times_text,
mMillisUntilFinished));
return;
}
if (mTryTimes >= 5) {
mTryTimes = 0;
}
// Camera 0 is not released timely when Screen on quickly
// try to start after 300ms
boolean isCameraInUse = isCameraInUse();
if (mTryTimes < 5 && isCameraInUse) {
Log.d(TAG, "camera is in used");
startDelay(300);
return;
} else if (isCameraInUse) {
Log.d(TAG, "try times: " + mTryTimes + ", camera is in used");
return;
}
if (!startInternal()) {//在这里进行比对初始化工作
sendPendingStartMessage();
return;
}
notifyUIThreadCompareMessage(mContext.getResources().getString(R.string.keyguard_face_unlock_discerning));
updateState(FACEUNLOCK_STATE_RUNNING);
return;
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
protected boolean startInternal() {
if (isCameraNeedToStop) {
return false;
}
if (!super.startInternal()) {
Log.d(TAG, "super.startInternal()=false, camera=" + camera);
return false;
}
refresh();
// TODO: test, currently we presume that surface is already created.
mSurfaceViewCreated = true;
if (!mSurfaceViewCreated) {
mPendingDectect = true;
} else {
//倘若初始化工作完成就开始比对
startDetect();
try {
mFaceEngine.prepare();
} catch (RemoteException ex) {
Log.d(TAG, "mFaceEngine.prepare(), " + ex);
stop();
}
}
return true;
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void startDetect() {
Log.i(TAG, "start detect");
mDetectStartTime = 0;
if (!isTrigger) {
isTrigger = true;
isSuccess = false;
isDetecting = false;
//removeTimeoutRunnable();
startCamera();
long startPreviewBeginTime = System.currentTimeMillis();
startCameraPreview();
long startPreviewEndTime = System.currentTimeMillis();
//updateState(FACEUNLOCK_STATE_RUNNING);
if (DEBUG_FACEUNLOCK) {
if (sharedUtil.getBooleanValueByKey("preview_enable")) {
surface.setAlpha(1.0f);
}
clearProfile();
}
}
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
@Override
public void onPreviewFrame(final byte[] bytes, final Camera camera) {
Log.d(TAG, "onPreviewFrame, isSuccess=" + isSuccess
+ ", isDetecting=" + isDetecting + ", isTrigger=" + isTrigger);
if (isSuccess || isDetecting || !isTrigger || mPaused) return;
isDetecting = true;
if (mDetectStartTime == 0) {
mDetectStartTime = System.currentTimeMillis();
}
//notifyUIThreadCompareMessage(R.string.keyguard_face_unlock_discerning);
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable for face sdk compare width = " + width + " height = " + height);
long start = System.currentTimeMillis();
Log.i(TAG, "onPreview ------------ 1, bytes.length=" + bytes.length);
try {
ParcelFileDescriptor pfd = null;
int dataLength = bytes.length;
try {
pfd = ParcelFileDescriptor.fromData(bytes, "face_unlock_auth");
} catch (IOException ex) {
Log.d(TAG, "ParcelFileDescriptor.fromData(), ex:" + ex);
return;
}
Log.i(TAG, "onPreview ------------ 2");
mFaceEngine.compare(pfd, dataLength, width, height, angle, true, strictMode,
new IFaceUnlockEngineCompareResult.Stub() {
@Override
public void onCompareCompleted(boolean succeeded, int result) {
onCompareResultCallback(succeeded, result);
}
});
} catch (RemoteException ex) {
Log.d(TAG, "lite.compare, " + ex);
}
if (DEBUG_FACEUNLOCK) {
String fileName = simpleDateFormat.format(new Date()) + "." + (System
.currentTimeMillis() % 1000);
int pScore = report[3] % 100;
int mScore = report[3] / 100;
Log.d(TAG, "run: pScore" + pScore + "|mScore:" + mScore);
}
}
});
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void onCompareResultCallback(boolean succeeded, int result) {
Log.d(TAG, "onCompareResultCallback, succeeded=" + succeeded + ", result=" + result);
if (succeeded) {
notifyWorkerThreadCompareSucceeded();
} else {
int stringResId = 0;
switch (result) {
case Lite.MG_UNLOCK_LIVENESS_WARNING:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_BOTTOM:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_LEFT:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_RIGHT:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_OFFSET_TOP:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_FACE_QUALITY:
stringResId = R.string.setting_face_unlock_prompt_entry_error_eight;
break;
case Lite.MG_UNLOCK_FACE_SCALE_TOO_LARGE:
stringResId = R.string.setting_face_unlock_prompt_entry_error_four;
break;
case Lite.MG_UNLOCK_FACE_SCALE_TOO_SMALL:
stringResId = R.string.setting_face_unlock_prompt_entry_error_three;
break;
case Lite.MG_UNLOCK_FAILED:
break;
case Lite.MG_UNLOCK_FACE_NOT_FOUND:
stringResId = R.string.setting_face_unlock_prompt_entry_error_seven;
break;
case Lite.MG_UNLOCK_KEEP:
stringResId = R.string.setting_face_unlock_prompt_entry_error_nine;
break;
case Lite.MG_ATTR_BLUR:
break;
case Lite.MG_ATTR_EYE_OCCLUSION:
break;
case Lite.MG_ATTR_EYE_CLOSE:
stringResId = R.string.setting_face_unlock_prompt_entry_error_five;
break;
case Lite.MG_ATTR_MOUTH_OCCLUSION:
break;
case Lite.MG_UNLOCK_FEATURE_MISS:
break;
case Lite.MG_UNLOCK_FEATURE_VERSION_ERROR:
break;
default:
}
isDetecting = false;
notifyUIThreadCompareMessage(mContext.getResources().getString(stringResId));
}
}
SystemUI / src/com/lenovo/faceunlock/FaceUnlockManager.java
private void notifyWorkerThreadCompareSucceeded() {
handler.sendEmptyMessage(FACE_UNLOCK_MSG_COMPARE_SUCCEEDED);
}
SystemUI / src/com/faceunlock/FaceUnlockManager.java
private void onCompareSucceeded() {
Log.d(TAG, "onCompareSucceeded");
removeCountdown();
mTryTimes = 0;
isSuccess = true;
handler.removeCallbacksAndMessages(null);
stopDetect();
//updateState(FACEUNLOCK_STATE_STOPPED);
// Note: Lite.reset() is not thread safe. Currently, we call this api in both onCompareSucceeded()
// and onCompareFailed(), so we only can these two methods in the same thread (UI thread)
try {
mFaceEngine.reset();
} catch (RemoteException ex) {
Log.d(TAG, "mFaceEngine.reset failed, " + ex);
}
mUIThreadHandler.post(new Runnable() {
@Override
public void run() {
//updateState(FACEUNLOCK_STATE_STOPPED);
if (Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_WHEN_SCREEN_ON, 0) == 1) {
notifyUIThreadCompareMessage(mContext.getResources().getString(R.string.keyguard_face_unlock_success_swipe_text));
}
//这里调用成功的回调接口
mCallback.onAuthenticationSucceeded();
updateState(FACEUNLOCK_STATE_STOPPED);
}
});
if (DEBUG_FACEUNLOCK) {
SharedUtil share = new SharedUtil(mContext);
int count = share.getIntValueByKey(UNLOCK_COUNT_KEY) + 1;
share.saveIntValue(UNLOCK_COUNT_KEY, count);
}
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
@Override
public void onAuthenticationSucceeded() {
handleFaceAuthenticated();
}
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void onFaceAuthenticated(int userId) {
mUserFaceAuthenticated.put(userId, true);
mFaceAlreadyAuthenticated = true;
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onFaceAuthenticated(userId);
}
}
}
SystemUI / src/com/android/systemui/statusbar/KeyguardIndicationController.java
@Override
public void onFaceAuthenticated(int userId) {
boolean faceUnlockEnabled = Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_SCREEN, 0) == 1;
boolean faceUnlockWhenScreenOn = Settings.System.getInt(mContext.getContentResolver(), FACE_UNLOCK_WHEN_SCREEN_ON, 0) == 1;
if (faceUnlockEnabled) {
mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(false);
}
if (faceUnlockWhenScreenOn) {
//这里是主管解锁的方法
mStatusBarKeyguardViewManager.hideBouncerAndKeyguard();
SystemUI /src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
public void hideBouncerAndKeyguard(){
showBouncerOrKeyguard(true);
}
SystemUI /src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (DEBUG) Log.d(TAG, "showBouncerOrKeyguard() is called.");
if (mBouncer.needsFullscreenBouncer() && !mDozing) {
if (DEBUG) {
Log.d(TAG, "needsFullscreenBouncer() is true, show \"Bouncer\" view directly.");
}
// The keyguard might be showing (already). So we need to hide it.
mStatusBar.hideKeyguard();
mBouncer.show(true /* resetSecuritySelection */);
} else {
if (DEBUG) {
Log.d(TAG, "needsFullscreenBouncer() is false,"
+ "show \"Notification Keyguard\" view.");
}
mStatusBar.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
mBouncer.prepare();
}
}
updateStates();
}
3.至此FaceUnlock大体功能已经完善,当然了,还需要加入比对错误次数等等一系列判断还有倒计时等等功能,值得一提的事,在与多用户的交互过程中,我们切换其他用户会出现打不开Camera的状况,在我之前的blog中有讲到,大家可以返回去看看,解决此问题后还会有SystemUI主用户一直持有FaceUnlockService的问题,这是因为比对的service开机在KeyguardViewMediator只启动了一回,我们需要在切换用户时解绑,切换用户后重新绑定该服务就可以解决此问题。
SystemUI / src/com/android/keyguard/KeyguardUpdateMonitor.java
private void handleUserSwitching(int userId, IRemoteCallback reply) {
.......
mUserSwitching = true;
unbindFaceUnlockService();
......
}
private void handleUserSwitchComplete(int userId) {
.......
bindFaceUnlockService();
mUserSwitching = false;
......
}