android 应用被系统回收,莫往Applicaotion存缓存/app被系统回收之后再打开发生了什么...

本文探讨了Android应用被系统回收后,不要在Application中存储缓存数据的原因,因为这可能导致空指针异常和数据丢失。当应用被回收后重新打开,会创建新的Application实例,先前的数据不再有效。解决方案包括避免使用全局静态变量,重新初始化数据,或者利用savedInstanceState进行数据恢复。同时,文章通过示例代码详细解释了不同解决方法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先上结论:

1、尽量不要往Application里面存放缓存数据(因为app用application缓存切使用全局数据,当这个app为后台程序时又被系统gc回收后我们再次打开app,就会导致空指针)

2、app被gc回收后再次点击该app发生了什么(比如我们 app甲先后依次打开了a,b,c这三个activity,这时a,b,c都是在同一个线程的,这时我们按下home键,利用MMDS强制关闭我们程序甲的process,其实也就是模拟被gc回收了。这个时候我们在打开程序甲,会发现打开的是程序甲这的c这个activity,这个时候c是在一个新的线程里面的,而不是在原来的线程里面的)

有图有真相。下图演示因为当安卓app被系统gc回收后我们再次打开app产生空指针导致数据丢失的现象

82nB33gaNzuiT7tm.gif

application缓存丢失演示.gif

上图程序没挂是因为我们缓存在application的值我们这是用作显示,null就是空了,当我们使用了这个值马上程序就会应为空指针而挂掉。

下面我们使用以下给gc回收的缓存在application的值

yQiW4vpbbZvrpfiy.gif

application空指针演示.gif

一、不要往Application存缓存数据

当然这不是绝对化,只是这么建议。

在你的App中的很多地方都需要使用到数据信息,它可能是一个session token,一次费时计算的结果等等,通常为了避免Activity之间传递数据的开销,会将这些数据通过持久化来存储。

有人建议将这些数据放在Application对象中方便所有的Activity访问,这个解决方案简单、优雅并且是……完全错误的。

你如果你将数据缓存到Application对象中,那么有可能你的程序最终会由于一个NullPointerException异常而崩溃掉。

1、数据存在Application被gc后数据都是的demo演示

下面通过demo演示因为数据存放在application导致数据丢失的情况:

1、新建一个类MyApplication继承自Application,弄一个那么的属性设置对应的get和set方法,并且让我们的程序的清单文件的application采用我们的MyApplication2、弄三个acitivity,MainActivity有一个按钮,点击则打开FirstActivity,而Activity的onCreate方法干的事就是给MyApplicatipn的name属性set一个值,比如“Try Text”,接着就直接打开我们的SecondActivity3、SecondAcrivity直接有一个TextView控件,这个控件用于显示从MyApplication取出来的那么的值。

嗯就是这样子,正常情况下,我们进入MainActivity,点击 “打开FirstActivity” 只会就进入FirstActivity之后瞬间进入SecondActivity。SecondActivity正常显示FirstActivity给存储在MyApplication里面的值。

这个是没什么问题的。这个时候,我们按下home键,然后利用DDMS把当前app杀掉(模拟给gc回收),然后在打开app,会发现SecondActivity显示的name值变为null,数据丢失了。一旦使用这个值就报空指针。

接下来贴一些代码:

MyApplication

public class MyApplication extends Application{

@Override

public void onCreate() {

super.onCreate();

}

String name;

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

MainActivity

public class MainActivity extends Activity {

private TextView mTvOpenFirst;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mTvOpenFirst= (TextView) findViewById(R.id.mTvOpenFirst);

mTvOpenFirst.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

Intent openFirst = new Intent(MainActivity.this,FirstActivity.class);

startActivity(openFirst);

}

});

}

}

FirstActivity

public class FirstActivity extends Activity{

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_first);

MyApplication app = (MyApplication) getApplication();

app.setName("Try Text");

startActivity(new Intent(this, SecondActivity.class));

}

}

SecondActivity

public class SecondActivity extends Activity{

private TextView mTv;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

mTv= (TextView) findViewById(R.id.mTv);

MyApplication app = (MyApplication) getApplication();

mTv.setText("HELLO " + app.getName());

Log.d("fixmothod", "是否为空:" + (app.getName() == null));

// app.getName()为null,调用下面这句代码就会引发空指针

//boolean equals= app.getName().equals("123");

}

}

去掉备注的boolean equals= app.getName().equals("123");再来一遍引发的报错图

DIwXsJo8G63vKlFV.png

空指针.png

2、原因分析

因为当我们重新打开这个app的时候,系统会为我们重新实例化一个新的Application对象,Application对象并不是始终在内存中的,它有可能会由于系统内存不足而被杀掉。但Android在你恢复这个应用时并不是重新开始启动这个应用,它会创建一个新的Application对象并且启动上次用户离开时的activity以造成这个app从来没有被kill掉得假象。

说到底,重新点击被杀掉的app时,会实例化一个新的Application对象,然后启动被杀死的app在被杀死之前的所停留的最后一个activity。

二、app被系统回收之后再打开发生了什么

android独特的内存机制,导致系统内存不足时会销毁后台的应用,这里我们研究一下应用被销毁后重新加载时的情形

1、问题:打开被gc杀死的程序后,存在Application等得全局静态变量全部被重置

一个安卓应用A先后打开3个Activity: a --> b --> c (此时的a、b、c是在同一个线程的)这个时候如果来了一个电话, 接电话的过程中, 手机内存不够, 那么应用A将会被系统回收

当打完电话,再次进入应用A的时候会发生下面的事情:

1, 系统会重新加载c,而且是在新线程中

2, 现在点返回关闭c,系统就会重新加载b,而且是在新的线程中(跟c不是一个线程)

3, 现在点返回关闭b,系统就会重新加载a,而且是在新的线程中(跟c,b的线程都不相同)(就是先打开c,finish了c时加载b,finish了b时加载a)

注意: 这个时候应用中的全局静态变量将全部重置(有默认值的为默认值,没有默认值的为null)

这就使得出现了错误的数据

2 解决办法:

方法1, 不使用全局变量

不使用全局变量, 放在Application中也不行, 因为重新加载的a,b,c的页面不在一个线程中,Application不唯一了 (有点不成文)

方法2,强制关闭非存储初值的界面,先开启能够先赋值的界面(一般都是登录界面)

结束b和c, 只重新加载a ,在a中重新初始化数据 (a往往是登录界面)

办法2的实现:在b,c等所有 非a的activity 中的onCreate里面加上下面的代码

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

if (isNull(Config.UserID)) {

if (null != savedInstanceState) {

// activity由系统打开 (是由于手机内存不够,activity在后台被系统回收,再打开时出现的现象)

// 因为系统加载的所有的Activity不在同一个线程,所以要结束除了loginActivity之外的其他线程

android.os.Process.killProcess(android.os.Process.myPid());

} else {

this.finish();

}

return;

}

// ...其他代码

}

代码的原理:因为重新加载的a,b,c都在不同的线程中,所以我们先后舍弃c,b的线程,那么a就会重新加载

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_login);

if (null != savedInstanceState) {

toast("程序被系统回收,需要重新登录!");

//activity由系统加载的时候savedInstanceState不为空

}

//...其他代码

}

关于以上的方法二这个是在实际app中使用。就不做具体分析了。

方法3,onCreate中添加异常处理 ,遇到异常的时候,就重启程序。

Intent i = getBaseContext().getPackageManager()

.getLaunchIntentForPackage(getBaseContext().getPackageName());

i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

startActivity(i);

方法4、利用savedInstanceState和onRestoreInstanceState

这个方法便捷针对本demo看起来挺便捷的,但是使用的时候要结合具体,因为写起来太麻烦。

我们的Activity可以复写savedInstanceState和onRestoreInstanceState这两个方法,简单说,这个两个方法正常情况下不会被调用,onSaveInstanceState方法只有在程序以外退出或Activity异常终止的时才会被调用,用于保存数据

savedInstanceState只有在程序以外退出或Activity异常终止的时才会被调用,用于保存数据。

onRestoreInstanceState则用来恢复数据,这些数据就是savedInstanceState那里拿来的。

我们只要修改下SecondActivity就可以看到效果:

public class SecondActivity extends Activity{

private TextView mTv;

MyApplication app;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

mTv= (TextView) findViewById(R.id.mTv);

app= (MyApplication) getApplication();

mTv.setText("HELLO " + app.getName());

}

/**

* onSaveInstanceState和onRestoreInstanceState一般都是配合着使用的

* 方法只有在程序以外退出或Activity异常终止的时才会被调用,用于保存数据

* @param outState

*/

@Override

protected void onSaveInstanceState(Bundle outState) {

super.onSaveInstanceState(outState);

// 键值对 key value

outState.putString("save_name",app.getName());

}

/**

* onRestoreInstanceState

* 用于恢复在以外发生之前保存的数据

* @param savedInstanceState

*/

@Override

protected void onRestoreInstanceState(Bundle savedInstanceState) {

super.onRestoreInstanceState(savedInstanceState);

// 获取 key 所对应的 value

String save_name = savedInstanceState.getString("save_name");

mTv.setText("HELLO" + save_name);

Log.d("fixmothod", "app.getName()是否为空1: " + app.getName());

app.setName(save_name);

Log.d("fixmothod", "app.getName()是否为空2: " + app.getName());

}

}

oOTwkpclDEIu08WJ.gif

利用onSaveInstanceState.gif

三、附上源码

module说明图

jEFsH8eXwTfQdha9.png

module说明.png

本篇完。

参考链接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值