前言
-
“再按一次退出应用”是一个常见的功能
-
但市面上流传着太多不全面的“再按一次退出应用”功能实现
-
本文将全面总结“再按一次退出应用”的实现方式,希望对你们有帮助
1. 需求分析
对比分析市面上App,可以分为四类,归纳出完整流程图:
-
Facebook、Instagram等:主Activity -> 主Tab -> 桌面
-
爱奇艺、高德地图、招商等(最多):主Activity -> 两次点击 -> 结束Activity
-
微信、支付宝等:主Activity -> 返回桌面
-
QQ音乐等:主Activity -> 两次点击 -> 返回桌面
-
需求本质
-
判定连续按两次返回键
-
结束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参数
-
true:只有当前Activity处于栈底有效
-
false:即使当前Activity不处于栈底也有效
-
-
对比finish()
-
finish()会结束Activity,moveTaskToBack()不会
-
用户返回应用时,使用finish()为温启动,使用moveTaskToBack()为热启动,启动速度更快
-
-
对比System.exit(0)
- 直接杀死进程,但不推荐使用。
- 要求两次点击返回键的目的是确保用户是真的需要退出,因此,退出后的行为应该保持和没有要求两次点击的效果一样
- 默认的返回键逻辑会结束Activity,后续用户返回应用是温启动,速度更快,而从System.exit(0)中恢复是冷启动,需要付出的代价大得多
-
更多介绍参考:Android: 任务栈与Activity启动模式
-
优点
实现简单&方便
-
缺点
-
返回键无法收起可折叠的ActionBar
-
返回键无法弹出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状态管理]
网友评论