APP被强杀,怎么办?

应用在后台运行时很容易被强杀,这很正常,但是回到前台时,很容易出现空指针的情况。怎么解决这样的问题?

我们先跳出来看看android的app运行原理。

app在后台被强杀,是在内存不足的情况下被强制释放了,也有一些恶心的rom会强制杀掉那些后台进程以释放缓存以提高所谓的用户体验。

我们都觉得android rom很恶心,但同时还是用些更恶心的手法去绕开这些瓶颈。乱,是因为在最上层没有一个很好的约束,这也是开源的弊端。anyway。我们还是得想破脑袋来解决这些问题,否则饭碗就没了。
我们先来重现这个bug:

假设: App A -> B -> C -> D

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

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

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

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

虽然变量被清空了,但Android给了一些补救措施。activity栈没有被清空,也就是说A -> B -> C -> D这个栈还保存了,只是ABCD这几个activity实例没有了。所以回到App时,显示的还是D页面。

另外当activity被强杀时,系统会调用onSaveInstance去让你保存一些变量,但我个人觉得面对海量的静态变量,这个根本不够用。

返回到C会黑屏,是因为C要重绘,重走onCreate流程,渲染上需要点时间,所以会黑屏。

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

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

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

如果你接受我的这个解决方案,那且想想如何让它不回到D而是重走流程呢?也就是说中断D的初始化而回到A,并且按back键,不会回到D,C,B。考虑一下。

我们先实例化这个场景吧。

  • A 为App的启动页
  • B 为登录页
  • C 为首页
  • D 为二级页面

简单说下解决方案,剩下的自己思考。

  • 把启动页launchMode设置为singleTask,在这种模式下,只要Activity在栈中存在,那么多次启动都不会重新创建该实例,还会把该实例调到栈顶并调用他的OnNewIntent方法,同时由于singleTask具有clearTop的效果,会导致该Activity上面的Activity全部出栈。
  • 在BaseActivity中onCreate中判断App是否被强杀,强杀就不往下走,直接重走App流程。
  • 首页起一个承接或者中转的作用,所有跨级跳转都需要通过首页来完成。

核心代码

BaseActivity

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (MyApp.mAppStatus == -1) {
            protectApp();
        } else {
            setupData();
        }
    }

    protected void protectApp() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra("action", "force_kill");
        startActivity(intent);
    }

    protected void setupData() {
    }
}

WelcomeActivity 为启动页,设置SingleTask启动模式

public class WelcomeActivity extends BaseActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        MyApp.mAppStatus=0;
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void setupData() {

        setContentView(R.layout.activity_welcome);
        findViewById(R.id.tv_go).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(WelcomeActivity.this,LoginActivity.class));
            }
        });
    }
}

MainActivity

public class MainActivity extends BaseActivity {

    @Override
    protected void setupData() {

        setContentView(R.layout.activity_main);
        findViewById(R.id.tv_read_news).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, NewsActivity.class));
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        String action = intent.getStringExtra("action");
        if (action.equals("force_kill")) {
            startActivity(new Intent(this, WelcomeActivity.class));
            finish();
        }
    }
}

试验步骤

假如有ABCD四个Activity,界面展示顺序为A -> B -> C -> D,然后在D activity中点Home键后台运行,打开ddms,选中该App进程,强杀。

试验结果(查看andorid应用Activity任务栈)

在终端或者Android Studio中的Terminal里敲入如下命令:

adb shell dumpsys activity activities

这里写图片描述

“sz=1”表示栈中只有一个Activity。

结束语

其实最重要的知识点就是launchMode,很多人面试的时候都能背出来,甚至是原理。但真正会合理应用它们的少之又少。有的时候,技术的优劣体现于此。生搬硬套肯定是站不到最高点的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值