如何让你的app在后台被干掉后优雅的重新启动

原文地址:http://www.jianshu.com/p/1946bd4f3bb5

我们现在来重现这个熟悉的一幕:

假设: App A -> B -> C

在C activity中点Home键后台运行,打开ddms,选中该App进程,强杀。

然后从“最近打开的应用”中选中该App,回到的界面是C activity,假设App中没有静态变量,这个时候是不会crash的,点击返回到B,这个时候也只是短暂白屏后显示B界面。但如果B中有引用静态变量,并想要获取静态变量中的某个值时,就NullPointer了。

以上复现的流程就几个点,我们展开说下:

当应用被强杀,整个App进程都是被杀掉了,所有变量全都被清空了。包括Application实例。更别提那些静态变量了。

虽然变量被清空了,但Android给了一些补救措施。activity栈没有被清空,也就是说A -> B -> C这个栈还保存了,只是ABC这几个activity实例没有了。所以回到App时,显示的还是C页面。另外当activity被强杀时,系统会调用onSaveInstance去让你保存一些变量,但我个人觉得面对海量的静态变量,这个根本不够用。返回到B会白屏,是因为B要重绘,重走onCreate流程,渲染上需要点时间,所以会白屏了。

大概是以上这些点。如果App中没有静态变量的引用,那就不用出现NullPointer这个crash,也就不需要解决。一旦你有静态变量,或者有些Application的全局变量,那就很危险了。比如登录状态,user profile等等。这些值都是空了。

肯定会有人说,这没关系啊,所有的静态变量都改到单例去不就好了吗?然后附加上一些持久化cache,空了再取缓存就ok了嘛。嗯,这肯定也是一个办法,但是这样的束手束脚对开发来说也是痛苦,至少需要多50%的编码时间才能全部cover。另外,还有那么多帮你挖坑的队友,难省心啊。

既然App都被强杀了,干嘛不重新走第一次启动的流程呢,别让App回到D而是启动A,这样所有的变量都是按正常的流程去初始化,也就不会空指针了,对吧?有人说这方案用户体验一点都不好呀。但哪有十全十美的事呢,是重走流程好,还是一点一个NullPointer好?好好去沟通,相信产品也不会为难你的。当然你也可以拿来举例,iOS在最近打开的应用里杀了某个App,重新点击那个App,还是会重走流程的啊。

如果你说用户已经打开了C界面,所以重新打开的是是恢复到C界面,这样的用户体验会更好啊,如果你是这样认为的,那你很多时间都是在防止恢复的时候不让你的app crash了,与其这样,还不如让整个app重新走整个流程呢,这样更省时间,而且这样也不用担心随时都会崩溃的情况,难道这样的用户体验不会更好吗?

那且想想如何让它不回到C而是重走流程呢?也就是说中断C的初始化而回到A,并且按back键,不会回到C,B。考虑一下。

我们先实例化这个场景吧。
A 为App的启动页
B 为首页
C 为二级页面

把首页launchMode设置为singleTask,具体为什么上面介绍activity的启动模式的时候已经介绍了singleTask的作用了。

在BaseActivity中onCreate中判断App是否被强杀,强杀就不往下走,直接重走App流程。

首页起一个承接或者中转的作用,所有跨级跳转都需要通过首页来完成。

再给个提示,以上场景的解决方案也可以用于解决其它相关问题:
在任意页面退出App
在任意页面返回到首页
其实最重要的知识点就是launchMode
具体实现

AppStatusConstant

public static final int STATUS_FORCE_KILLED = -1; //应用放在后台被强杀了
public static final int STATUS_NORMAL = 2; //APP正常态//intent到MainActivity 区分跳转目的
public static final String KEY_HOME_ACTION = “key_home_action”;//返回到主页面
public static final int ACTION_BACK_TO_HOME = 0; //默认值
public static final int ACTION_RESTART_APP = 1;//被强杀
AppStatusManager

public int appStatus= AppStatusConstant.STATUS_FORCE_KILLED; //APP状态 初始值为没启动 不在前台状态
public static AppStatusManager appStatusManager;
private AppStatusManager() {
}
public static AppStatusManager getInstance() {
if (appStatusManager == null{
appStatusManager = new AppStatusManager();
}
return appStatusManager;
}
public int getAppStatus() {
return appStatus;
}
public void setAppStatus(int appStatus) {
this.appStatus = appStatus;
}
BaseActivity(大致内容)

switch (AppStatusManager.getInstance().getAppStatus()) {
case AppStatusConstant.STATUS_FORCE_KILLED:
restartApp();
break;
case AppStatusConstant.STATUS_NORMAL:
setUpViewAndData();
break;
}
protected abstract void setUpViewAndData();
protected void restartApp() {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_RESTART_APP);
startActivity(intent);
}
每一个继承于父activity的都不要在oncreat中实现界面初始化和数据的初始化,因为如果被杀死之后,回来会走一次正常的生命流程的。

StartPageActivity配置(在oncreat()方法配置,并且在super()前):

AppStatusManager.getInstance().setAppStatus(AppStatusConstant.STATUS_NORMAL); //进入应用初始化设置成未登录状态
MainActivity(配置了singleTask的主界面)

@Override
protected void restartApp() {
Toast.makeText(getApplicationContext(),”应用被回收重启”,Toast.LENGTH_LONG).show();
startActivity(new Intent(this, StartPageActivity.class));
finish();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
int action = intent.getIntExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_BACK_TO_HOME);
switch (action) {
case AppStatusConstant.ACTION_RESTART_APP:
restartApp();
break;
}
}
当应用打开的时候,启动StartPageActivity,然后设置app的status为normal状态,记住,一定要设置,因为默认的是被杀死的状态的。

当应用被杀死之后,所有数据都会被回收,所以之前设置的app status也会置于默认状态,即杀死状态,所以再次打开app的时候,status为杀死状态,就会走重启的流程,这里为什么要先跳转到MainActivity呢?就是因为MainActivity配置为了Sing了Task,当跳转到这个界面时,MainActivity就会置于Activity Task的最上层,其他的Activity将会被默认销毁掉,利用这种技巧去销毁其他的Activity,最后才是重新启动StartPageActivity。整个流程就是这样了。

大致的实现就如上所述了,我所倡导的宗旨就是花最少的时间,写最好的代码,实现最好的体验!之前也参考过很多网上大神们的实现方式,但是我觉得以上实现的应该是比较完整的一种了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值