本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。 先上结论:
有图有真相。 application缓存丢失演示.gif 上图程序没挂是因为我们缓存在application的值我们这是用作显示,null就是空了,当我们使用了这个值马上程序就会应为空指针而挂掉。 下面我们使用以下给gc回收的缓存在application的值 application空指针演示.gif 一、不要往Application存缓存数据
在你的App中的很多地方都需要使用到数据信息,它可能是一个session token,一次费时计算的结果等等,通常为了避免Activity之间传递数据的开销,会将这些数据通过持久化来存储。 有人建议将这些数据放在Application对象中方便所有的Activity访问,这个解决方案简单、优雅并且是……完全错误的。 你如果你将数据缓存到Application对象中,那么有可能你的程序最终会由于一个NullPointerException异常而崩溃掉。 1、数据存在Application被gc后数据丢失的demo演示下面通过demo演示因为数据存放在application导致数据丢失的情况: 1、新建一个类MyApplication继承自Application,弄一个那么的属性设置对应的get和set方法,并且让我们的程序的清单文件的application采用我们的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');再来一遍引发的报错图 空指针.png 2、原因分析因为当我们重新打开这个app的时候,系统会为我们重新实例化一个新的Application对象,Application对象并不是始终在内存中的,它有可能会由于系统内存不足而被杀掉。但Android在你恢复这个应用时并不是重新开始启动这个应用,它会创建一个新的Application对象并且启动上次用户离开时的activity以造成这个app从来没有被kill掉得假象。 说到底,重新点击被杀掉的app时,会实例化一个新的Application对象,然后启动被杀死的app在被杀死之前的所停留的最后一个activity。 二、app被系统回收之后再打开发生了什么
1、问题:打开被gc杀死的程序后,存在Application等得全局静态变量全部被重置一个安卓应用A先后打开3个Activity: 当打完电话,再次进入应用A的时候会发生下面的事情:
注意: 这个时候应用中的全局静态变量将全部重置(有默认值的为默认值,没有默认值的为null) 这就使得出现了错误的数据 2 解决办法:方法1, 不使用全局变量不使用全局变量, 放在Application中也不行, 因为重新加载的a,b,c的页面不在一个线程中,Application不唯一了 (有点不成文) 方法2,强制关闭非存储初值的界面,先开启能够先赋值的界面(一般都是登录界面)结束b和c, 只重新加载a ,在a中重新初始化数据 (a往往是登录界面) 办法2的实现: @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; } // ...其他代码 } 代码的原理: @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异常终止的时才会被调用,用于保存数据
我们只要修改下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()); }} 利用onSaveInstanceState.gif 三、附上源码module说明图 module说明.png 本篇完。 参考链接: 如何处理android程序变为后台程序,系统回收资源,再次打开时,程序因为null指针等崩溃 著作权归作者所有 |
|
来自: 月冷星河 > 《Android文章》