Android-Application被回收引发空指针异常分析(消灭全局变量)
Android-Application被回收引发空指针异常分析(消灭全局变量)
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
问题描述
App切换到后台后,一段时间不操作,再切回来,很容易就发生崩溃(配置低的手机这种问题出现更频繁)。究其原因,是因为常常把对象存储在Application里面,而App切换到后台后,进程很容易就被系统回收了,下次切换回来的时候App页面再重建,但是系统重建的App对于原来存储的全局变量却无能为力。
示例工程
例如:有这样的场景,在App登陆页面登录成功后,把接口返回的用户信息(用户名,电话,服务器返回用于后续网络请求的口令-Token)存储起来,方便下次使用。
1.创建存储用户信息的UserInfoBean
/** 用户信息 */
public class UserInfoBean {private String name;private String tel;private String token;public UserInfoBean(String name, String tel, String token) {super();this.name = name;this.tel = tel;this.token = token;}@Overridepublic String toString() {return "UserInfoBean [name=" + name + ", tel=" + tel + ", token="+ token + "]";}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
2.因为很多页面都有可能会设计到使用网络访问,获取用户信息,于是把它存储到Application中。
public class XApp extends Application {private UserInfoBean userinfo;public UserInfoBean getUserinfo() {return userinfo;}public void setUserinfo(UserInfoBean userinfo) {this.userinfo = userinfo;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
3.模拟登录成功,存储接口返回的UserInfoBean
public class LoginActivity extends Activity {private Button btnLogin;private ProgressDialog pdLogin;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);pdLogin = new ProgressDialog(this, ProgressDialog.THEME_HOLO_LIGHT);pdLogin.setMessage("登陆中...");btnLogin = (Button) findViewById(R.id.btnLogin);btnLogin.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {// 弹出等待对话框 模拟登录耗时操作pdLogin.show();btnLogin.getHandler().postDelayed(new Runnable() {@Overridepublic void run() {pdLogin.dismiss();// 存储数据UserInfoBean userInfo = new UserInfoBean("Tony","17011110000", "tokenabcdefg");((XApp) getApplication()).setUserinfo(userInfo);MainActivity.actionStart(LoginActivity.this);}}, 1500);}});}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
4.获取Application中的UserInfoBean使用
public class MainActivity extends Activity {private Button btnShowUserInfo;private UserInfoBean userInfo;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnShowUserInfo = (Button) findViewById(R.id.btnShowUserInfo);btnShowUserInfo.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {userInfo = ((XApp) getApplicationContext()).getUserinfo();Toast.makeText(getApplicationContext(), userInfo.toString(),Toast.LENGTH_LONG).show();}});}public static void actionStart(Context context) {context.startActivity(new Intent(context, MainActivity.class));}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
情景重现
模拟切换到后台,App进程被系统回收的场景
- 开启应用,进入登录页,登录成功跳转到主页
- 按Home键退出应用
- 使用DDMS-Stop Process结束进程
- 回到应用中,正常使用(注:现在处于一个新的Application中,没有之前操作存储的数据了)
出现崩溃
解决办法
从Application获取数据的时候使用空判断,只能防止不崩溃,数据还是获取不到
userInfo = ((XApp) getApplicationContext()).getUserinfo();if (null != userInfo) {// do something}
- 1
- 2
- 3
- 4
使用页面数据传递使用Intent携带,不再从全局变量里面获取(推荐)
可以解决问题,建议新项目这样做,但是项目如果已经上线,重构这一块问题稍显麻烦
public class MainActivity extends Activity {private Button btnShowUserInfo;private UserInfoBean userInfo;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//从getIntent中获取userInfo = (UserInfoBean) getIntent().getSerializableExtra("bean");setContentView(R.layout.activity_main);btnShowUserInfo = (Button) findViewById(R.id.btnShowUserInfo);btnShowUserInfo.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(getApplicationContext(), userInfo.toString(),Toast.LENGTH_LONG).show();}});}//定义给,外部调用启动MainActivitypublic static void actionStart(Context context, UserInfoBean bean) {Intent intent = new Intent(context, MainActivity.class);intent.putExtra("bean", bean);context.startActivity(intent);}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
把对象序列化到本地,如果为空再从本地读出来
1.创建对象存储和读取工具类
public class StreamUtil {public static final void saveObject(String path, Object saveObject) {FileOutputStream fOps = null;ObjectOutputStream oOps = null;File file = new File(path);try {fOps = new FileOutputStream(file);oOps = new ObjectOutputStream(fOps);oOps.writeObject(saveObject);} catch (Exception e) {e.printStackTrace();} finally {CloseUtils.close(oOps);CloseUtils.close(fOps);}}public static final Object restoreObject(String path) {FileInputStream fis = null;ObjectInputStream ois = null;Object obj = null;File file = new File(path);if (!file.exists()) {return null;}try {fis = new FileInputStream(file);ois = new ObjectInputStream(fis);obj = ois.readObject();} catch (Exception e) {e.printStackTrace();} finally {CloseUtils.close(fis);CloseUtils.close(ois);}return obj;}static class CloseUtils {public static void close(Closeable stream) {if (stream != null) {try {stream.close();} catch (IOException e) {e.printStackTrace();}}}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
2.对象保存
/** 用户信息 */
public class UserInfoBean implements Serializable {public static final String TAG = "UserInfoBean";private static final long serialVersionUID = 1L;private String name;private String tel;private String token;public UserInfoBean(String name, String tel, String token) {super();this.name = name;this.tel = tel;this.token = token;save();}private void save() {StreamUtil.saveObject(XApp.getCacheFile() + TAG, this);}// App退出的时候,清空本地存储的对象,否则下次使用的时候还会存有上次遗留的数据public void reset() {this.name = null;this.tel = null;this.token = null;save();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
3.从Application中读取
public class XApp extends Application {private UserInfoBean userinfo;/** 因为每次App被回收重建的时候都会执行onCreate方法,mContext对象永远不会为空 */public static XApp mContext;@Overridepublic void onCreate() {super.onCreate();mContext = this;}public UserInfoBean getUserinfo() {// 从本地读取if (null == userinfo) {userinfo = (UserInfoBean) StreamUtil.restoreObject(getCacheFile()+ UserInfoBean.TAG);}return userinfo;}public void setUserinfo(UserInfoBean userinfo) {this.userinfo = userinfo;}public static String getCacheFile() {return mContext.getCacheDir().getAbsolutePath();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
注意事项
1.App退出的时候需要执行,UserInfoBean的reset方法清除存储的数据,否则下次进入App的时候,可能会得到上次遗留下的脏数据
2.在使用userInfo的时候还是需要加上空判断,因为还是会存在userInfo为空,从本地磁盘读取同样为空的情况
userInfo = ((XApp) getApplicationContext()).getUserinfo();if (userInfo != null) {Toast.makeText(getApplicationContext(),userInfo.toString(), Toast.LENGTH_LONG).show();}
- 1
- 2
- 3
- 4
- 5
3.如果使用UserInfoBean的set方法修改数据,修改后需要同步本地存储的数据
public void setName(String name) {this.name = name;save();}public void setTel(String tel) {this.tel = tel;save();}public void setToken(String token) {this.token = token;save();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
重构代码
不足
- 代码混乱,在UserInfoBean类中操作数据,在Application类中仍然操作读取数据,显得冗余。reset方法放在Application类显得冗余,放在具体对象实体类中又不容易查找,不符合面向对象开发的-单一职责原则。考虑设计一个单例的全局变量类统一操作这一类的数据
- 对象从序列化和反序列化是一个磁盘操作,现在每次修改对象数据都会进行一次这样的操作,磁盘操作本身就存在风险,多次操作风险变高了。
- 对于不支持序列化数据格式如HashMap
重构代码
/*** 保存全局对象的单例*/
public class SaveInstance implements Serializable, Cloneable {public final static String TAG = "SaveInstance";private static final long serialVersionUID = 1L;private static SaveInstance instance;public static SaveInstance getInstance() {if (null == instance) {Object obj = StreamUtil.restoreObject(XApp.getCacheFile() + TAG);if (null == obj) {obj = new SaveInstance();StreamUtil.saveObject(XApp.getCacheFile() + TAG, obj);}instance = (SaveInstance) obj;}return instance;}private UserInfoBean userInfo;private String title;private HashMap<String, Object> map;public UserInfoBean getUserInfo() {return userInfo;}public String getTitle() {return title;}public HashMap<String, Object> getMap() {return map;}/** 是否需要保存到本地 */public void setUserInfo(UserInfoBean userInfo, boolean needSave) {this.userInfo = userInfo;if (needSave) {save();}}public void setTitle(String title, boolean needSave) {this.title = title;if (needSave) {save();}}/*** 把不支持序列化的对象转换成String类型存储*/public void setMap(HashMap<String, Object> map, boolean needSave) {this.map = new HashMap<String, Object>();if (null == map) {StreamUtil.saveObject(XApp.getCacheFile() + TAG, this);return;}Set set = map.entrySet();Iterator it = set.iterator();while (it.hasNext()) {Entry entry = (Entry) it.next();this.map.put(String.valueOf(entry.getKey()),String.valueOf(entry.getValue()));}if (needSave) {save();}}private void save() {StreamUtil.saveObject(XApp.getCacheFile() + TAG, this);}// App退出的时候,清空本地存储的对象,否则下次使用的时候还会存有上次遗留的数据public void reset() {this.userInfo = null;this.title = null;this.map = null;save();}// -----------以下3个方法用于序列化-----------------@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}// 保证单例序列化后不产生新对象public SaveInstance readResolve() throws ObjectStreamException,CloneNotSupportedException {instance = (SaveInstance) this.clone();return instance;}private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException {ois.defaultReadObject();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
后序
- 使用这种方式一定程度上可以解决已有代码出现,App后台回收引发空指针异常的问题,但是这个方式解决的核心是使用磁盘操作,很容易引发ANR,这始终是一个那么可靠的临时方案
- 使用了单例模式,那么在序列化的时候就应该实现Cloneable接口,加入readResolve,readObject,clone方法。不然在反序列化的时候回来得对象和原来的对象不是同个对象
- 代码显得臃肿难看
demo下载地址
参考资料:《App研发录》
Android-Application被回收引发空指针异常分析(消灭全局变量)相关推荐
- Android AdapterView 源码分析以及其相关回收机制的分析
前言 忽然,发现,网上的公开资料都是教你怎么继承一个baseadapter,然后重写那几个方法,再调用相关view的 setAdpater()方法, 接着,你的item 就显示在手机屏幕上了.很少有人 ...
- Android MVP Presenter 中引发的空指针异常
Android MVP Presenter 中引发的空指针异常 参考文章: (1)Android MVP Presenter 中引发的空指针异常 (2)https://www.cnblogs.com/ ...
- 转:Android应用开发性能优化完全分析
转自:http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜 ...
- Android 操作系统的进程回收机制
Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理 ...
- Android应用开发性能优化完全分析,移动应用开发课程报告
如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当 ...
- Android应用开发性能优化完全分析
1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...
- Android 操作系统的内存回收机制
转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的 ...
- Android 操作系统的内存回收机制。
转载自品略网:http://www.pinlue.com/article/2020/03/0808/089994336918.html Android APP 的运行环境 Android 是一款基于 ...
- Android中app卡顿原因分析示例
http://www.cnblogs.com/zhucai/p/weibo-graphics-performance-analyse.html 朱才 专注于Android图形动画 MIUI工程师 博客 ...
最新文章
- 简谈 Java 中的泛型通配符
- kinect c++
- 数据结构二分法算法的步骤_数据结构与算法之算法思想:二分法搜索实现(python)...
- OpenCV霍夫变换的演示代码(附完整代码)
- qt 程序windows 上发布
- [渝粤教育] 武汉理工大学 认识武理 参考 资料
- (二)ElasticSearch6.1.1 Python API
- 扩展图形输出 1111 java
- kafka 如何做到1秒发布百万级条消息?
- flutter上拉抽屉效果 flutter拖动抽屉效果
- JavaScript 简史 1
- 页面jquery调试的一个宝贵经验(类似于Eclipse中的写出一个对象点它的方法时候用alt加/可以跳出来它所有的方法)...
- Python3笔记——IDE的选择
- Gauss-Jordan法求逆矩阵
- 中孚保密检查客户端 完全卸载_中孚计算机终端保密检查工具
- 【C语言实现】goto语句实现简易关机程序
- 微软面试58道逻辑面试题
- 年轻人,你为什么要来阿里搞技术?
- 使用VMware安装Ubuntu虚拟机 - 完整教程
- 100个世界上鲜为人知的“常识”
热门文章
- Python数据分析三剑客学习笔记Day3——pandas包的使用:认识series类型,DataFrame类型,读取excel表格数据及数据操作
- python对mp3音乐剪切
- 基于全生命周期的主数据管理:MDM详解与实战学习02 第二章 主数据管理的内涵
- PG内核分析 Question and Answer
- IPFS云服务器预售登录系统,ipfs 云服务器
- 半年损失超20亿美元,区块链安全赛道被资本疯抢
- 使用nw.js将网址打包生成exe可安装程序支持xp系统
- 英语和计算机水平,关于我的英语和计算机水平的分析
- 微信小程序(微信开发者工具及工程创建、小程序配置、逻辑层、模块化)
- linux runtime pm机制的深入理解