APP被强杀,怎么办?

来源:互联网 发布:linux文件权限第三段 编辑:程序博客网 时间:2024/05/17 23:33

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

我们先跳出来看看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,很多人面试的时候都能背出来,甚至是原理。但真正会合理应用它们的少之又少。有的时候,技术的优劣体现于此。生搬硬套肯定是站不到最高点的。

原创粉丝点击