Android 判斷應用前后臺運行狀態并獲取當前 Activity 實例

JackieGrass 8年前發布 | 17K 次閱讀 Activity Android開發 移動開發

判斷應用是否處于運行狀態,如果處于運行狀態是在前臺運行還是在后臺運行,以及如何獲取應用當前展示的 Activity 實例(Activity 堆棧中的Top Activity),是 Android 開發人員經常遇到的問題,特別是在后臺運行的 service 中單憑一個 context 對象處理這些,常見應用場景如消息推送:當推送通知到達客戶端時是否需要展示通知欄消息,當用戶點擊通知欄消息時,如何啟動應用,如何觸發對應 Activity 的跳轉等。

Android 5.0(Lollipop,API 21)之前,我們可以通過 ActivityManager 提供的 getRunningTasks 方法獲取當前設備所有處于運行狀態的應用信息,從而判斷自己的應用的運行狀態,包括topActivity信息,如:

public static ComponentName getTopActivity(Context context){
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Service.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> runningTaskInfoList = activityManager.getRunningTasks(Integer.MAX_VALUE);
    for (ActivityManager.RunningTaskInfo taskInfo : runningTaskInfoList) {
        if (taskInfo.topActivity.getPackageName().equals(context.getPackageName())){
            return taskInfo.topActivity;
        }
    }
    return null;
}

然而,Google 為了提升 Android 系統的安全性,從 5.0 開始,廢棄了 getRunningTasks 方法,不再為開發人員提供類似的服務,僅可作為開發時的調試和展示,源代碼中明確寫有注釋信息:

Note: this method is only intended for debugging and presenting task management user interfaces. This should never be used for core logic in an application, such as deciding between different behaviors based on the information found here. Such uses are not supported, and will likely break in the future. For example, if multiple applications can be actively running at the same time, assumptions made about the meaning of the data here for purposes of control flow will be incorrect.

機智如你,此刻肯定和我一樣,想到了尚未被標注 Deprecated 的 getRunningAppProcesses 方法,獲取處于“運行狀態”的應用進程信息。結合包名和 importance 屬性值的聯合判斷,即可判定應用是否處于前臺運行狀態:

public static boolean isAppRunningForeground(Context context){
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Service.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList = activityManager.getRunningAppProcesses();
    if (runningAppProcessInfoList==null){
        return false;
    }
    for (ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {
        if (processInfo.processName.equals(context.getPackageName())
                && processInfo.importance==ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
            return true;
        }
    }
    return false;
}

注意:一定要添加 processInfo.importance==ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 這句判斷,區分 process 與 task 的區別。實際上,此處后臺服務在運行, processInfo.processName.equals(context.getPackageName()) 一定為true。

不幸的是,如同 getRunningTasks 方法, getRunningAppProcesses 方法在源碼中一樣被注明,該方法僅用于調試和展示性工作:

Note: this method is only intended for debugging or building a user-facing process management UI.

既然系統API出于安全的考慮,已經無法獲取應用的前后臺狀態,只能另辟蹊徑了。比如利用 Activity 的生命周期變化,自定義一個全局變量來保存應用的狀態,然后將當前 Activity 的實例對象保存在全局變量中。

很多人喜歡將這個全局變量放置在自定義的 Application 類中,并且采用強引用的方式定義,這種做法不僅會給 Application 帶來壓力(增加應用的啟動初始化時間等),還會造成內存泄漏。

基于這些考慮,我們可以使用餓漢式單例模式保存 Activity 的實例,如:

public class CustomActivityManager {

    private static AppActivityManager customActivityManager = new CustomActivityManager();
    private WeakReference<Activity> topActivity;

    private CustomActivityManager() {

    }

    public static CustomActivityManager getInstance(){
        return customActivityManager;
    }

    public Activity getTopActivity() {
        if (topActivity!=null){
            return topActivity.get();
        }
        return null;
    }

    public void setTopActivity(Activity topActivity) {
        this.topActivity = new WeakReference<>(topActivity);
    }

}

然后在 Activity 的生命周期函數中賦值修改這個狀態值。有兩種方式,第一種,在自定義的 BaseActivity 中重寫 Activity 的 onResume() 和 onStop() ,然后其他所有 Activity 均繼承自這個 BaseActivity;第二種,借助 API 14 版本引入的 ActivityLifecycleCallbacks 類,在 Application 中注冊使用:

public class CustomApplication extends Application{

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {
                CustomActivityManager.getInstance().setTopActivity(activity);
            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {
                CustomActivityManager.getInstance().setTopActivity(null);
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }
}

注意:再次強調, ActivityLifecycleCallbacks 類是有版本限制的,如果你的應用需要兼容到 API 14版本以下,就不能采取這種方式。

有了這個保存 Activity 狀態的全局變量,就可以在后臺服務中判斷應用的運行狀態,進行其它操作。比如,當消息推送的場景,當用戶點擊通知欄消息時,打開應用:如果應用處于關閉狀態,重新啟動;如果處于后臺運行狀態,打開應用并顯示當前 Activity 即可。參考代碼:

public static void launchApp(Context context){
    Intent intent;
    if (CsjActivityManager.getInstance().getTopActivity()!=null){
        intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    }else{
        intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    context.startActivity(intent);
}

以上便是有關 Android 開發獲取應用的前后臺運行狀態的一些見解,感謝 技術小黑屋 提供的 Activity 狀態保存方式。也許這些還不是最好的解決方案,歡迎大家踴躍留言,提出自己的見解,重在交流。最后,再分享一項黑科技,通過訪問 /proc 文件的方式獲取應用信息,無需任何權限,只是在 Android Nougat 上不起作用。

 

來自:http://yifeng.studio/2016/11/02/android-app-running-status/

 

 本文由用戶 JackieGrass 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!