为什么不能往Android的Application对象里存储数据
在一个App里面总有一些数据需要在多个地方用到。这些数据可能是一个 session token,一次费时计算的结果等。通常为了避免activity之间传递对象的开销 ,这些数据一般都会保存到持久化存储里面。
有人建议将这些数据保存到 Application 对象里面,这样这些数据对所有应用内的activities可用。这种方法简单,优雅而且……完全扯淡。
假设把你的数据都保存到Application对象里面去了,那么你的应用最后会以一个NullPointerException 异常crash掉。
一个简单的测试案例
代码
Application 对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// access modifiers omitted for brevity
class
MyApplication
extends
Application
{
String
name
;
String
getName
(
)
{
return
name
;
}
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
}
|
第一个activity,我们往application对象里面存储了用户姓名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// access modifiers omitted for brevity
class
WhatIsYourNameActivity
extends
Activity
{
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
writing
)
;
// Just assume that in the real app we would really ask it!
MyApplication
app
=
(
MyApplication
)
getApplication
(
)
;
app
.
setName
(
"Developer Phil"
)
;
startActivity
(
new
Intent
(
this
,
GreetLoudlyActivity
.
class
)
)
;
}
}
|
第二个activity,我们调用第一个activity设置并存在application里面的用户姓名:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// access modifiers omitted for brevity
class
GreetLoudlyActivity
extends
Activity
{
TextView
textview
;
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
reading
)
;
textview
=
(
TextView
)
findViewById
(
R
.
id
.
message
)
;
}
void
onResume
(
)
{
super
.
onResume
(
)
;
MyApplication
app
=
(
MyApplication
)
getApplication
(
)
;
textview
.
setText
(
"HELLO "
+
app
.
getName
(
)
.
toUpperCase
(
)
)
;
}
}
|
测试场景
- 用户启动app。
- 在 WhatIsYourNameActivity里面,要求用户输入姓名,并存储到 MyApplication。
- 在 GreetLoudlyActivity里面,你从MyApplication 对象中获得用户姓名,并且显示。
- 用户按home键离开这个app。
- 几个小时后,Android系统为了回收内存kill掉了这个app。到目前为止,一切尚好。接下来就是crash的部分了…
- 用户重新打开这个App。
- Android系统创建一个新的 MyApplication 实例并恢复 GreetLoudlyActivity。
- GreetLoudlyActivity 从新的 MyApplication 实例中获取用户姓名,可得到的为空,最后导致NullPointerException。
为什么会Crash?
在上面这个例子中,app会crash得原因是这个 Application 对象是全新的,所以这个name 变量里面的值为 null,当调用String#toUpperCase() 方法时就导致了NullPointerException。
整个问题的核心在于:application 对象不会一直呆着内存里面,它会被kill掉。与大家普遍的看法不同之处在于,实际上app不会重新开始启动。Android系统会创建一个新的Application 对象,然后启动上次用户离开时的activity以造成这个app从来没有被kill掉得假象。
你以为你的application可以保存数据,却没想到你的用户在没有打开activity A 之前就就直接打开了 activity B ,于是你就收到了一个 crash 的 surprise。
有哪些替代方法呢?
这里没啥神奇的解决方法,你可以试试下面几种方法:
- 直接将数据通过intent传递给 Activity 。
- 使用官方推荐的几种方式将数据持久化到磁盘上。
- 在使用数据的时候总是要对变量的值进行非空检查。
如果模拟App被Kill掉
更新: Daniel Lew指出,kill app更简单的方式就是使用DDMS里面“停止进程” 。你在调试你的应用的时候可以使用这招。
为了测试这个,你必须使用一个Android模拟器或者一台root过的Android手机。
- 使用home按钮退出app。
- 在终端里:
123456789101112131415# find the process idadb shell ps# then find the line with the package name of your app# Mac / Unix : save some time by using grep :adb shell ps | grep your . app . package# The result should look like :# USER PID PPID VSIZE RSS WCHAN PC NAME# u0 _a198 21997 160 827940 22064 ffffffff 00000000 S your . app . package# Kill the app by PIDadb shell kill - 9 21997# the app is now killed - 长按home按钮回到之前的app。
你现在是出于一个新的application实例中了。总结
不要在application对象里面储存数据,这容易出错,导致你的app crash。
要么将你后面要用的数据保存到磁盘上面或者保存到intent得extra里面直接传递给activity 。这些结论不但对application对象有用,对你app里面的单例对象(singleton)或者公共静态变量(public static)同样适用。