記一次息屏指紋解鎖效能優化實現
需求背景
。客戶反饋我司的指紋解鎖機器冷屏(息屏)解鎖下速度太慢,體驗很差,而對比機卻非常快
。對比發現,我司機器跟市面品牌機的冷屏解鎖速度差了不是一個等級, 急待改善.
既然要優化功能,首先要做到理解功能實現原理,如此才能找到效能瓶頸,開啟突破點.
1、解鎖流程
Google預設的冷屏指紋解鎖基本框架流程如下:
(1)指紋IC檢測到手指觸控模組,HW觸發irq,被Linux kernel接收到;
(2)fingerprintd守護程序接到kernel上報的irq後呼叫TZ的系統呼叫介面發起指紋資料採圖、比對auth請求;
(3)指紋資料auth比對成功,從TZ返回kernel再返回fingerprintd,此處耗費約180-260ms;
(4)fingerpritnd接到auth成功訊息,binder回撥FingerprintService物件onAuthenticated介面;
(5)auth success結果回撥到keyguard,準備wakeup system;
(6)keyguard呼叫PowerManager.wakeUp() 喚醒系統,耗費約350+200 ms
(7)Wakeup completed,keyguard呼叫keyguardDone解鎖完成後,耗費約200-300ms
(8)解鎖完成,呼叫系統介面點亮背光,此刻完成整個解鎖全過程。
從以上流程可以看出,整個息屏解鎖流程的大部分時間集中在三個過程:指紋運算比對、系統和外設wakeup、解鎖繪圖,這三塊時間之和即為
整個解鎖流程完成的理論時間之和約:900 ~1100 ms
2、影響耗時因素
從上面的流程看到幾個資訊:wakeup、authenticated、keyguard unlock屬於最耗時的三個部分,而且是序列執行的,下面分別分析這幾個部分是否有改善空間.
2.1 Sys wakeup
指紋比對成功後keyguard程序會通過PowerManagerService的wakeup()介面執行喚醒系統的操作,該函式最耗時部分在於底層驅動裝置的resume過程,其中佔比重最大的屬LCD,TP的resume,所以此處屬於重點優化部分內容。另外,系統要顯示資料,肯定要等LCD resume完成才可以,這個可以理解,但是為什麼TP 的resume會影響到系統亮屏呢?這個原因其實可以從程式碼中找到,後面原始碼分析將會介紹,TP resume的動作是放在fb unblank的執行緒中的,而且會發現系統要亮屏其實是可以不等TP resume的。
2.2 指紋運算比對
fingerprintd收到指紋IRQ後,會通過系統呼叫介面向Trustzoon端的指紋TA(trust app)發起指紋影象採集、模板對比操作,而這個耗時業內標準是200ms左右,這塊各個方案供應商表現差別不大,此部分的優化完全依靠方案提供商。
2.3 解鎖繪製
現有的方案是要等執行解鎖動作完成後再去點亮背光,如果不等解鎖完成就去點亮背光會看到解鎖退去的動畫閃動,使用者體驗不佳。這部分的耗時比較多,從log看至 200-300 ms的delay,屬於重點優化部分。若能解決解鎖時候不出現“動畫退去的閃動”也是個不錯的方法。
3、競品分析
既然對比機比我們的速度快這麼多,拋開客觀條件(CPU,flash讀寫速度等),還是可以認真的研究一番,看看優秀的產品是怎麼去做的,我們可以嘗試參考優秀做的實現方式用到我們的產品上,師夷長技以制夷,下面簡單描述下兩款對比機的分析過程.
3.1 某品牌機1
老實說,要不是因為這個分析我還真沒聽過這個牌子的手機,不過人家的冷屏解鎖做的如此之快,確實很不錯,很值得我們研究學習一番。經過log分析,機器程式碼逆向發現,該機器不走尋常路,簡單來說就是:
。收到指紋中斷後就另外開執行緒直接去wakup系統,而不是等指紋auth success再去做,這樣做的好處就是wakeup系統跟auth同時進行,由原來序列改為並行,重疊部分的時間就相當於省下了.
。系統休眠的時候先自動上鎖,然後再將keyguard鎖解掉!這樣系統滅屏的時候相當於沒有上鎖狀態,指紋冷屏解鎖就相當於只有wakeup 、指紋auth,點亮背光的操作了,沒有keyguard unlock的操作,這樣keyguard unlock的200-300ms直接為0 了!一想這樣明顯會有問題啊,息屏了沒上鎖,我按power鍵類似的亮屏不是不用解鎖就進入系統了麼?對沒錯,所以需要把這些地方堵住,在非指紋冷屏解鎖亮屏的條件下亮屏會先去給keyguard上鎖再亮屏,這樣非指紋冷屏解鎖的亮屏就一樣會看到keyguard鎖。
收到指紋中斷:
08-30 10:09:20.251 2054 6017 D btl_algo: -- btl_api_waitSignal 08-30 10:09:20.251 2054 6017 D btl_algo: Finger is pressed 08-30 10:09:20.251 2054 6017 D fingerprintd: onAcquired(0)
...
比對成功,耗時:217ms
08-30 10:09:20.468 2054 6017 D fingerprintd: onAuthenticated(fid=2, gid=0) 08-30 10:09:20.469 2399 2399 D PowerManagerNotifier: onUserActivity: event=2, uid=1000 ...
亮背光: 08-30 10:09:20.472 3447 3461 D btl_jni : FpEnBlackLight 08-30 10:09:20.472 3447 3461 D btl_jni : User_EnBlackLight enable: 1
整個流程時間耗費:221ms,非常之快.
這裡看到它自己呼叫jni去亮背光,我們知道要點亮背光led之前需要wakeup顯示系統才可以成功,所以這裡推斷它是在收到指紋中斷的時候就去執行wakeup的操作,可由於log太少,所以沒有看到相關的log資訊.
3.2 某品牌機2
某品牌機2也做了很多優化跟某品牌機1類似,收到irq後報個key==304上來,然後直接wakeup系統:
01-03 02:40:38.105 1470 1856 D WindowManager: interceptKeyTq keycode=304 interactive=false keyguardActive=true policyFlags=2000000 01-03 02:40:38.105 1470 1856 D WindowManager: isPhicalHomeKey = false ,result = 0 01-03 02:40:38.105 2151 2161 V PowerEffectManager: noteInputState key = 304 01-03 02:40:38.105 1470 1856 D WindowManager: before sendMessage MSG_WAKEUP_BY_WHO_FINGERPRINT 01-03 02:40:38.105 1470 1854 D FingerprintWakeHook: onWakeUpByWho: eventTime:6238453 who:FingerPrint uid:1000 ident:4294967297470
...
01-03 02:40:38.275 787 1912 D fingerprintd: onAuthenticated(fid=265437463, gid=0) --- Auth success!
01-03 02:40:38.285 1983 2507 D KeyguardViewMediator: keyguardDone(true)--- 開始解鎖 .... 01-03 02:40:38.305 1470 1470 I frontfingerprintkey: fingerprintReceiver Receiver action: android.intent.action.USER_PRESENT --解鎖完成 ... 01-03 02:40:38.395 1470 1941 D DisplayPowerState: Updating screen state: state=ON, backlight=39, backlightChanged=true mPowerAssistantMode=0 01-03 02:40:38.395 1470 1941 D BBKTouchScreenService: Set LCD backlight state ON ---- 點亮背光
整體耗時:
指紋資料比對耗時:275-105 == 170 ms
解鎖完成到亮屏耗時:395-285 == 110 ms
整個週期耗時:170 + 110 == 280 ms
解鎖時間總體很快,但比某品牌機1的221ms略低,可是沒有不上鎖啊,說明它還是有解鎖keyguard的時間耗時的,但是它是如何做到這麼快的呢?帶著疑問,嘗試逆向程式碼看會發現:
com/android/server/wm/WindowManagerService.java:
指紋認證成功後會先執行hideKeyguardLocked函式,從上面程式碼猜測是1939行設定取消動畫,1942-1946,1952行設定keyguardWindow為透明,如果是這樣那麼就可以不用管解鎖的“閃動”導致的使用者體驗差的問題了,因為已經透明瞭,對使用者不可見了,這是個很不錯的優化keyguard解鎖時間的思路。
4、任務分解
4.1 LCD resume時間優化
此部分屬於耗時大頭,跟系統亮屏速度強關聯,同時關係到wakeup跟sleep的速度,影響很大,目前我司v12xx系列的機器優化的還不錯,大概是190-240 ms左右,亮屏速度體驗較好。7201/6901系列就非常慢,大概需要330-350 ms,亮屏體驗差。此部分的優化需要對應的LCD驅動工程師配合IC廠工程師聯合除錯.
4.2 TP resume時間優化
系統要正常亮屏顯示,lcd肯定要先準備好,所以lcd resume會影響亮屏時間,但是tp resume跟系統亮滅屏又有什麼關係呢?要知道原因,分析程式碼,看tp resume是如何被call到的。首先找到你手機用的TP驅動是哪一個,這裡是用ft5364i,程式碼在如下位置:
drivers/input/touchscreen/ft5364i/focaltech_core.c
搜尋resume發現執行tp resume的是這個函式:
從函式名字可以猜到,是在執行fb blank的unblank,也就是亮屏的執行緒被“通知”執行的,那為什麼會這樣執行呢?繼續看fb_notifier_callback在哪裡呼叫,首先看到會再tp驅動的probe函式中註冊到fb 的通知鏈中,
那註冊的過程做了什麼呢?進一步深究,如下程式碼:
我們可以看看這個 fb_notifier_list 的定義:
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
典型的訂閱-釋出模式設計,從巨集定義名字可以知道就是一個通知鏈的頭指標,也就是說上面fb_register_client的作用就是將新的這個node(nb)加入到fb_notifier_list頭指標的連結串列中管理起來,這個是我們從函式名字上猜想的結果,實際上是如何呢?繼續看程式碼看具體做了什麼,如下最終會呼叫到這個函式:
其中nl就是上面傳下來的fb_notifier_list頭指標,n就是需要新加入的節點指標,從上面程式碼可以看到一個細節,新節點的插入會根據優先順序來按順序實現,而不是單單從呼叫關係先後而決定的.
上面的分析搞清楚了fb resume是註冊到fb_notifier_list連結串列裡面,那什麼時候執行呢?看如下程式碼:
drivers/video/fbdev/core/fbmem.c
我們知道系統亮滅屏的時候SurfaceFlinger經常會呼叫驅動跑fb_blank的程式碼,亮屏就是unblank引數,滅屏就是blank引數,而這裡就會去呼叫fb_notifier_call_chain函式,看看這個函式:
是不是很眼熟?fb_notifier_list,就是上面看到的註冊試試的那個頭指標,理解了上面的註冊過程,那麼這個也就很容易理解了,就是去依次遍歷每一個註冊到該連結串列的節點,依次回撥notifier_call函式,而TP 的resume就是在這個函式裡面的,所以自然就會影響到fb blank整個函式的執行週期,如果tp resume耗時太久也就自然對亮屏會有影響了。
4.3 指紋解鎖流程調整
4.3.1 調整wakeup策略:
預設的解鎖流程都是序列執行的,沒有有效的利用相同的時間內做更多的事情,現在都是多核CPU,完全可以在收到指紋irq立即去執行wakeup動作,然後等Auth success就直接去點亮背光,這樣做就可以在指紋auth的200ms內一起把wakeup的事情也幹了,而重疊的時間就是省下來來的時間。
4.3.2 背光控制邏輯
直接呼叫Power.wakeu預設就會點亮背光,而觸發irq的時候並不知道auth是否成功,所以wakeup之前需要把背光攔截掉,不能點亮,只有等指紋auth success後才點亮,所以這裡需要更改點亮背光的策略.
4.3.3 Keyguard隱藏
背光提早到keyguard unlock之前亮會引入一個副作用就是會看到keyguard消退的動畫跳變過程,使用者體驗不佳,這個時候就可以藉助vivo-x9的做法了,直接設定為透明解鎖,這樣就可以巧妙的避開這個問題,當然實際開發過程可能會有各種狀態考慮需要細細調整.
5、原始碼實現
5.1 tp resume改為非同步
上面分析了tp resume是跟fb blank同步進行,那麼事實上tp resume時間大概是200ms以下,人的反應時間大概是100-200ms,人眼看到亮屏在到用手去觸控式螢幕幕時間需要至少200+ms以上了,所以可以嘗試改為非同步執行,這樣就可以節省掉tp resume的200ms時間了。程式碼實現很簡單如下:
首先增加一個指標fb_notify_work,在probe函式中建立一個workqueue,INIT_WORK該巨集的本質是建立一個新的核心執行緒,然後再使之與指標fb_notify_work繫結,這樣就可以操作該指標來實現啟動、關閉該執行緒的執行了。
實現workqueue的callback:
修改notifier通知鏈回撥程式碼:
入行程式碼中同步呼叫tp resume改為schedule_work呼叫,該函式本質是執行新的核心執行緒來實現非同步執行,這樣tp resume的執行就跟fb blank不在同一個執行緒了,也就不會影響到亮屏的時間了.
5.2 中斷喚醒
分兩步,其一就是IRQ(中斷)要能觸發,其二就是system_server程序需要接受到這個IRQ訊號,在指紋認證等待模式下,只要手指有觸控指紋模組,那麼自動就會觸發指紋IRQ訊號,所以我們需要做的就是需要新建一個執行緒用於監聽來自指紋驅動上報的IRQ訊號,然後再通過PowerManagerService執行wakeup的動作喚醒系統。
5.2.2 監聽IRQ
圖解:
(1-3)FingerprintService啟動的時候初始化本地封裝類FingerprintNative,然後通過jni呼叫本地函式native_init();
(4)執行路徑路由熬fp_dev_init(),呼叫fp_dev_open();
(5)open(“/dev/fp_drv") 裝置節點,拿到控制代碼dev_fd,註冊非同步監聽該裝置來自核心的訊息,fp_input_handler()是回撥函式;
(6)return;
(7)傳入new FingerprintNative()物件到native層;
(8)通過pthread_create()建立監聽執行緒,監聽來自“/dev/fp_drv”裝置的訊息,該監聽是阻塞執行的。
5.2.3 IRQ上報
圖解:
(1)觸控指紋模組,硬體觸發中斷,被kernel接到,回撥中斷處理函式;
(2)呼叫fp_drv.c裡面的介面,核心空間使用kill_fasync()傳送非同步訊息;
(3)訊息通過系統呼叫介面傳入到使用者空間,被native層的監聽執行緒監聽到,通過JNI回到到java層;
(4)JNI回撥Java層的onReport() 函式;
(5)呼叫Power的wakeUp()函式執行喚醒系統;
5.3 背光策略
5.3.1 背光攔截
預設情況下執行Power的wakeup()就會自動亮背光,但是事實上我們需要改成指紋Auth success才亮背光,所以需要在呼叫wakeup前攔截掉背光控制流程,可以通過改上層或者底層來實現,經過實際測試綜合考慮還是改kernel層更合適,如下核心led介面處新增攔截判斷:
kernel-3.18/drivers/leds/leds.h
kernel-3.18/drivers/input/fingerprint/fp_drv/fp_drv.c
get_bl_ctr_flag()函式實現在fp_drv.c中:
這裡會判斷傳入的led dev,如果是背光 "lcd-backlight" 則需要判斷fp_backlight_control的值是否為1,為1則攔截,否則不攔截,如此通過控制全域性變數fp_backlight_control的值就可以實現背光的攔截操作。
5.3.2 點亮背光
這個就比較簡單了,有兩種方式實現,一種在使用者空間,另外一種在核心空間:
先看使用者空間的實現方式:
#define BL_DEV_ATTR "/sys/class/leds/lcd-backlight/brightness"
也可以再核心空間操作,核心空間實現方法如下:
所有的led裝置初始化的時候都會掛到一個連結串列中,頭指標是leds_list,遍歷這個連結串列找到“lcd-backlight" 那就是背光碟機動,設定需要的值即可.
5.4 keyguard隱藏
實現涉及檔案:
SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
關鍵程式碼:
public void setStatusBarWindowAlpha(int mode ){
2973 synchronized(PhoneStatusBar.this){
2975 fingerHandler.removeMessages(FINGFERPRINT_QUICK_UNLOCK_MSG);
2976 recoveryAlphaState(true);
2977 if(mode == KeyguardUpdateMonitor.MSG_POWER_KEY_PRESS ) {
2979 recoveryAlphaState(false);
2980 return;
2981 }
2983 try {
2984 mScaleFlag = true;
2985 mScale = mWindowManagerService.getAnimationScale(0);
2986 mWindowManagerService.setAnimationScale(0, 0.0f);
2987 } catch (Exception e) {
2988
2989 }
2991 int count = mStatusBarWindow.getChildCount();
2992 for(int i=0 ; i< count ;i++){
2993 View v = mStatusBarWindow.getChildAt(i);
2994 if (v instanceof PhoneStatusBarView) {
2995
2996 } else {
2997 Object obj = v.getTag();
2999 if (obj == null) {
3000 float f;
3001 if(v instanceof BackDropView){
3002 //Some time BackDropView getAlpha= 0.001999,cause bug
3004 f = 1.0f;
3005 } else {
3006 f = v.getAlpha();
3007 }
3008 v.setTag(new Float(f));
3010 }
3011 v.setAlpha(0.0f);
3012 }
3013 }
3014 fingerHandler.sendEmptyMessageDelayed(FINGFERPRINT_QUICK_UNLOCK_MSG, 1500);
3015 }
3016 }
簡析:
LINE 2985-2986:獲取keyguard視窗的動畫值備份mScale,然後設定為0,關閉視窗動畫;
LINE 2994:如果是狀態列或者導航欄的view,則不納入檢視隱藏,保持不變;
LINE 3011:設定透明度為0.0f,隱藏顯示;
LINE 2979:若是按下Power鍵亮屏,則需要恢復動畫顯示,恢復keyguard顯示;
5.5 Power/usb亮背光處理
5.5.1 按Power鍵處理
使用錯誤指紋觸控模組,此刻會wakup系統,此時如果按power鍵,系統是不會亮屏的,因為當前已經wakeup了,這明顯是引入的問題點,所以需要特別針對這樣的情況特殊處理下,主要涉及修改如下:
services/core/java/com/android/server/policy/PhoneWindowManager.java:
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
..
1192 if (isFpQuickWakeUpSupport) {
1193 if (mFpManager == null) {
1194 mFpManager = (FingerprintManager) mContext.getSystemService(Context.FINGERPRINT_SERVICE);
1195 }
1196 if (mFpManager != null && mFpManager.isFingerprintQuickWakeup(1, "power key down")) {
1197 mFpWakeupFlag = true;
1198 return;
1199 }
1200 }
..
private void powerPress(long eventTime, boolean interactive, int count) {
..
1293 if (isFpQuickWakeUpSupport) {
1294 if (mFpWakeupFlag) {
1295 mFpWakeupFlag = false;
1296 return;
1297 }
1298 }
..
services/core/java/com/android/server/fingerprint/FingerprintService.java:
1299 @Override // Binder call
1300 public boolean isFingerprintQuickWakeup(int arg0, String reason) {
1301 if (arg0 == 1 && mCurrFingerprintState == FingerprintManager.FP_TRIGGER_IRQ) {
1302 boolean on = mFpEvent.isBackLightOn();
1303 if (on == false) {
1304 mFpEvent.userActivity("power key press");
1305 mHandler.postDelayed(new Runnable() {
1306 @Override
1307 public void run() {
1308 mFpEvent.bl_ctl_enable(mFpEvent.FP_BL_UNLOCK_AND_TRIGGER, reason);
1309 }
1310 }, 100);
1311 mCurrFingerprintState = 0;
1312 return true;
1313 }
簡析:
LINE 1196:Power down事件會調interceptPowerKeyDown函式,這個時候判斷是否是屬於使用過錯誤指紋觸控過模組,如果是,就直接通過isFingerprintQuickWakeup介面內部實現點亮背光,同時設定標記mFpWakeupFlag=true;
LINE 1294:Power up事件的時會調此函式,此函式主要作用是gotosleep(),如果mFpWakeupFlag=true說明是上面說的這種情況就不執行休眠,直接返回,如此就可以解決前面說的按power無法亮屏的問題.
5.5.2 拔插USB,來電亮屏處理
729 @Override
730 public void onReceive(final Context context, final Intent intent) {
731 final String action = intent.getAction();
732 if (Intent.ACTION_POWER_CONNECTED.equals(action)
741 || Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
742 boolean on = isBackLightOn();
743 if(DEBUG) Log.i(TAG, "#onReceive: ACTION_POWER_CONNECTED/DISCONNECTED: on:"+ on);
744 if (!on) {
745 userActivity("ACTION_POWER_CONNECTED/DISCONNECTED");
746 bl_ctl_enable(FP_BL_UNLOCK_AND_TRIGGER, "ACTION_POWER_CONNECTED/DISCONNECTED");
747 }
748 }
749 else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
750 int state = mTelemanager.getCallState();
751 if(DEBUG) Log.i(TAG, "#onReceive: ACTION_PHONE_STATE_CHANGED:"+state);
752 switch (state) {
753 case TelephonyManager.CALL_STATE_RINGING:
754 case TelephonyManager.CALL_STATE_IDLE:
755 boolean on = isBackLightOn();
756 if (!on) {
757 userActivity("ACTION_PHONE_STATE_CHANGED");
758 bl_ctl_enable(FP_BL_UNLOCK_AND_TRIGGER, "ACTION_PHONE_STATE_CHANGED");
759 }
760 break;
761 }
762 }
..
可以看到,程式碼其實很簡單,就是監聽相關廣播,然後直接設定為亮背光,不再解釋.
總結:
一系列優化除錯後,息屏指紋解鎖亮屏整個耗時在400-500ms左右,提升一倍速率。