前几天在做一个密码管理本类的应用,遇到这么一个需求:
应用每次退出到后台,都要关闭存储密码信息的数据库,并在应用再切换到前台时,向用户展示一个输入框让用户输入访问口令(该口令用于对数据库进行加解密)。
这就要求我对应用的前后台切换进行监听。
为实现这个需求,我第一个想到就是借助Activity
生命周期各个回调方法来实现。
大致思路就是在onPause()
/onStop()
方法中关闭数据库,然后在onStart()
/onResume()
方法中检查数据库的状态,
然后根据这个状态值的来做出对应的动作。伪代码表示如下:
public class BaseActivity extends AppCompatActivity {
@Override
protected void onStart() {
super.onStart();
if(isSecureDataBaseClosed()){
showEnterPasswordActivity();
}else{
//do something else here
}
}
@Override
protected void onStop() {
super.onStop();
closeSecureDatabase();
}
}
兴冲冲地按照这个思路实现了代码,在虚拟机上测试,很快就暴露了问题:
当一个Activity
启动另外一个Activity
,或者一个Activity
从另外一个Activity
返回时,都会调用相应的onStart()
和onStop()
方法。
因此这个方法仅仅适用于应用只有单个Activity
情况,当有多个Activity
时,上面的思路并不能够达到需求。
于是只能看看有没有别的方法。例行google了一下,发现stackoverflow上早已有人问过类似的问题。有两个高票回答看起来都十分可行。于是决定整理一下,当作笔记。
1. 使用定时器TimerTask + Timer
第一个的思路跟我上面所述的方法十分地相似,但是解决了上述方法中存在的问题。
上述方法之所以不能工作,原因在于在onStop()
方法中马上就认为应用即将进入后台,并且马上关闭了数据库。这样就会对Activity
间的切换进行误判。
而stackoverflow上的方案就是对时间进行了一个判断。因为Activity
间的一次切换时间是比较短的,因此可以借助这一点来改良上述方案。
下面给出描述这个思路的伪代码:
private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;
@Override
public void onResume(){
super.onResume();
if(wasAppInBackground()){
//Do specific came-here-from-background code
}
stopTimer();
setAppWasInBackground(false);
}
@Override
public void onPause(){
super.onPause();
startTimer(MAX_ACTIVITY_TRANSITION_TIME_MS,TaskToSetAppWasInBackground(true));
}
简单概括这个方法,就是在onPause()
启动一个定时任务,在MAX_ACTIVITY_TRANSITION_TIME_MS
时间间隔之后才将应用状态设置为后台状态。
然后在onResume()
方法中读取应用状态,同时停止定时任务,将应用状态设置为不在后台。
这样一来,如果在onPause()
方法调用后,超过MAX_ACTIVITY_TRANSITION_TIME_MS
时间间隔才调用onResume()
方法,则认为应用是从后台切换到前台的,否则就认为是Activity
间的切换。
具体的实现代码在原帖里找到。
2. 使用ComponentCallbacks2
这其实是一个非常直接有效的方法,也是我最终选择的方法。从 Ice Cream Sandwich (API 14) 开始,Android 官方便提供了这么一个回调接口。
ComponentCallbacks2
继承自ComponentCallbacks
,在ComponentCallbacks
的 基础上添加了onTrimMemory(int)
回调。
我们首先来看一下 Android Developers 上对ComponentCallbacks2
的说明:
Extended ComponentCallbacks interface with a new callback for finer-grained memory management. This interface is available in all application components (Activity, Service, ContentProvider, and Application).
You should implement onTrimMemory(int) to incrementally release memory based on current system constraints. Using this callback to release your resources helps provide a more responsive system overall, but also directly benefits the user experience for your app by allowing the system to keep your process alive longer. That is, if you don't trim your resources based on memory levels defined by this callback, the system is more likely to kill your process while it is cached in the least-recently used (LRU) list, thus requiring your app to restart and restore all state when the user returns to it.
可见ComponentCallbacks2
的主要作用就是在内存状态变化的时候通知应用中的组件,让应用对其所占用的资源进行适当的释放,来降低被系统杀死的概率。ComponentCallbacks2
提供的回调适用于 Application 的各种组件。
在onTrimMemory(int)
回调中,系统提供给我们一个int
型的Level值,这个值代表着当前系统可用内存的状态。不同的值对应不同的级别。
其中有一个值为TRIM_MEMORY_UI_HIDDEN
,对这个level值的定义如下:
Your app's UI is no longer visible, so this is a good time to release large resources that are used only by your UI.
没错就是它,这个值代表了当前应用的UI已不再可见。通过它,我们就可以认为应用进入了后台。
清楚了原理之后,实现就变得非常简单了。首先我们要写一个实现ComponentCallbacks2
的类,比如这里命名为MemoryBoss
:
public class MemoryBoss implements ComponentCallbacks2 {
@Override
public void onConfigurationChanged(final Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
@Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// We're in the Background
}
// you might as well implement some memory cleanup here and be a nice Android dev.
}
}
当然如果不想写一个额外的类,使用匿名内部类也是可以的。
接着在应用的 Application 的onCreate()
方法中注册该回调:
MemoryBoss mMemoryBoss;
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mMemoryBoss = new MemoryBoss();
registerComponentCallbacks(mMemoryBoss);
}
}
如果不再需要该回调,可以通过unregisterComponentCallbacks(mMemoryBoss)
来注销它。不过一般不必要这么做。
当然,ComponentCallbacks2
还提供了许多不同的Level值来指明内存当前的状态。有兴趣可以去Android Developers查阅。
参考链接:
网友评论