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;
}
@Override
public String toString() {
return "UserInfoBean [name=" + name + ", tel=" + tel + ", token="
+ token + "]";
}
}
2.因为很多页面都有可能会设计到使用网络访问,获取用户信息,于是把它存储到Application中。
public class XApp extends Application {
private UserInfoBean userinfo;
public UserInfoBean getUserinfo() {
return userinfo;
}
public void setUserinfo(UserInfoBean userinfo) {
this.userinfo = userinfo;
}
}
3.模拟登录成功,存储接口返回的UserInfoBean
public class LoginActivity extends Activity {
private Button btnLogin;
private ProgressDialog pdLogin;
@Override
protected 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() {
@Override
public void onClick(View v) {
// 弹出等待对话框 模拟登录耗时操作
pdLogin.show();
btnLogin.getHandler().postDelayed(new Runnable() {
@Override
public void run() {
pdLogin.dismiss();
// 存储数据
UserInfoBean userInfo = new UserInfoBean("Tony",
"17011110000", "tokenabcdefg");
((XApp) getApplication()).setUserinfo(userInfo);
MainActivity.actionStart(LoginActivity.this);
}
}, 1500);
}
});
}
}
4.获取Application中的UserInfoBean使用
public class MainActivity extends Activity {
private Button btnShowUserInfo;
private UserInfoBean userInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnShowUserInfo = (Button) findViewById(R.id.btnShowUserInfo);
btnShowUserInfo.setOnClickListener(new OnClickListener() {
@Override
public 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));
}
}
模拟切换到后台,App进程被系统回收的场景
userInfo = ((XApp) getApplicationContext()).getUserinfo();
if (null != userInfo) {
// do something
}
可以解决问题,建议新项目这样做,但是项目如果已经上线,重构这一块问题稍显麻烦
public class MainActivity extends Activity {
private Button btnShowUserInfo;
private UserInfoBean userInfo;
@Override
protected 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() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), userInfo.toString(),
Toast.LENGTH_LONG).show();
}
});
}
//定义给,外部调用启动MainActivity
public static void actionStart(Context context, UserInfoBean bean) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("bean", bean);
context.startActivity(intent);
}
}
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();
}
}
}
}
}
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();
}
}
3.从Application中读取
public class XApp extends Application {
private UserInfoBean userinfo;
/** 因为每次App被回收重建的时候都会执行onCreate方法,mContext对象永远不会为空 */
public static XApp mContext;
@Override
public 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.App退出的时候需要执行,UserInfoBean的reset方法清除存储的数据,否则下次进入App的时候,可能会得到上次遗留下的脏数据
2.在使用userInfo的时候还是需要加上空判断,因为还是会存在userInfo为空,从本地磁盘读取同样为空的情况
userInfo = ((XApp) getApplicationContext()).getUserinfo();
if (userInfo != null) {
Toast.makeText(getApplicationContext(),
userInfo.toString(), Toast.LENGTH_LONG).show();
}
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();
}
/** * 保存全局对象的单例 */
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个方法用于序列化-----------------
@Override
protected 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();
}
}