本文來自於騰訊bugly開發者社區,非經做者贊成,請勿轉載,原文地址:http://dev.qq.com/topic/57875330c9da73584b025873html
鎖屏做爲一種黑白屏時代就存在的手機功能,至今仍發揮着巨大做用,特別是觸屏時代的到來,鎖屏的功用被髮揮到了極致。多少人曾經在無聊的時候每隔幾分鐘劃開鎖屏再關上,孜孜不倦,其酸爽程度不亞於捏氣泡膜。確實,一款漂亮的鎖屏能爲手機增色很多,但鎖屏存在的核心目的主要是三個:保護本身手機的隱私,防止誤操做,在不關閉系統軟件的狀況下節省電量。java
當下,各個款式的手機自帶的系統鎖屏徹底可以知足這些需求,並且美觀程度非凡,那麼開發者爲何仍然須要構建自定義鎖屏呢?讓咱們試想一個場景,一位正在使用音樂播放器聽歌的美女用戶,在沒有播放器自定義鎖屏的狀況下,切換一首歌須要幾步(參考自同類文章):android
點亮手機屏幕程序員
解開系統鎖屏安全
打開音樂播放器微信
切歌再熄滅屏幕app
這時的她估計已經被廣場舞的歌曲騷擾了有10秒,續了10次命,這是咱們程序員不肯意看到的,因此有必要依靠咱們靈活的雙手構建出自定義的音樂鎖屏頁,將切歌過程被壓縮爲兩步:點亮屏幕和切歌,順即可以看看歌詞。若是再加個開啓和關閉自定義鎖屏的開關,就能完美解決用戶的痛點。ide
然而,要實現一個自定義鎖屏是一件繁瑣的事情,由於系統有100種方法讓這個非本地的鎖屏待不下去。可是,人類的智慧是無限的,程序員須要逆流而上。佈局
Android系統實現自定義鎖屏頁的思路很簡單,即在App啓動時開啓一個service,在Service中時刻監聽系統SCREEN_OFF的廣播,當屏幕熄滅時,Service監聽到廣播,開啓一個鎖屏頁Activity在屏幕最上層顯示,該Activity建立的同時會去掉系統鎖屏(固然若是有密碼是禁不掉的)。示意圖以下:測試
道理很簡單,咱們這裏須要討論的是細節。
Service是普通的Service,在應用啓動時直接startService,與應用同一個進程便可。此外,SCREEN_OFF廣播監聽必須是動態註冊的,若是在AndroidManifest.xml中靜態註冊將沒法接收到SCREEN_OFF廣播,這點在Android官方文檔中有明確說明,即須要經過以下代碼註冊:
IntentFilter mScreenOffFilter = new IntentFilter(); mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(mScreenOffReceiver, mScreenOffFilter); 對應的BroadcastReceiver定義以下: private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(NOTIFY_SCREEN_OFF)) { Intent mLockIntent = new Intent(context, LockScreenActivity.class); mLockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(mLockIntent); } } };
關於啓動Activity時Intent的Flag問題,若是不添加FLAG_ACTIVITY_NEW_TASK的標誌位,會出現「Calling startActivity() from outside of an Activity」的運行時異常,畢竟咱們是從Service啓動的Activity。Activity要存在於activity的棧中,而Service在啓動activity時必然不存在一個activity的棧,因此要新起一個棧,並裝入啓動的activity。使用該標誌位時,也須要在AndroidManifest中聲明taskAffinity,即新task的名稱,不然鎖屏Activity實質上仍是在創建在原來App的task棧中。
標誌位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是爲了不在最近使用程序列表出現Service所啓動的Activity,但這個標誌位不是必須的,其使用依狀況而定。
鎖屏的activity內部也要作相應的配置,讓activity在鎖屏時也可以顯示,同時去掉系統鎖屏。固然若是設置了系統鎖屏密碼,系統鎖屏是沒有辦法去掉的,這裏考慮沒有設置密碼的狀況。
典型的去掉系統鎖屏頁的方法是使用KeyguardManager,具體代碼以下:
KeyguardManager mKeyguardManager = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); KeyguardManager.KeyguardLock mKeyguardLock = mKeyguardManager.newKeyguardLock("CustomLockScreen"); mKeyguardLock.disableKeyguard();
其中,KeyguardManager是鎖屏管理類,咱們經過getSystemService()的方式獲取實例對象mKeyguardManager,調用該對象的newKeyguardLock()方法獲取KeyguardManager的內部類KeyguardLock的實例mKeyguardLock,該方法傳入的字符串參數用於標識是誰隱藏了系統鎖屏,最後調用mKeyguardLock的disableKeyguard()方法能夠取消系統鎖屏。
上述方法已經不推薦使用,可使用更好的方法來替代。咱們在自定義鎖屏Activity的onCreate()方法裏設定如下標誌位就能徹底實現相同的功能:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
FLAG_DISMISS_KEYGUARD用於去掉系統鎖屏頁,FLAG_SHOW_WHEN_LOCKED使Activity在鎖屏時仍然可以顯示。固然,不要忘記在Manifest中加入適當的權限:
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
當自定義鎖屏頁最終出如今手機上時,咱們總但願它像系統鎖屏頁那樣屹立不倒,全部的按鍵都不能觸動它,只有經過劃瓶或者指紋才能解鎖,所以有必要對按鍵進行必定程度上的屏蔽。針對只有虛擬按鍵的手機,咱們能夠經過隱藏虛擬按鍵的方式部分解決這個問題,具體方法在後文會介紹。可是當用戶在鎖屏頁底部滑動,隱藏後的虛擬按鍵仍是會滑出,並且若是用戶是物理按鍵的話就必須進行屏蔽了。
Back鍵和Menu鍵能夠經過重寫onKeyDown()方法進行屏蔽:
public boolean onKeyDown(int keyCode, KeyEvent event) { int key = event.getKeyCode(); switch (key) { case KeyEvent.KEYCODE_BACK: { return true; } case KeyEvent.KEYCODE_MENU:{ return true; } } return super.onKeyDown(keyCode, event); }
Home鍵與Recent鍵(調出最近打開應用的按鍵)的點擊事件是在framework層進行處理的,所以onKeyDown與dispatchKeyEvent都捕獲不到點擊事件。關於這兩個按鍵的屏蔽方法,網上相關的資料有不少,有的用到了反射,有的經過改變Window的標誌位和Type等,總的來講這些方法只對部分android版本有效,有的則徹底沒法編譯經過。其實,這麼作的目的無非是爲了實現一個純粹的鎖屏頁,可是這種作法有些多此一舉,容易形成鎖屏頁的異常崩潰,咱們要知足的是用戶在鎖屏頁的快捷操做,Home鍵和Recent鍵無關痛癢,徹底能夠無論,少一些套路,多一點真誠嘛。
作完以上幾步,當屏幕熄滅後,再打開屏幕就可以看到咱們的自定義鎖屏頁了,可是這時候,就算劃破手指也沒法解鎖。因此,接下來要實現劃屏解鎖。
劃瓶解鎖的基本思路很簡單,當手指在屏幕上滑動時,攔截並處理滑動事件,使鎖屏頁面隨着手指運動,當運動到達必定的閥值時,用戶手指鬆開手指,鎖屏頁自動滑動到屏幕邊界消失,若是沒有達到運動閥值,就會自動滑動到起始位置,從新覆蓋屏幕。
爲了將劃屏邏輯與頁面內容隔離開來,咱們在鎖屏頁面佈局中添加一個自定義的UnderView,這個UnderView填充整個屏幕,位於鎖屏內容View(將其引用稱之爲mMoveView,並傳入到UnderView中)的下方,全部劃屏相關的事件都在這裏攔截並處理。
mMoveView是鎖屏頁的顯示內容,除了處理一些簡單的點擊事件,其餘非點擊事件序列都由底層的UnderView進行處理。只須要重寫UnderView的onTouchEvent方法就可以實現:
Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); final float nx = event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: mStartX = nx; onAnimationEnd(); case MotionEvent.ACTION_MOVE: handleMoveView(nx); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: doTriggerEvent(nx); break; } return true; }
其中,mStartX記錄滑動操做起始的x座標,handleMoveView方法控制mMoveView隨手指的移動,doTriggerEvent處理手指離開後mMoveView的移動動畫。兩個方法的定義以下:
private void handleMoveView(float x) { float movex = x - mStartX; if (movex < 0) movex = 0; mMoveView.setTranslationX(movex); float mWidthFloat = (float) mWidth;//屏幕顯示寬度 if(getBackground()!=null){ getBackground().setAlpha((int) ((mWidthFloat - mMoveView.getTranslationX()) / mWidthFloat * 200));//初始透明度的值爲200 } }
在handleMoveView()中,首先計算當前觸點x座標與初始x座標mStartX的差值movex,而後調用mMoveView的setTranslationX方法移動。值得注意的是,目前setTranslationX方法只能在Android 3.0以上版本使用,若是採用動畫兼容庫nineoldandroid中ViewHelper類提供的setTranslation方法,則沒有這個問題。scrollTo與scrollBy也能夠實現移動,可是隻是移動View的內容,並不能移動View自己。另外就是經過修改佈局參數LayoutParams實現移動,雖然沒有版本的限制,用起來相對複雜。這裏咱們採用setTranslationX,爲了簡潔,也是爲了可以與後續使用的屬性動畫相統一。
此外,咱們能夠經過getBackground()獲取UnderView的背景,並根據已劃開屏幕佔整個屏幕的百分比調用setAlpha方法改變背景的透明度,作出抽屜拉開時的光影變化效果。
private void doTriggerEvent(float x) { float movex = x - mStartX; if (movex > (mWidth * 0.4)) { moveMoveView(mWidth-mMoveView.getLeft(),true);//自動移動到屏幕右邊界以外,並finish掉 } else { moveMoveView(-mMoveView.getLeft(),false);//自動移動回初始位置,從新覆蓋 } } private void moveMoveView(float to,boolean exit){ ObjectAnimator animator = ObjectAnimator.ofFloat(mMoveView, "translationX", to); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if(getBackground()!=null){ getBackground().setAlpha((int) (((float) mWidth - mMoveView.getTranslationX()) / (float) mWidth * 200)); } } });//隨移動動畫更新背景透明度 animator.setDuration(250).start(); if(exit){ animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mainHandler.obtainMessage(LockScreenActivity.MSG_LAUNCH_HOME).sendToTarget(); super.onAnimationEnd(animation); } }); }//監聽動畫結束,利用Handler通知Activity退出 }
當手指離開屏幕,doTraiggerEvent方法會對滑動的距離與閥值進行一個比較,此處的閥值爲0.4*屏幕寬度,若是低於閥值,則經過ObjectAnimator在0.25s將mMoveView移動到初始位置,同時在ObjectAnimator的AnimatorUpdateListener的onAnimationUpdate方法中更新背景透明度;若是低於閥值,以一樣的方式將mMoveView移出屏幕右邊界,而後將Activity幹掉,具體作法是爲animator增長一個AnimatorListenerAdapter的監聽器,在該監聽器的onAnimationEnd方法中使用在Activity中定義的mHandler發送finish消息,完成解鎖,效果以下圖:
沉浸模式與透明欄是兩個不一樣的概念,因爲某些緣由,國內一些開發或產品會把這兩個概念混淆。不過不要緊,在接下來的內容咱們會對這兩個概念進行詳細的解釋和區分,並應用這兩種不一樣的模式進一步完善已經初具模樣的鎖屏頁。
什麼是沉浸模式?從4.4開始,Android 爲 「setSystemUiVisibility()」方法提供了新的標記 「SYSTEM_UI_FLAG_IMMERSIVE」以及」SYSTEM_UI_FLAG_IMMERSIVE_STIKY」,就是咱們所談的沉浸模式,全稱爲 「Immersive Full-Screen Mode」,它可使你的app隱藏狀態欄和導航欄,實現真正意義上的全屏體驗。
以前 Android 也是有全屏模式的,主要經過」setSystemUiVisibility()」添加兩個Flag,即」SYSTEM_UI_FLAG_FULLSCREEN」,」SYSTEM_UI_FLAG_HIDE_NAVIGATION」(僅適用於使用導航欄的設備,即虛擬按鍵)。
這兩個標記都存在一些問題,例如使用第一個標記的時候,除非 App 提供暫時退出全屏模式的功能(例如部分電子書軟件中點擊一次屏幕中央位置),用戶是一直都無法看見狀態欄的。這樣,若是用戶想去看看通知中心有什麼通知,那就必須點擊一次屏幕,顯示狀態欄,而後才能調出通知中心。
而第二個標記的問題在於,Google 認爲導航欄對於用戶來講是十分重要的,因此只會短暫隱藏導航欄。一旦用戶作其餘操做,例如點擊一次屏幕,導航欄就會立刻被從新調出。這樣的設定對於看圖軟件,視頻軟件等等沒什麼大問題,可是對於遊戲之類用戶須要常常點擊屏幕的 App,那就幾乎是悲劇了——這也是爲何你在 Android 4.4 以前找不到什麼全屏模式會自動隱藏導航欄的應用。
Android 4.4 以後加入的Immersive Full-Screen Mode 容許用戶在應用全屏的狀況下,經過在原有的狀態欄/導航欄區域內作向內滑動的手勢來實現短暫調出狀態欄和導航欄的操做,且不會影響應用的正常全屏,短暫調出的狀態欄和導航欄會呈半透明狀態,而且在一段時間內或者用戶與應用內元素進行互動的狀況下自動隱藏,沉浸模式的四種狀態以下圖。(參考http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0616/3047.html)
狀態1表明沒有進入沉浸模式時頁面的狀態,仍然能夠看到Status Bar和Navigation Bar;狀態2表明用戶第一次進入沉浸模式時,系統的提示彈窗,告訴用戶如何在沉浸模式下呼出Status Bar和Navigation Bar;狀態3表明沉浸模式,能夠看到Status Bar和Navigation Bar都被隱藏;狀態4表明用戶在Sticky沉浸模式下呼出Status Bar和Navigation Bar,能夠看到兩個Bar從新出現,可是過一段時間可以自動隱藏。
通常來講,沉浸模式的標記與其餘Full Screen相關的Flag搭配起來才能達到咱們想要的效果,即經過沉浸模式標記規定狀態欄status bar和導航欄navigation bar顯示和隱藏的運轉邏輯,經過其餘標籤設定狀態欄和導航欄顯示或隱藏,以及顯示或隱藏的樣子。這些常見的Flag及相應功能以下表:
如此多的標籤,看起來很是亂,但用起來卻很是簡單和明確,感興趣的開發者能夠自由搭配來測試一下。下面,咱們經過一個例子,將這些標籤應用於鎖屏頁,實現對Navigation Bar的自動隱藏,同時保留Status Bar。代碼很是簡單,在Activity的onCreate()方法中使用:
getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION );
總共用到了5個Flag:SYSTEM_UI_FLAG_LAYOUT_STABLE保持整個View穩定,使View不會由於SystemUI的變化而作layout;SYSTEM_UI_FLAG_IMMERSIVE_STIKY,可以在隱藏的bar被呼出時(好比從屏幕下邊緣開始向上作滑動手勢),使bar在無相關操做的狀況下自動再次隱藏;對於SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,開發者容易被其中的HIDE_NAVIGATION所迷惑,其實這個Flag沒有隱藏導航欄的功能,只是控制導航欄浮在屏幕上層,不佔據屏幕布局空間;SYSTEM_UI_FLAG_HIDE_NAVIGATION,纔是可以隱藏導航欄的Flag;SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隱藏狀態欄,只是使狀態欄浮在屏幕上層。
須要注意的是,這段代碼除了須要加在Activity的OnCreate()方法中,也要加在重寫的onWindowFocusChanged()方法中,在窗口獲取焦點時再將Flag設置一遍,不然可能致使沒法達到預想的效果。
Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if(hasFocus){ getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ); } }
此外,有個部份要稍微留意一下,若是不但願界面的內容被上拉到狀態欄(Status bar)的話,要記得在界面(Layout)XML文件中,在最外層Layout中將fitsSystemWindows屬性設置爲true。以下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- Content --> </RelativeLayout>
設置了前文的5個Flag以後,鎖屏頁效果圖以下:
手指在屏幕底端上劃,Navigation Bar會彈出,懸浮於鎖屏頁底部,隨後自動消失。Status Bar也按照咱們預期的那樣,懸浮在上方,沒有隱藏。
什麼是透明欄?Google 在 Android 4.4 的 API 描述頁面裏提到了「Translucent system UI styling」,即半透明化的系統UI風格。這個「半透明化」包括了狀態欄和通知欄,當開發者讓應用支持這個新特性的時候,狀態欄和導航欄能夠單獨/同時變爲漸變的半透明樣式,以下圖:
在 Android 5.0 以後引入了 Material Design,狀態欄和導航欄也玩出了更多花樣。如今除了原有的「半透明」模式之外,還有「全透明」以及「變色」模式,一種會徹底隱藏背景,另外一種能夠取色做爲背景顏色,多種樣式的透明欄以下圖(上圖爲透明狀態欄,下圖爲透明導航欄):
因此,透明欄只是可以改變狀態欄和導航欄的顏色,並不像沉浸模式那樣隱藏狀態欄和導航欄,二者是有本質區別的。
對於Android 4.4以上5.0如下的版本,設置透明狀態欄的方式以下:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }
對於Android 5.0及以上版本,設置透明狀態欄的方法以下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView() .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(0); }
除了要清理掉4.4的FLAG_TRANSLUCENT_STATUS外,還要配合SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE,添加標誌位FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,並調用setStatusBarColor設置狀態欄的顏色爲透明。
在綜合運用了沉浸模式和透明欄以後,鎖屏頁效果以下:
到這裏,咱們的鎖屏頁已經基本完工,徹底可以很是優雅地解決用戶的痛點,可是跟當下App自定義鎖屏頁的區別並不明顯。接下來對新型號手機廣泛具有的指紋解鎖功能的考慮,則可以爲鎖屏頁增色很多。
持有指紋解鎖手機的用戶在使用App自定義鎖屏頁時會出現一種困惑,當你點亮屏幕,可以看到自定義鎖屏頁,在使用指紋解鎖成功以後(部分機型指紋解鎖操做只能在系統鎖屏頁進行),自定義鎖屏頁依然存在,你仍是須要劃開自定義鎖屏頁,才能看到手機主界面。
解決這一問題的方案是一種取巧的方法,那就是在鎖屏頁的service中監聽ACTION_USER_PRESENT廣播。ACTION_USER_PRESENT廣播是系統鎖屏解鎖廣播,當系統鎖屏頁解鎖時就會觸發。若是在接收到這一廣播時,將自定義鎖屏頁finish掉,就能避免在指紋解鎖成功後自定義鎖屏頁仍然顯示的問題。可是細心的讀者會發現這種解法在邏輯上還存在問題,由於在用戶沒有設置鎖屏密碼的狀況下,前文自定義鎖屏頁在onCreate()時設置的FLAG_DISMISS_KEYGUARD標誌位可以輕易解鎖系統的鎖屏頁,並觸發ACTION_USER_PRESENT廣播,此時自定義鎖屏頁的Service接收到這一廣播後,發finish廣播給自定義鎖屏頁,致使自定義鎖屏頁剛create就finish掉了,永遠不可能出現。
所以,咱們必須對場景進行區分,只在有鎖屏密碼的狀況下,纔對接收到的ACTION_USER_PRESENT廣播進行處理,finish自定義鎖屏頁。即在BroadcastReceiver的onReceive()方法中加入以下代碼:
if(intent.getAction().equals(Intent.ACTION_USER_PRESENT)) { if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (km.isKeyguardSecure()) { MLog.d(TAG, "KeyguardSecure!"); Intent i = new Intent(NOTIFY_USER_PRESENT); context.sendBroadcast(i); } } }
這裏KeyguardManager對象km的isKeyguardSecure()方法就是用來判斷是否設置了鎖屏密碼。NOTIFY_USER_PRESENT是自定義廣播,用來通知鎖屏頁Activity調用finish方法。
這種作法是合理的,由於若是沒有設置鎖屏密碼,FLAG_DISMISS_KEYGUARD標誌位解鎖系統鎖屏以後,到達上述代碼塊,isKeyguardSecure()返回爲false,不會致使自定義鎖屏頁Activity的finish操做。而若是設置了鎖屏密碼,FLAG_DISMISS_KEYGUARD必然沒法解鎖系統鎖屏,到達不了上述代碼塊,也不會finish。這樣就避免了自定義鎖屏頁剛建立出來就將本身finish掉的困境。另外一方面,其餘非FLAG_DISMISS_KEYGUARD方式觸發的解鎖,好比指紋解鎖,都會使Activity消失,知足了需求。
此外,有些手機型號,好比小米,在自定義鎖屏頁罩在系統鎖屏頁之上時(設置有鎖屏密碼),指紋解鎖是無效的,也就是必需要劃開自定義鎖屏頁,在系統鎖屏頁上才能進行指紋解鎖。爲了改善這種體驗,咱們能夠在Activity中引入指紋解鎖API,識別指紋並解鎖,具體代碼以下:
private void startFingerPrintListening() { if (!isFingerprintAuthAvailable()) { return; } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED) { mFingerprintManager.authenticate(null, mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { super.onAuthenticationSucceeded(result); finish(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); } }, null); return; } } } } public boolean isFingerprintAuthAvailable() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { mKeyguardManager = (KeyguardManager) getSystemService(Activity.KEYGUARD_SERVICE); if(!mKeyguardManager.isKeyguardSecure()){ return false; } if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED) { mFingerprintManager = (FingerprintManager) getSystemService(Activity.FINGERPRINT_SERVICE); mCancellationSignal = new CancellationSignal(); return mFingerprintManager.isHardwareDetected()&&mFingerprintManager.hasEnrolledFingerprints(); }else{ return false; } }else{ return false; } }
固然,不要忘記在Manifest中加入適當的權限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
在調用指紋識別功能以前,咱們須要判斷指紋識別功能是否可用,以及APP是否有相應的權限。這一過程體如今isFingerprintAuthAvailable()中,第一步是獲取KeyguardManager對象,調用isKeyguardSecure()判斷是否設置有鎖屏密碼,若是有,則需進一步判斷。checkSelfPermission用來判斷APP是否有指紋識別的權限(SDK 23要求),若是有則獲取FingerprintManager對象,調用該對象的isHardwareDetected()方法判斷指紋識別硬件是否可用,調用hasEnrolledFingerprints()判斷是否有事先錄入好的指紋,只有以上條件都知足,接下來才能調用指紋識別功能。
指紋識別的調用體如今startFingerPrintListening()方法中,主要就是調用FingerprintManager的方法
authenticate(FingerprintManager.CryptoObject crypto, CancellationSignal cancel, int flags, FingerprintManager.AuthenticationCallback callback, Handler handler)
其中,crypto參數表明Android6.0中crypto objects的wrapper class,能夠經過該對象使authenticate過程更加安全,也能夠不使用,這裏咱們將其設爲null;cancel用來取消anthenticate(),咱們new出一個對象傳入就能夠;flags是標誌位,設置爲0;callback爲指紋識別回調,包含指紋識別的核心方法:onAuthenticationError()是指紋匹配連續失敗後的回調(幾十秒後才能繼續匹配),onAuthenticationSucceeded()是指紋匹配成功的回調,onAuthenticationFailed()是指紋匹配失敗時的回調。咱們在這幾個方法中作相應的處理便可,在onAuthenticationSucceeded()方法中調用finish(),就可以在指紋識別成功後關閉Activity。
經過以上內容的分享,本鵝但願可以對你們的開發有所幫助,若是內容有問題,也但願你們指點。綜上所述,在Android上實現自定義鎖屏頁並非一件複雜的事情,關鍵是對一些技術點的把握要比較清楚。Service中啓動Activity的正確方法,廣播靜態註冊與動態註冊的差異,touch事件的分發傳播機制,透明欄與沉浸模式的綜合運用,以及指紋識別新技術的應用,都有不少值得推敲的地方。筆者當初實現自定義鎖屏頁時,沒有太多思考,有時照搬前人的作法,有時各類flag隨便添加,有時新舊API混淆,雖然實現了需求,可是代碼不夠簡潔,可讀性也差。所以,在從此的開發過程當中,除了要快速實現需求,還要在隨後的維護中,多多思考和研究,使代碼可以達到「少一行不行,多一行難受」的境界。
更多精彩內容歡迎關注bugly的微信公衆帳號: