Carson帶你學Android: 這里涵蓋了所有實現 “一鍵退出 App” 的方法


前言

  • Android開發(fā)中捅厂,會經常存在 “一鍵退出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);
    }
  • 優(yōu)點
    使用簡單 & 方便

  • 缺點

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


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

  • 原理:對入口Activity采用 2 標記位:
    1. Intent.FLAG_ACTIVITY_CLEAR_TOP:銷毀目標Activity和它之上的所有Activity脓斩,重新創(chuàng)建目標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,重新創(chuàng)建目標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);
  • 優(yōu)點
    使用簡單 & 方便

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

  • 應用場景
    Activity單任務棧


方法3:通過系統(tǒng)任務棧

  • 原理:通過 ActivityManager 獲取當前系統(tǒng)的任務棧 & 把棧內所有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);
  • 優(yōu)點
    使用簡單佩憾、方便

  • 缺點

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

b. 通過 Android 組件: BroadcastReceiver

即使用 BroadcastReceiver 廣播監(jiān)聽

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

  • 具體實現

步驟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時 發(fā)送廣播請求

context.sendBroadcast(new Intent(BaseApplication.EXIT));
// 注:此處不能使用:System.exit(0);結束進程
// 原因:發(fā)送廣播這個方法之后楞黄,不會等到廣播接收器收到廣播,程序就開始執(zhí)行下一句System.exit(0)抡驼,然后就直接變成執(zhí)行System.exit(0)的效果了鬼廓。
  • 優(yōu)點
    應用場景廣泛:兼顧單 / 多任務棧 & 多啟動模式的情況
  • 缺點
    實現復雜:需要在每個 Activity 里注冊廣播接收器

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

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


c. 自身實現

方法1:創(chuàng)建 鏈表
  • 原理:通過在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();


  • 效果圖
示意圖
示意圖
  • 優(yōu)點
    應用場景廣泛:兼顧單 / 多任務棧 & 多啟動模式的情況

  • 缺點
    需要 Activity 經歷正常的生命周期馏锡,即創(chuàng)建時調用onCreate()雷蹂,結束時調用onDestroy()

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

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

方法2:RxBus

  • 原理:使用 RxBus當作事件總線,在每個 Activity里注冊RxBus訂閱(響應動作 = 關閉自身)眷篇;當需要退出App時 發(fā)送退出事件請求即可萎河。

  • 具體使用

步驟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時 發(fā)送退出事件

        RxBus.getInstance().post("exit");
        System.exit(0);
  • 優(yōu)點
    可與 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)的區(qū)別
  // 1. System.exit(0):正常退出擎椰;
  // 2. System.exit(1):非正常退出,通常這種退出方式應該放在catch塊中创肥。 
  • 特別注意
    假設場景:當前 Activity ≠ 當前任務棧最后1個Activity時达舒,調用上述兩個方法會出現什么情況呢?(即Activity1 - Activity2 -Activity3(在Activity3調用上述兩個方法))

答:

  1. 結束Activity3(當前 Activity )& 結束進程
  2. 再次重新開啟進程 & 啟動 Activity1 叹侄、 Activity2
示意圖

即在Android 中巩搏,調用上述Dalvik VM本地方法結果是:

  1. 結束當前 Activity & 結束進程
  2. 之后再重新開啟進程 & 啟動 之前除當前 Activity 外的已啟動的 Activity
  • 原因:** Android中的ActivityManager時刻監(jiān)聽著進程**。一旦發(fā)現進程被非正常結束趾代,它將會試圖去重啟這個進程贯底。
  • 應用場景
    當任務棧只剩下當前Activity(即退出了其余 Activity后),調用即可退出該進程撒强,即在(步驟1)結束當前 App 所有的 Activity 后禽捆,調用該方法即可一鍵退出App(更多體現在結束進程上)

注: 與 “在最后一個Activity調用 finish()”的區(qū)別:finish()不會結束進程,而上述兩個方法會

至此飘哨,關于 ** 一鍵退出App ** 的兩個步驟講解完畢胚想。


3. Demo地址

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


4. 總結

  • 在 需要實現 一鍵退出 App 功能時,實際上是需要完成2個步驟:
    步驟1:一鍵結束當前App所有的Activity
    步驟2:一鍵結束當前App進程
  • 每個步驟的方法總結如下
示意圖
  • 下一篇文章我將對講解Android 的相關知識芽隆,感興趣的同學可以繼續(xù)關注本人的簡書哦浊服。

相關系列文章閱讀
Carson帶你學Android:學習方法
Carson帶你學Android:四大組件
Carson帶你學Android:自定義View
Carson帶你學Android:異步-多線程
Carson帶你學Android:性能優(yōu)化
Carson帶你學Android:動畫


歡迎關注Carson_Ho的簡書

不定期分享關于安卓開發(fā)的干貨统屈,追求短、平牙躺、快鸿吆,但卻不缺深度


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末蕉毯,一起剝皮案震驚了整個濱河市乓搬,隨后出現的幾起案子,更是在濱河造成了極大的恐慌代虾,老刑警劉巖进肯,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異棉磨,居然都是意外死亡江掩,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門乘瓤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來环形,“玉大人,你說我怎么就攤上這事衙傀√б鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵统抬,是天一觀的道長火本。 經常有香客問我,道長聪建,這世上最難降的妖魔是什么钙畔? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮金麸,結果婚禮上擎析,老公的妹妹穿的比我還像新娘。我一直安慰自己钱骂,他們只是感情好叔锐,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著见秽,像睡著了一般愉烙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上解取,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天步责,我揣著相機與錄音,去河邊找鬼。 笑死蔓肯,一個胖子當著我的面吹牛遂鹊,可吹牛的內容都是我干的。 我是一名探鬼主播蔗包,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼秉扑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了调限?” 一聲冷哼從身側響起舟陆,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耻矮,沒想到半個月后秦躯,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡裆装,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年踱承,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哨免。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡茎活,死狀恐怖,靈堂內的尸體忽然破棺而出琢唾,到底是詐尸還是另有隱情妙色,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布慧耍,位于F島的核電站身辨,受9級特大地震影響,放射性物質發(fā)生泄漏芍碧。R本人自食惡果不足惜煌珊,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泌豆。 院中可真熱鬧定庵,春花似錦、人聲如沸踪危。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贞远。三九已至畴博,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蓝仲,已是汗流浹背俱病。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工官疲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亮隙。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓途凫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溢吻。 傳聞我的和親對象是個殘疾皇子维费,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容