優雅的使用ActivityLifecycleCallbacks管理Activity和區分App前後臺

一、ActivityLifecycleCallbacks接口介紹

API 14之後,在Application類中,提供了一個應用生命週期回調的註冊方法,用來對應用的生命週期進行集中管理,這個接口叫registerActivityLifecycleCallbacks,可以通過它註冊自己的ActivityLifeCycleCallback,每一個Activity的生命週期都會回調到這裏的對應方法。之前我們想做類似限制制定Activity個數的時候都要自己去添加和計數,有了ActivityLifeCycleCallback接口,所有Activity的生命週期都會在這裏回調,我們可以根據條件隨心處理。

Activity生命週期圖:
這裏寫圖片描述

ActivityLifecycleCallbacks接口代碼:
這裏寫圖片描述

兩者幾乎是一一對應的,不管是做Activity的限制還是Activity的狀態統計都是非常方便的,裏面還有一個void onActivitySaveInstanceState(Activity activity, Bundle outState) 方法,非常方便我們來保存Activity狀態數據,是不是很周到美滋滋!

二、限制指定Activity的數量

我們需要建立一個集合存儲指定打開的Activity,使用java中的Stack集合最好,Stack堆棧 is a last-in-first-out (LIFO) stack of objects,先進後出很天然的複合Activity堆棧的容器集合:

//這裏我們爲了限制商品詳情頁Activity的最多打開個數
    public static Stack<ActivityDetail> store = = new Stack<>();

在只需要在Application中處理就可以了,請看代碼:

/**
 * Created by dawish on 2017/2/16.
 */
public class App extends Application {
    private static App mApp;
    public static Stack<ActivityDetail> store;
    //商品詳情頁最多個數,這裏爲了測試只寫了2,大家根據自己的情況設值
    private static final int MAX_ACTIVITY_DETAIL_NUM = 2;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        store = new Stack<>();
        //註冊監聽器
        registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
    }

    public static App getAppContext() {
        return mApp;
    }

    private class SwitchBackgroundCallbacks implements ActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
            if(activity instanceof ActivityDetail) {
                if(store.size() >= MAX_ACTIVITY_DETAIL_NUM){
                    store.peek().finish(); //移除棧底的詳情頁並finish,保證商品詳情頁個數最大不超過指定
                }
                store.add((ActivityDetail) activity);
            }
        }

        @Override
        public void onActivityStarted(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {

        }

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

        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            store.remove(activity);
        }
    }

    /**
     * 獲取當前的Activity
     *
     * @return
     */
    public Activity getCurActivity() {
        return store.lastElement(); //返回棧頂Activity
    }
}

寫的App不要忘記在manifest清單文件中註冊一下。

三、控制同一個商品只會有一個ActivitDetail被打開

這個稍微麻煩點,我們需在在打開的商品ActivityDetail之前判斷該商品的詳情頁是否已經在Activity棧中,這樣的情況在電商APP中應該很常見吧,商品之間會互相推薦,我們點擊推薦商品去打開詳情頁,因爲會互相推薦,所以存在一個商品會被重複打開詳情頁。
ActivityDetail的代碼,關鍵的是要保持當前商品的ID,方便區分不同的商品詳情頁:

    private String ID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_detail);
        AnnotateUtils.inject(ActivityDetail.this);

        ID = getIntent().getStringExtra("ID");
        currentGoodId.setText("當前詳情頁展示商品ID: "+ID);

    }

    public String getID() {
        return ID;
    }

如果當前點擊打開的商品詳情頁已經被打開了,我們直接把之前打開的ActivityDetail調到前臺不就行了麼?

    /**
     *
     * @param id
     * @return
     */
    public static boolean toGoodsDetail(String id){

        if(store == null || store.empty()){
            return false;
        }
        for(ActivityDetail activityDetail : store){
            if(id.equalsIgnoreCase(activityDetail.getID())){ //當前商品的詳情頁已經打開就直接調到前臺
                ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
                am.moveTaskToFront(activityDetail.getTaskId(), ActivityManager.MOVE_TASK_NO_USER_ACTION);
                return true;
            }
        }
        return false;
    }

注意: ActivityManager 的moveTaskToFront是你需要在AndroidManifest.xml中添加”Android.permission.STOP_APP_SWITCHES”用戶權限,前提是必須是系統應用纔可以。

扎心不?蛋疼不?
想着實現除非你是手機廠商預裝的App,或者是root掉手機,把你的app放在system/app目錄下面,但是這是不可能的。

最終的解決辦法就是,要打開的商品詳情頁如果已經打開,我們就把之前的關閉掉,然後重新打開,這樣就在 Activity的棧頂重新創建了一個。有點類似把之前打開的Activity調到了棧頂,但是之前的ActivityDetail的狀態你可以自己保存下來傳到到新的ActivityDetail中來恢復現場,做到隱藏式無縫。
在每次打開商品詳情頁之前都調用一下App中的
toGoodsDetail(String id)方法來檢查:

    /**
     *
     * @param id
     * @return
     */
    public static boolean toGoodsDetail(String id){

        if(store == null || store.empty()){
            return false;
        }
        for(ActivityDetail activityDetail : store){
            if(id.equalsIgnoreCase(activityDetail.getID())){ //當前商品的詳情頁已經打開
                activityDetail.finish();
//                這是你需要在AndroidManifest.xml中添加"Android.permission.STOP_APP_SWITCHES"用戶權限,前提是必須是系統應用纔可以。
//                ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
//                am.moveTaskToFront(activityDetail.getTaskId(), 0);
                return true;
            }
        }
        return false;
    }

ActivityDetail中的實現:

/**
 * Created by dawish on 2017/8/10.
 */

public class ActivityDetail extends AppCompatActivity {

    @ViewInject(R.id.recGoods1)
    private Button recGoods1;

    @ViewInject(R.id.recGoods2)
    private Button recGoods2;

    @ViewInject(R.id.recGoods3)
    private Button recGoods3;

    @ViewInject(R.id.currentGoodsId)
    private TextView currentGoodsId;

    private String ID;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_detail);
        AnnotateUtils.inject(ActivityDetail.this);

        ID = getIntent().getStringExtra("ID");
        currentGoodsId.setText("當前詳情頁展示商品ID: "+ID);

    }

    public String getID() {
        return ID;
    }

    @OnClick({R.id.recGoods1, R.id.recGoods2, R.id.recGoods3})
    public void recGoodClick(View v){

        int id = v.getId();

        switch (id){
            case R.id.recGoods1:
                String goodId1 = "101";
                toGoodDetail(goodId1);
                break;
            case R.id.recGoods2:
                String goodId2 = "102";
                toGoodDetail(goodId2);
                break;
            case R.id.recGoods3:
                String goodId3 = "103";
                toGoodDetail(goodId3);
                break;
        }

    }

    /**
     * 根據推薦商品的點擊打開對應的詳情頁
     * @param id
     */
    public void toGoodDetail(String id){
        App.toGoodsDetail(id); //調用App中的方法去檢測點擊的商品詳情頁是否被打開,被打開就將其關閉
        Intent intent = new Intent(ActivityDetail.this, ActivityDetail.class);
        intent.putExtra("ID", id);
        startActivity(intent);
    }

}

效果圖:
這裏寫圖片描述

這樣的話不管我們怎麼點擊下面的推薦商品,同一個商品不會存在多個詳情頁,我們詳情頁的總個數也可以控制。完美!

四、判斷App前後臺狀態

App 前後臺的切換一般情況下都是按Home來進行,當然也有別的方式,但是此時Activity的生命週期是一樣的:

HOME鍵前後臺切換Activity的執行順序:onPause->onStop->onRestart->onStart->onResume

BACK鍵前後臺切換Activity鍵的順序: onPause->onStop->onDestroy->onCreate->onStart->onResume

其實按BACK按鍵就是退出app了,不算是前臺後切換。

現在我們知道App的由前臺切換到後臺所有打開的Activity會走:

onPause->onStop

後臺切換到前臺所有打開的Activity會走:

->onRestart->onStart->onResume

前後臺切換App所有打開的Activity的生命週期都是一樣的,這樣我就可以在ActivityLifecycleCallbacks回調接口中記錄生命週期:

App類最終完整代碼:


/**
 * Created by dawish on 2017/2/16.
 */
public class App extends Application {
    //記錄Activity的總個數
    public int count = 0;
    private static App mApp;
    public static Stack<ActivityDetail> store;
    //商品詳情頁最多個數,這裏爲了測試只寫了2,大家根據自己的情況設值
    private static final int MAX_ACTIVITY_DETAIL_NUM = 2;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        store = new Stack<>();
        registerActivityLifecycleCallbacks(new SwitchBackgroundCallbacks());
    }

    public static App getAppContext() {
        return mApp;
    }

    /**
     *
     * @param id
     * @return
     */
    public static boolean toGoodsDetail(String id){

        if(store == null || store.empty()){
            return false;
        }
        for(ActivityDetail activityDetail : store){
            if(id.equalsIgnoreCase(activityDetail.getID())){ //當前商品的詳情頁已經打開
                activityDetail.finish();
//                這是你需要在AndroidManifest.xml中添加"Android.permission.STOP_APP_SWITCHES"用戶權限,前提是必須是系統應用纔可以。
//                ActivityManager am = (ActivityManager) getAppContext().getSystemService(Activity.ACTIVITY_SERVICE);
//                am.moveTaskToFront(activityDetail.getTaskId(), 0);
                return true;
            }
        }
        return false;
    }

    private class SwitchBackgroundCallbacks implements ActivityLifecycleCallbacks {

        @Override
        public void onActivityCreated(Activity activity, Bundle bundle) {
            if(activity instanceof ActivityDetail) {
                if(store.size() >= MAX_ACTIVITY_DETAIL_NUM){
                    store.peek().finish(); //移除棧底的詳情頁並finish,保證商品詳情頁個數最大爲10
                }
                store.add((ActivityDetail) activity);
            }
        }

        @Override
        public void onActivityStarted(Activity activity) {
            if (count == 0) { //後臺切換到前臺
                Log.v("danxx", ">>>>>>>>>>>>>>>>>>>App切到前臺");
            }
            count++;
        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivityStopped(Activity activity) {
            count--;
            if (count == 0) { //前臺切換到後臺
                Log.v("danxx", ">>>>>>>>>>>>>>>>>>>App切到後臺");
            }
        }

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

        }

        @Override
        public void onActivityDestroyed(Activity activity) {
            store.remove(activity);
        }
    }

    /**
     * 獲取當前的Activity
     *
     * @return
     */
    public Activity getCurActivity() {
        return store.lastElement();
    }
}

Github代碼請點擊

發佈了73 篇原創文章 · 獲贊 155 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章