Android-Application被回收引发空指针异常分析(消灭全局变量)

标签: 全局变量异常应用崩溃空指针异常全局变量存储
2016-05-19 18:39 1069人阅读 评论(0) 收藏 举报
 分类:
Android开发(27) 

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]

问题描述

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进程被系统回收的场景

  1. 开启应用,进入登录页,登录成功跳转到主页
  2. 按Home键退出应用
  3. 使用DDMS-Stop Process结束进程
  4. 回到应用中,正常使用(注:现在处于一个新的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

重构代码

不足

  1. 代码混乱,在UserInfoBean类中操作数据,在Application类中仍然操作读取数据,显得冗余。reset方法放在Application类显得冗余,放在具体对象实体类中又不容易查找,不符合面向对象开发的-单一职责原则。考虑设计一个单例的全局变量类统一操作这一类的数据
  2. 对象从序列化和反序列化是一个磁盘操作,现在每次修改对象数据都会进行一次这样的操作,磁盘操作本身就存在风险,多次操作风险变高了。
  3. 对于不支持序列化数据格式如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被回收引发空指针异常分析(消灭全局变量)相关推荐

  1. Android AdapterView 源码分析以及其相关回收机制的分析

    前言 忽然,发现,网上的公开资料都是教你怎么继承一个baseadapter,然后重写那几个方法,再调用相关view的 setAdpater()方法, 接着,你的item 就显示在手机屏幕上了.很少有人 ...

  2. Android MVP Presenter 中引发的空指针异常

    Android MVP Presenter 中引发的空指针异常 参考文章: (1)Android MVP Presenter 中引发的空指针异常 (2)https://www.cnblogs.com/ ...

  3. 转:Android应用开发性能优化完全分析

    转自:http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜 ...

  4. Android 操作系统的进程回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理 ...

  5. Android应用开发性能优化完全分析,移动应用开发课程报告

    如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当 ...

  6. Android应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  7. Android 操作系统的内存回收机制

    转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的 ...

  8. Android 操作系统的内存回收机制。

    转载自品略网:http://www.pinlue.com/article/2020/03/0808/089994336918.html Android APP 的运行环境 Android 是一款基于 ...

  9. Android中app卡顿原因分析示例

    http://www.cnblogs.com/zhucai/p/weibo-graphics-performance-analyse.html 朱才 专注于Android图形动画 MIUI工程师 博客 ...

最新文章

  1. 简谈 Java 中的泛型通配符
  2. kinect c++
  3. 数据结构二分法算法的步骤_数据结构与算法之算法思想:二分法搜索实现(python)...
  4. OpenCV霍夫变换的演示代码(附完整代码)
  5. qt 程序windows 上发布
  6. [渝粤教育] 武汉理工大学 认识武理 参考 资料
  7. (二)ElasticSearch6.1.1 Python API
  8. 扩展图形输出 1111 java
  9. kafka 如何做到1秒发布百万级条消息?
  10. flutter上拉抽屉效果 flutter拖动抽屉效果
  11. JavaScript 简史 1
  12. 页面jquery调试的一个宝贵经验(类似于Eclipse中的写出一个对象点它的方法时候用alt加/可以跳出来它所有的方法)...
  13. Python3笔记——IDE的选择
  14. Gauss-Jordan法求逆矩阵
  15. 中孚保密检查客户端 完全卸载_中孚计算机终端保密检查工具
  16. 【C语言实现】goto语句实现简易关机程序
  17. 微软面试58道逻辑面试题
  18. 年轻人,你为什么要来阿里搞技术?
  19. 使用VMware安装Ubuntu虚拟机 - 完整教程
  20. 100个世界上鲜为人知的“常识”

热门文章

  1. Python数据分析三剑客学习笔记Day3——pandas包的使用:认识series类型,DataFrame类型,读取excel表格数据及数据操作
  2. python对mp3音乐剪切
  3. 基于全生命周期的主数据管理:MDM详解与实战学习02 第二章 主数据管理的内涵
  4. PG内核分析 Question and Answer
  5. IPFS云服务器预售登录系统,ipfs 云服务器
  6. 半年损失超20亿美元,区块链安全赛道被资本疯抢
  7. 使用nw.js将网址打包生成exe可安装程序支持xp系统
  8. 英语和计算机水平,关于我的英语和计算机水平的分析
  9. 微信小程序(微信开发者工具及工程创建、小程序配置、逻辑层、模块化)
  10. linux runtime pm机制的深入理解