Android | 再按一次返回鍵退出【Deprecated】

提示 2021年6月14日

這篇文章是 2019 年寫的规伐,今年我更新了一些新內(nèi)容,你可以直接看:《Android | Jetpack 處理回退事件的新姿勢(shì) —— OnBackPressedDispatcher》

前言

  • “再按一次退出應(yīng)用”是一個(gè)常見的功能
  • 但市面上流傳著太多不全面的“再按一次退出應(yīng)用”功能實(shí)現(xiàn)
  • 本文將全面總結(jié)“再按一次退出應(yīng)用”的實(shí)現(xiàn)方式淌友,希望對(duì)你們有幫助

1. 需求分析

對(duì)比分析市面上App,可以分為四類磷账,歸納出完整流程圖:

  1. Facebook荣瑟、Instagram等:主Activity -> 主Tab -> 桌面
  2. 愛奇藝高德地圖咏尝、招商等(最多):主Activity -> 兩次點(diǎn)擊 -> 結(jié)束Activity
  3. 微信压语、支付寶等:主Activity -> 返回桌面
  4. QQ音樂等:主Activity -> 兩次點(diǎn)擊 -> 返回桌面
流程圖
  • 需求本質(zhì)

    1. 判定連續(xù)按兩次返回鍵
    2. 結(jié)束Activity或者進(jìn)入后臺(tái)

2. 最小可行方案

  • 需求:判定連續(xù)按兩次返回鍵后結(jié)束Activity

    重寫Activity#onBackPressed()實(shí)現(xiàn)功能邏輯:

class MainActivity : AppCompatActivity() {
    /**
     * 上次點(diǎn)擊返回鍵的時(shí)間
     */
     private var lastBackPressTime = -1L
     ...
     override fun onBackPressed() {
        val currentTIme = System.currentTimeMillis()
        if(lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000){
            // 顯示提示信息
            showBackPressTip()
            // 記錄時(shí)間
            lastBackPressTime = currentTIme
        }else{
            //退出應(yīng)用
            finish()
        }
    }
    private fun showBackPressTip(){
        Toast.makeText(this,"再按一次退出",Toast.LENGTH_SHORT).show();
    }
}
  • 注意

    因?yàn)镵ey事件會(huì)先傳遞給視圖中持有焦點(diǎn)的View,需要保證沒有View消費(fèi)KeyEvent.KEYCODE_BACK事件编检,否則Key事件無法傳遞到Activity中。

更多介紹參考:Android: Key事件分發(fā)機(jī)制

  • 需求:進(jìn)入后臺(tái)

    Activity#moveTaskToBack(boolean nonRoot)可以將當(dāng)前任務(wù)棧轉(zhuǎn)入后臺(tái)扰才,但不會(huì)真正結(jié)束Activity允懂。當(dāng)用戶返回應(yīng)用時(shí),不會(huì)重建Activity實(shí)例衩匣,Activity生命周期為:onStart() -> onResume()蕾总。

    • nonRoot參數(shù)

      1. true:只有當(dāng)前Activity處于棧底有效
      2. false:即使當(dāng)前Activity不處于棧底也有效
    • 對(duì)比finish()

      1. finish()會(huì)結(jié)束Activity,moveTaskToBack()不會(huì)
      2. 用戶返回應(yīng)用時(shí)琅捏,使用finish()為溫啟動(dòng)生百,使用moveTaskToBack()為熱啟動(dòng),啟動(dòng)速度更快
    • 對(duì)比System.exit(0)

      1. 直接殺死進(jìn)程柄延,但不推薦使用蚀浆。
      2. 要求兩次點(diǎn)擊返回鍵的目的是確保用戶是真的需要退出缀程,因此,退出后的行為應(yīng)該保持和沒有要求兩次點(diǎn)擊的效果一樣
      3. 默認(rèn)的返回鍵邏輯會(huì)結(jié)束Activity市俊,后續(xù)用戶返回應(yīng)用是溫啟動(dòng)杨凑,速度更快,而從System.exit(0)中恢復(fù)是冷啟動(dòng)摆昧,需要付出的代價(jià)大得多

更多介紹參考:Android: 任務(wù)棧與Activity啟動(dòng)模式

  • 優(yōu)點(diǎn)

    實(shí)現(xiàn)簡(jiǎn)單&方便

  • 缺點(diǎn)

    1. 返回鍵無法收起可折疊的ActionBar
    2. 返回鍵無法彈出Fragment回退棧
  • 適用場(chǎng)景

    不需要通過返回鍵操作可折疊ActionBar和Fragment回退棧的情況撩满,如果能滿足需求,下文內(nèi)容均可以跳過绅你。


3. 源碼分析

由Key事件分發(fā)機(jī)制可知伺帘,如果沒有其他View消費(fèi)了返回鍵事件,最終將分發(fā)給Activity#onKeyUp():

// android.app.Activity:
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                && !event.isCanceled()) {
            // 返回鍵事件
            onBackPressed();
            // 消費(fèi)KeyEvent.KEYCODE_BACK事件
            return true;
        }
     }
     // 不消費(fèi)
     return false;
 }
  • 外部的條件判斷targetSdkVersion大于API 5忌锯,這是為了兼容舊時(shí)代的設(shè)備伪嫁,不要深究。
  • 返回鍵事件分發(fā)最終分發(fā)到Activity#onBackPressed()
// android.app.Activity:
public void onBackPressed() {
    // 收起可折疊ActionBar
    if (mActionBar != null && mActionBar.collapseActionView()) {
        return;
    }

    // 彈出Fragment回退棧棧頂?shù)腇ragment
    FragmentManager fragmentManager = mFragments.getFragmentManager();
    if (fragmentManager.isStateSaved() 
            || !fragmentManager.popBackStackImmediate()) {
        finishAfterTransition();
    }
 }

4. 演進(jìn)

  • 支持android.support.v4.app.Fragment回退棧

    按下返回鍵時(shí)優(yōu)先檢查Fragment回退棧并彈出:

// android.app.Activity:
override fun onBackPressed() {
    // 彈出support庫Fragment回退棧
    if(popSupportBackStack()){
        return;
    }
    val currentTIme = System.currentTimeMillis();
    ...
}
/**
 * @return true:沒有Fragment彈出 false:有Fragment彈出
 */
private fun popSupportBackStack():Boolean{
    // 當(dāng)Fragment狀態(tài)保存了
    return supportFragmentManager.isStateSaved 
          ||supportFragmentManager.popBackStackImmediate()
}
  • 注意
    • 判斷supportFragmentManager.isStateSaved()非常有必要
    • Activty#onSaveInstanceState()或者Activity#onStop()之后汉规,更改Fragment狀態(tài)礼殊,將拋出異常:
      IllegalStateException:Can not perform this action after onSaveInstanceState

更多介紹參考:Android: Activity狀態(tài)管理

  • 兼容性

    重寫Activity#finishAfterTransition()可以保留源碼默認(rèn)的邏輯;

// android.app.Activity:
override fun finishAfterTransition() {
    if(popSupportBackStack()){
        return
    }
    val currentTIme = System.currentTimeMillis();
    if(lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000){
        showBackPressTip()
        lastBackPressTime = currentTIme
    }else{
        //退出應(yīng)用
        finish()
    }
}
  • 支持WebView
    使用WebView顯示多層級(jí)頁面针史,需要利用返回鍵向上導(dǎo)航:
class WebViewImpl(context: Context) : WebView(context) {
    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
        // 判斷可以向上導(dǎo)航
        if(canGoBack()){
            // 導(dǎo)航到之前的頁面
            goBack()
            return true
        }
        return false
    }
}

5. 延伸閱讀


推薦閱讀


感謝喜歡晶伦!你的點(diǎn)贊是對(duì)我最大的鼓勵(lì)!歡迎關(guān)注彭旭銳的簡(jiǎn)書啄枕!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婚陪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子频祝,更是在濱河造成了極大的恐慌泌参,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件常空,死亡現(xiàn)場(chǎng)離奇詭異沽一,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漓糙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門铣缠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人昆禽,你說我怎么就攤上這事蝗蛙。” “怎么了醉鳖?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵捡硅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我盗棵,道長(zhǎng)壮韭,這世上最難降的妖魔是什么北发? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮泰涂,結(jié)果婚禮上鲫竞,老公的妹妹穿的比我還像新娘。我一直安慰自己逼蒙,他們只是感情好从绘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著是牢,像睡著了一般僵井。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驳棱,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天批什,我揣著相機(jī)與錄音,去河邊找鬼社搅。 笑死驻债,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的形葬。 我是一名探鬼主播合呐,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼笙以!你這毒婦竟也來了淌实?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤猖腕,失蹤者是張志新(化名)和其女友劉穎拆祈,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倘感,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡放坏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了老玛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轻姿。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逻炊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情犁享,我是刑警寧澤余素,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站炊昆,受9級(jí)特大地震影響桨吊,放射性物質(zhì)發(fā)生泄漏威根。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一视乐、第九天 我趴在偏房一處隱蔽的房頂上張望洛搀。 院中可真熱鬧,春花似錦佑淀、人聲如沸留美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谎砾。三九已至,卻和暖如春捧颅,著一層夾襖步出監(jiān)牢的瞬間景图,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工碉哑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挚币,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓扣典,卻偏偏與公主長(zhǎng)得像妆毕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子激捏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 面試題總結(jié) 通用 安卓學(xué)習(xí)途徑, 尋找資料學(xué)習(xí)的博客網(wǎng)站 AndroidStudio使用, 插件使用 安卓和蘋果的...
    JingBeibei閱讀 1,676評(píng)論 2 21
  • 一 Activity 1 Activity 生命周期 1.1 Activity 的四種狀態(tài) running 當(dāng)前...
    _執(zhí)_念__閱讀 10,377評(píng)論 0 91
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情況下的生命周期:在用戶參與的情況下...
    AndroidMaster閱讀 3,040評(píng)論 0 8
  • 選擇題 1.activity對(duì)一些資源以及狀態(tài)的操作保存设塔,最好是保存在生命周期的哪個(gè)函數(shù)中進(jìn)行( a ) a.o...
    海晨憶閱讀 1,125評(píng)論 0 1
  • 哇!又出新書包了远舅, 萌翻了闰蛔,太可愛了吧! 適合年齡图柏,班級(jí)序六;(6-10歲)三年級(jí)以下 在11月12號(hào)之前下單,還送文...
    美兔照片書閱讀 286評(píng)論 0 0