Android 應用技巧: 手把手教你 優雅實現 “一鍵退出 App”

Carson_Ho發表於2018-01-02

Android 應用技巧: 手把手教你 優雅實現 “一鍵退出 App”


前言

  • Android開發中,會經常存在 “一鍵退出App” 的需求
  • 但市面上流傳著 太多不可用的“一鍵退出App”功能實現
  • 本文將全面總結“一鍵退出App”的實現方式,併為你一一實踐,希望你們會喜歡。

目錄

示意圖


1. 需求本質

一鍵退出 App 其實是 兩個需求:

  1. 一鍵結束當前App所有的Activity
  2. 一鍵結束當前App程式

即 需要2個步驟 才可 完成 一鍵退出 App 需求。下面,我將根據這兩個步驟進行功能實現講解。


2. 功能實現

2.1 (步驟1)一鍵結束當前 App 所有 Activity

2.1.1 實現方法型別
  • 主要分為2類:通過 Android元件 & 自身實現
  • 具體如下圖:

示意圖

注:上述方法僅僅只是結束當前App所有的Activity (在使用者的角度確實是退出了 App),但實際上該App的程式還未結束。

2.1.2 具體介紹

a. 通過 Android 元件:Activity

方法1:採用Activity啟動模式:SingleTask

  • 原理

    1. App的入口 Activity 採用 SingleTask 啟動模式

    a. 入口 Activity 此時處於棧底 b. 關於 SingleTask的原理如下:

    singleTask

    1. 當需要退出 App時啟動入口 Activity

    此時入口 Activity 上層的Activity例項都將自動關閉移除 & 自身被放置在棧頂(這是SingleTask啟動模式的特點)

    1. 通過在入口 Activity 回撥的onNewIntent()中關閉自身即可

    若在後面的Activity啟動 任務棧底的Activity時,就會呼叫任務棧底ActivityonNewIntent()

  • 具體實現

步驟1:將 App的入口 Activity 設定成 SingleTask 啟動模式

// AndroidMainifest.xml中的Activity配置進行設定

<activity

android:launchMode="singleTask"
//屬性
//standard:標準模式
//singleTop:棧頂複用模式
//singleTask:棧內複用模式
//singleInstance:單例模式
//如不設定,Activity的啟動模式預設為 標準模式(standard)
</activity>
複製程式碼

步驟2:在入口 Activity重寫 onNewIntent()

// 在該方法傳入一標誌位標識是否要退出App & 關閉自身
  @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (intent != null) {
            // 是否退出App的標識
            boolean isExitApp = intent.getBooleanExtra("exit", false);
            if (isExitApp) {
                // 關閉自身
                this.finish();
            }
        }
    }
複製程式碼

步驟3:在需要退出時呼叫 exitApp()

private void exitApp() {
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("exit", true);
        context.startActivity(intent);

         // 結束程式
        // System.exit(0);
    }
複製程式碼
  • 優點 使用簡單 & 方便

  • 缺點

    1. 規定 App的入口Activity採用SingleTask啟動模式
    2. 使用範圍侷限:只能結束當前任務棧的Activity,若出現多工棧(即採用SingleInstance啟動模式)則無法處理
  • 應用場景 Activity單任務棧


方法2:採用Activity啟動標記位

  • 原理:對入口Activity採用 2 標記位:

    1. Intent.FLAG_ACTIVITY_CLEAR_TOP:銷燬目標Activity和它之上的所有Activity,重新建立目標Activity
    2. Intent.FLAG_ACTIVITY_SINGLE_TOP:若啟動的Activity位於任務棧棧頂,那麼此Activity的例項就不會重建,而是重用棧頂的例項( 呼叫onNewIntent()
  • 具體使用(從MainActivity(入口Activity) 跳轉到 Activity2 & 一鍵退出)

步驟1:在MainActivity 中設定 重寫 onNewIntent() MainActivity.java

        // 設定 按鈕 跳轉到Activity2
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, Activity2.class));

            }

        });
    }

    // 在onNewIntent()傳入一識別符號
    // 作用:標識是否要退出App
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (intent != null) {
            // 是否退出App的標識
            boolean isExitApp = intent.getBooleanExtra("exit", false);
            if (isExitApp) {
                // 關閉自身
                this.finish();
            }
        }
        // 結束程式
        // System.exit(0);
    }
}
複製程式碼

步驟2:在需要退出的地方(Activity2)啟動MainActivity & 設定標記位

// 當需要退出時,啟動入口Activity
                Intent intent = new Intent();
                intent.setClass(Activity2.this, MainActivity.class);

                // 設定標記位
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                // 步驟1:該標記位作用:銷燬目標Activity和它之上的所有Activity,重新建立目標Activity

                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                // 步驟2:若啟動的Activity位於任務棧棧頂,那麼此Activity的例項就不會重建,而是重用棧頂的例項( 呼叫例項的 onNewIntent() )

                // 在步驟1中:MainActivity的上層的Activity2會被銷燬,此時MainActivity位於棧頂;由於步驟2的設定,所以不會新建MainActivity而是重用棧頂的例項&呼叫實onNewIntent()

                // 傳入自己設定的退出App標識
                intent.putExtra("exit", true);

                startActivity(intent);
複製程式碼
  • 優點 使用簡單 & 方便

  • 缺點 使用範圍侷限:只能結束當前任務棧的Activity,若出現多工棧(即採用SingleInstance啟動模式)則無法處理

  • 應用場景 Activity單任務棧


方法3:通過系統任務棧

  • 原理:通過 ActivityManager 獲取當前系統的任務棧 & 把棧內所有Activity逐個退出

  • 具體使用

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        
        // 1. 通過Context獲取ActivityManager
        ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);

        // 2. 通過ActivityManager獲取任務棧
        List<ActivityManager.AppTask> appTaskList = activityManager.getAppTasks();
        
        // 3. 逐個關閉Activity
        for (ActivityManager.AppTask appTask : appTaskList) {
            appTask.finishAndRemoveTask();
        }
        // 4. 結束程式
        // System.exit(0);
複製程式碼
  • 優點 使用簡單、方便

  • 缺點

    1. 使用範圍侷限:只能結束當前任務棧的Activity,若出現多工棧(即採用SingleInstance啟動模式)則無法處理
    2. Android 版本要求較高:Android 5.0以上
  • 應用場景 Android 5.0以上的 Activity單任務棧


b. 通過 Android 元件: BroadcastReceiver

即使用 BroadcastReceiver 廣播監聽

  • 原理:在每個 Activity 裡註冊廣播接收器(響應動作 = 關閉自身);當需要退出 App 時 傳送廣播請求即可

  • 具體實現

步驟1:自定義廣播接收器

public class ExitAppReceiver extends BroadcastReceiver {
    private Activity activity;

    public ExitAppReceiver(Activity activity){
        this.activity = activity;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        activity.finish();
    }
}
複製程式碼

步驟2:在每個 Activity 裡註冊廣播接收器(響應動作 = 關閉自身)

public class Activity extends AppCompatActivity {

private  ExitAppReceiver mExitAppReceiver;

// 1. 在onCreate()中註冊廣播接收器
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mExitAppReceiver = new ExitAppReceiver(this);
        registerReceiver(mExitAppReceiver,new IntentFilter(BaseApplication.EXIT));
    }

// 1. 在onDestroy()中登出廣播接收器
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mExitAppReceive);
    }

複製程式碼

步驟3:當需要退出App時 傳送廣播請求

context.sendBroadcast(new Intent(BaseApplication.EXIT));
// 注:此處不能使用:System.exit(0);結束程式
// 原因:傳送廣播這個方法之後,不會等到廣播接收器收到廣播,程式就開始執行下一句System.exit(0),然後就直接變成執行System.exit(0)的效果了。
複製程式碼
  • 優點 應用場景廣泛:兼顧單 / 多工棧 & 多啟動模式的情況

  • 缺點 實現複雜:需要在每個 Activity 裡註冊廣播接收器

  • 應用場景 任意情況下的一鍵退出 App,但無法終止 App 程式

所以該方法僅僅是在使用者的角度來說 “一鍵退出App”


c. 自身實現

方法1:建立 連結串列
  • 原理:通過在Application子類中建立一個 Activity連結串列:儲存正在執行的Activity例項;當需要一鍵退出App時把連結串列內所有Activity例項逐個退出即可

  • 具體使用

步驟1:在BaseApplication類的子類裡建立Activity連結串列

Carson_BaseApplicaiton.java

public class Carson_BaseApplicaiton extends Application {

    // 此處採用 LinkedList作為容器,增刪速度快
    public static LinkedList<Activity> activityLinkedList;


    @Override
    public void onCreate() {
        super.onCreate();

        activityLinkedList = new LinkedList<>();

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.d(TAG, "onActivityCreated: " + activity.getLocalClassName());
                activityLinkedList.add(activity);
                // 在Activity啟動時(onCreate()) 寫入Activity例項到容器內
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.d(TAG, "onActivityDestroyed: " + activity.getLocalClassName());
                activityLinkedList.remove(activity);
                // 在Activity結束時(Destroyed()) 寫出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 outState) {

            }


        });
    }
    
    public  void exitApp() {

        Log.d(TAG, "容器內的Activity列表如下 ");
        // 先列印當前容器內的Activity列表
        for (Activity activity : activityLinkedList) {
            Log.d(TAG, activity.getLocalClassName());
        }

        Log.d(TAG, "正逐步退出容器內所有Activity");

        // 逐個退出Activity
        for (Activity activity : activityLinkedList) {
            activity.finish();
        }
      
        //  結束程式
        // System.exit(0);
    }
}

// 記得在Manifest.xml中新增
<application
        android:name=".Carson_BaseApplicaiton"
        ....
</application>
複製程式碼

步驟2:需要一鍵退出 App 時,獲取該 Applicaiton類物件 & 呼叫exitApp()

                private Carson_BaseApplicaiton app;

                app = (Carson_BaseApplicaiton)getApplication();
                app.exitApp();


複製程式碼
  • 效果圖

示意圖

示意圖

  • 優點 應用場景廣泛:兼顧單 / 多工棧 & 多啟動模式的情況

  • 缺點 需要 Activity 經歷正常的生命週期,即建立時呼叫onCreate(),結束時呼叫onDestroy()

因為只有這樣經歷正常的生命週期才能將 Activity正確寫入 & 寫出 容器內

  • 應用場景 任意情況下的一鍵退出 App 實現

方法2:RxBus

  • 原理:使用 RxBus當作事件匯流排,在每個 Activity裡註冊RxBus訂閱(響應動作 = 關閉自身);當需要退出App時 傳送退出事件請求即可。

  • 具體使用

步驟1:在每個 Activity裡註冊RxBus訂閱(響應動作 = 關閉自身)

public class Activity extends AppCompatActivity {
  private Disposable disposable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity2);
        
        // 註冊RxBus訂閱
        disposable = RxBus.getInstance().toObservable(String.class)
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(String s) throws Exception {
                        // 響應動作 = 關閉自身
                        if (s.equals("exit")){
                            finish();
                        }
                    }
                });
    }

// 注意一定要取消訂閱
 @Override
    protected void onDestroy() {
        if (!disposable.isDisposed()){
              disposable.dispose();;
        }
}
複製程式碼

步驟2:當需要退出App時 傳送退出事件

        RxBus.getInstance().post("exit");
        System.exit(0);
複製程式碼
  • 優點 可與 RxJava & RxBus 相結合

  • 缺點 實現複雜:RxBus 本身的實現難度 & 需要在每個Activity註冊和取消訂閱 RxBus 使用

  • 應用場景 需要與RxJava 結合使用時

若專案中沒有用到RxJava & RxBus 不建議使用

  • 至此,一鍵結束當前 App的所有 Activity的 方法 講解完畢。
  • 注:上述方法僅僅只是結束當前App所有的Activity (在使用者的角度確實是退出了 App),但實際上該App的程式還未結束

2.2 (步驟2)一鍵結束當前 App 程式

主要採用 Dalvik VM本地方法

  • 作用 結束當前 Activity & 結束程式

即 在 (步驟1)結束當前 App 所有的 Activity 後,呼叫該方法即可一鍵退出 App(更多體現在結束程式上)

  • 具體使用
// 方式1:android.os.Process.killProcess()
  android.os.Process.killProcess(android.os.Process.myPid()) ;

// 方式2:System.exit()
// System.exit() = Java中結束程式的方法:關閉當前JVM虛擬機器
  System.exit(0);  

// System.exit(0)和System.exit(1)的區別
  // 1. System.exit(0):正常退出;
  // 2. System.exit(1):非正常退出,通常這種退出方式應該放在catch塊中。 
複製程式碼
  • 特別注意 假設場景:當前 Activity ≠ 當前任務棧最後1個Activity時,呼叫上述兩個方法會出現什麼情況呢?(即Activity1 - Activity2 -Activity3(在Activity3呼叫上述兩個方法))

答:

  1. 結束Activity3(當前 Activity )& 結束程式
  2. 再次重新開啟程式 & 啟動 Activity1Activity2

示意圖

即在Android 中,呼叫上述Dalvik VM本地方法結果是:

  1. 結束當前 Activity & 結束程式
  2. 之後再重新開啟程式 & 啟動 之前除當前 Activity 外的已啟動的 Activity
  • 原因:** Android中的ActivityManager時刻監聽著程式**。一旦發現程式被非正常結束,它將會試圖去重啟這個程式。

  • 應用場景 當任務棧只剩下當前Activity(即退出了其餘 Activity後),呼叫即可退出該程式,即在(步驟1)結束當前 App 所有的 Activity 後,呼叫該方法即可一鍵退出App(更多體現在結束程式上)

注: 與 “在最後一個Activity呼叫 finish()”的區別:finish()不會結束程式,而上述兩個方法會

至此,關於 一鍵退出App 的兩個步驟講解完畢。


3. Demo地址

關於上述說的方法Demo都在Carson_Ho的Github地址:一鍵退出App


4. 總結

  • 在 需要實現 一鍵退出 App 功能時,實際上是需要完成2個步驟: 步驟1:一鍵結束當前App所有的Activity 步驟2:一鍵結束當前App程式
  • 每個步驟的方法總結如下

示意圖


請點贊!因為你的鼓勵是我寫作的最大動力!

相關文章