專案需求討論- 手機鎖屏及APP退到後臺後自動鎖定功能

青蛙要fly發表於2017-07-28

大家好,又到了新一期的專案需求討論,很多APP都有安全的意識,比如一些銀行的APP,你登入後,看一些東西,然後這時候鎖屏了。或者是按了Home鍵退到了後臺,這時候,再啟動這個App,可能就會又到了這個APP的解鎖的介面。或者重新登入的介面。防止安全。


在前面的文章中我介紹過APP第一次開啟登入進去時候的解鎖功能:
專案需求討論-APP手勢解鎖及指紋解鎖
假設我們我們這裡APP的登入用的是手勢解鎖,那麼我們的APP在使用過程中,退到後臺或者鎖屏後,就應該再次出現這個手勢解鎖的介面。也就是:

手勢解鎖
手勢解鎖

還是老話,我寫的方法可能不是最佳的,希望大家輕點噴,哈哈。

我們分情況來看:

1.使用者按了Home鍵或者啟動其他APP等導致當前APP居於後臺:

因為使用者是在操作過程中,把APP退到了後臺,所以我們不可能在特定的某個Activity中去監聽這些使用者退出後臺等操作。所以我們想到了在BaseActivity中做處理。

我們假設使用者在操作我們APP的A介面,A介面呼叫onStop方法有二種情況,一種是你開啟了APP中的其他介面,還有一種就是比如按了Home鍵退到了後臺,所以當onStop呼叫的時候,我們來判斷下,我們的APP當前是不是在還處於前臺,如果還在前臺,就說明只是APP內部開啟了另外一個Activity而已。不然就是處於了後臺。

判斷我們的APP是處於前臺還是後臺有很多介紹,網上也有很多。但是我這裡還是要說一點:
網上很多程式碼都是這樣的:

ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses();  
    for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {  

       if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {  
           for (String activeProcess : processInfo.pkgList) {  
               if (activeProcess.equals(context.getPackageName())) {  
                   isInBackground = false;  
               }  
           }  
       }  
   }複製程式碼

我們可以看到網上很多介紹ActivityManager.getRunningAppProcessed方法說是返回了系統中正在執行的APP的程式,所有拿到的是List,所以然後遍歷一遍,判斷哪個程式處於前端,然後再判斷這個處於前端的程式的包名是不是我們這個APP的名字。網上清一色的介紹也都是這樣,但是在我實際開發中,我發現runningProcess的size一直返回為1。直接就返回了我們的APP的程式,還不是像網上所說的那樣。就算我額外開了好幾個其他APP也還是一樣,返回的size為1,後來查了其他的資料發現了原因:

在Android 5.0+的系統上,getRunningTasks方法和getRunningAppProcess方法返回的都是你的application process。我在測試時候也都驗證了(Android 6.0),的確這二個方法都返回的size都為1。這裡大家可以留個心。說不定以後就碰到這方面問題。

最後我們的程式碼如下所示:

public boolean isApplicationBroughtToBackground(final Context context) {

   ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
   if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
       List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses();
       if(runningProcesses != null && !runningProcesses.isEmpty()){
           if(runningProcesses.get(0).importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND){
               return true;
           }
       }

    }else{
        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
        if (tasks != null && !tasks.isEmpty()) {
            ComponentName topActivity = tasks.get(0).topActivity;
            if (!topActivity.getPackageName().equals(context.getPackageName())) {
                return true;
            }
        }
    }
    return false;
}複製程式碼

然後我們在onRestart方法中進行判斷來決定是否啟動我們的手勢鎖介面:

@Override
protected void onRestart() {
    super.onRestart();

    if (isBackground) {
        isBackground = false;
        Intent intent = new Intent();
        intent.putExtra("isReLock", true);
        intent.setClass(aty, PatternLockActivity.class);
        startActivity(intent);
    }
}複製程式碼

細心的讀者發現,我們這裡intent裡面傳了個引數intent.putExtra("isReLock", true);,為什麼要傳這個值,是這樣的:

  1. 如果你是第一次開啟這個APP。啟動這個手勢介面的時候,你不想進去了。你可以按返回鍵,然後退出了這個APP,但是如果是你在操作我們的APP過程中,因為退到了後臺後再次被鎖定,這時候出來的手勢鎖就不能有響應返回鍵的功能了。除非你重新解鎖,不然按了返回鍵,手勢介面被關閉,豈不是裡面的內容又被看到了。再次啟動的手勢介面也毫無保密功能可言。
  2. 第一次開啟APP的顯示的手勢鎖解鎖成功的時候,是會進行其他登入的程式碼,然後跳到主頁面,而後面再次鎖上的手勢鎖,解鎖成功後,我們只是把它給finish掉而已。所以我們這裡要傳這麼個引數來進行標記。

我們來看下相關的程式碼:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (!isReLock && keyCode == KeyEvent.KEYCODE_BACK) {
        finish();
    }
    return true;
}複製程式碼

2. 使用者對手機進行了鎖屏操作:

我們一般在APP登入成功後,進入到主介面MainActivity,然後通過MainActivity進行相關介面的跳轉及操作,所以一般來說,這個MainActivity是一直存在的。不會說被關閉等,所以我們只需要在主介面處動態註冊廣播,來監聽相關的鎖屏介面即可。

自定義廣播LockStateReceiver.java:

public class LockStateReceiver extends BroadcastReceiver{

    private StateListener listener;

    public LockStateReceiver(StateListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

       if(Intent.ACTION_USER_PRESENT.equals(intent.getAction())){
            if(!App.getBackgroundApp()){
                listener.stateScreenOff();
            }
        }
    }

    public interface StateListener{
        void stateScreenOff();
    }
}複製程式碼

MainActivity.java:

receiver = new LockStateReceiver(this);
if (receiver != null) {
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_USER_PRESENT);
    registerReceiver(receiver, filter);
}複製程式碼

記得在MainActivity.java銷燬時候取消註冊的廣播:

if (receiver != null) {
    unregisterReceiver(receiver);
}複製程式碼

這裡大家可能會問,你不是說好的監聽鎖屏的嗎,怎麼現在監聽的是使用者解鎖的Action了。

是這樣的,我解釋下:

在我的上一篇文章中,我們的使用者可能用的是指紋解鎖的功能,
專案需求討論-APP手勢解鎖及指紋解鎖
如果你在監聽使用者鎖屏動作,然後在接受到鎖屏的廣播時候就去把我們APP的指紋鎖屏介面給調出來,這時候你會發現,你去解鎖手機自帶的鎖屏介面時候,用指紋解鎖無效,因為指紋解鎖的功能已經被我們的APP給挾持過去了。所以反而手機的鎖屏無法用指紋解鎖了。所以我們思路換一下,既然有手機要鎖定,肯定有解鎖的時候,我們只需要監聽手機解鎖動作,然後把我們的APP給鎖定起來即可。

所以我們只需要在接受到Intent.ACTION_USER_PRESENT的廣播後,判斷下當前是不是處於後臺,如果是處於後臺,我們就不需要做處理,為什麼,因為我們的APP處於後臺後,本身就已經有一套機制去呼叫APP鎖定介面,如果我們的APP處於前端,然後手機解鎖後,我們才會去啟動APP的鎖定介面。
所以程式碼才會反過來:

if(!App.getBackgroundApp()){
    listener.stateScreenOff();
}複製程式碼

同樣的,stateScreenOff方法也就是啟動我們的APP的手勢鎖定介面:

public void stateScreenOff() {

    Intent intent = new Intent();
    intent.putExtra("isReLock", true);
    intent.setClass(aty, PatternLockActivity.class);
    startActivity(intent);

}複製程式碼

好了。基本的實現就是這樣。希望大家多提意見。哈哈。。

PS:感謝大家留言,下面也有人說了可以使用ActivityLifecycleCallbacks來執行。。大家也可以參考。o( ̄︶ ̄)o

相關文章