Android:双击返回键退出应用

作者: 彭旭锐 | 来源:发表于2019-07-18 23:55 被阅读203次

    前言

    • “再按一次退出应用”是一个常见的功能

    • 但市面上流传着太多不全面的“再按一次退出应用”功能实现

    • 本文将全面总结“再按一次退出应用”的实现方式,希望对你们有帮助


    1. 需求分析

    对比分析市面上App,可以分为四类,归纳出完整流程图:

    1. Facebook、Instagram等:主Activity -> 主Tab -> 桌面

    2. 爱奇艺、高德地图、招商等(最多):主Activity -> 两次点击 -> 结束Activity

    3. 微信、支付宝等:主Activity -> 返回桌面

    4. QQ音乐等:主Activity -> 两次点击 -> 返回桌面

    流程图
    • 需求本质

      1. 判定连续按两次返回键

      2. 结束Activity或者进入后台


    2. 最小可行方案

    • 需求:判定连续按两次返回键后结束Activity

      重写Activity#onBackPressed()实现功能逻辑:

    class MainActivity : AppCompatActivity() {
        /**
         * 上次点击返回键的时间
         */
         private var lastBackPressTime = -1L
         ...
         override fun onBackPressed() {
            val currentTIme = System.currentTimeMillis()
            if(lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000){
                // 显示提示信息
                showBackPressTip()
                // 记录时间
                lastBackPressTime = currentTIme
            }else{
                //退出应用
                finish()
            }
        }
        private fun showBackPressTip(){
            Toast.makeText(this,"再按一次退出",Toast.LENGTH_SHORT).show();
        }
    }
    
    • 注意

      因为Key事件会先传递给视图中持有焦点的View,需要保证没有View消费KeyEvent.KEYCODE_BACK事件,否则Key事件无法传递到Activity中。

    更多介绍参考:Android: Key事件分发机制

    • 需求:进入后台

      Activity#moveTaskToBack(boolean nonRoot)可以将当前任务栈转入后台,但不会真正结束Activity。当用户返回应用时,不会重建Activity实例,Activity生命周期为:onStart() -> onResume()。

      • nonRoot参数

        1. true:只有当前Activity处于栈底有效

        2. false:即使当前Activity不处于栈底也有效

      • 对比finish()

        1. finish()会结束Activity,moveTaskToBack()不会

        2. 用户返回应用时,使用finish()为温启动,使用moveTaskToBack()为热启动,启动速度更快

      • 对比System.exit(0)

        1. 直接杀死进程,但不推荐使用。
        2. 要求两次点击返回键的目的是确保用户是真的需要退出,因此,退出后的行为应该保持和没有要求两次点击的效果一样
        3. 默认的返回键逻辑会结束Activity,后续用户返回应用是温启动,速度更快,而从System.exit(0)中恢复是冷启动,需要付出的代价大得多

    更多介绍参考:Android: 任务栈与Activity启动模式

    • 优点

      实现简单&方便

    • 缺点

      1. 返回键无法收起可折叠的ActionBar

      2. 返回键无法弹出Fragment回退栈

    • 适用场景

      不需要通过返回键操作可折叠ActionBar和Fragment回退栈的情况,如果能满足需求,下文内容均可以跳过。


    3. 源码分析

    由Key事件分发机制可知,如果没有其他View消费了返回键事件,最终将分发给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();
                // 消费KeyEvent.KEYCODE_BACK事件
                return true;
            }
         }
         // 不消费
         return false;
     }
    
    • 外部的条件判断targetSdkVersion大于API 5,这是为了兼容旧时代的设备,不要深究。

    • 返回键事件分发最终分发到Activity#onBackPressed()

    // android.app.Activity:
    public void onBackPressed() {
        // 收起可折叠ActionBar
        if (mActionBar != null && mActionBar.collapseActionView()) {
            return;
        }
    ​
        // 弹出Fragment回退栈栈顶的Fragment
        FragmentManager fragmentManager = mFragments.getFragmentManager();
        if (fragmentManager.isStateSaved() 
                || !fragmentManager.popBackStackImmediate()) {
            finishAfterTransition();
        }
     }
    

    4. 演进

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

      按下返回键时优先检查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{
        // 当Fragment状态保存了
        return supportFragmentManager.isStateSaved 
              ||supportFragmentManager.popBackStackImmediate()
    }
    
    • 注意
      • 判断supportFragmentManager.isStateSaved()非常有必要
      • Activty#onSaveInstanceState()或者Activity#onStop()之后,更改Fragment状态,将抛出异常:
        IllegalStateException:Can not perform this action after onSaveInstanceState

    更多介绍参考:Android: Activity状态管理

    • 兼容性

      重写Activity#finishAfterTransition()可以保留源码默认的逻辑;

    // android.app.Activity:
    override fun finishAfterTransition() {
        if(popSupportBackStack()){
            return
        }
        val currentTIme = System.currentTimeMillis();
        if(lastBackPressTime == -1L || currentTIme - lastBackPressTime >= 2000){
            showBackPressTip()
            lastBackPressTime = currentTIme
        }else{
            //退出应用
            finish()
        }
    }
    
    • 支持WebView
      使用WebView显示多层级页面,需要利用返回键向上导航:
    class WebViewImpl(context: Context) : WebView(context) {
        override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
            // 判断可以向上导航
            if(canGoBack()){
                // 导航到之前的页面
                goBack()
                return true
            }
            return false
        }
    }
    

    5. 延伸阅读

    • [Android: Key事件分发机制]

    • [Android: 任务栈与Activity启动模式]

    • [Android: Activity状态管理]


    感谢喜欢!你的点赞是对我最大的鼓励!

    相关文章

      网友评论

        本文标题:Android:双击返回键退出应用

        本文链接:https://www.haomeiwen.com/subject/ptrdlctx.html