最近改bug的时候看到我们的软件在本地如果不点击退出登陆,直接用任务管理器杀死的话,在服务器端依旧保持着在线状态,直到Token过期(我们的Token四个小时才过期。。。我也不懂为什么要这么设计)。但是既然存在这样的问题,那么就想解决方案就好了。
####方案计划:
监听进程是否还在存活,如果已经被清理了,就向服务器发送退出请求。也就是:
if(APP.isDead()){
LoginOut();
}
计划是有了,接下来就应该思考如何去编码实现。
####编码实现:
第一步:监听进程这种任务肯定是要放在Service中利用定时器+ActivityManager来解决。代码片段:
private boolean isBackgroundRunning() {
activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
if (activityManager == null) {
return false;
}
List<ActivityManager.RunningTaskInfo> processList = activityManager.getRunningTasks(100);
for (ActivityManager.RunningTaskInfo info : processList) {
//在这里只能用baseActivity,今天看到有些人写的是用topActivity,不是不行
//问题就在于 如果程序中调用了相机之类的系统应用,那么此时topActivity的包名与当前进程包名不匹配
//就会出现错误监听的情况
if (info.baseActivity.getPackageName().startsWith(PackageName)) {
return true;
}
}
return false;
}
第二步:在启动Service的时候启动定时器,也就是在OnCreate中定义好定时器,并执行。代码片段:
@Override
public void onCreate() {
//每5秒向handler发送一次消息 而不是直接在TimerTask中进行判断
timerMail.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.what = 1;
mHandler.sendMessage(message);
}
}, 15000, 5000);
super.onCreate();
}
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
try {
//没有过多的操作,简单粗暴 通俗易懂
if(!isBackgroundRunning()){
logout();
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
});
第三步:当请求发送成功之后,调用onDestroy方法,销毁当前服务。代码片段:
@Override
public void onDestroy() {
//将定时器取消
if (timerMail!= null){
timerMail.cancel();
}
//Handler中提供了去清除Message和Runnable的方法,不再占用多余的队列空间
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
第四步:大功告成,开始测试。贴上全部代码:
public class CheckService extends Service {
private static final String PackageName = "com.****.****";
private final Timer timerMail = new Timer();
private LogUtils mLogUtils = LogUtils.getInstance();
private ActivityManager activityManager=null;
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
mLogUtils.e(" 开启检查服务" , 2);
timerMail.schedule(new TimerTask() {
@Override
public void run() {
mLogUtils.e(" 发送消息", 2);
Message message = new Message();
message.what = 1;
handlerMail.sendMessage(message);
}
}, 15000, 5000);
super.onCreate();
}
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
try {
if(!isBackgroundRunning()){
mLogUtils.e(" 退出操作", 2);
logout();
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
});
private boolean isBackgroundRunning() {
activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
if (activityManager == null) {
return false;
}
List<ActivityManager.RunningTaskInfo> processList = activityManager.getRunningTasks(100);
for (ActivityManager.RunningTaskInfo info : processList) {
if (info.baseActivity.getPackageName().startsWith(PackageName)) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see android.app.Service#onDestroy()
*/
@Override
public void onDestroy() {
if (timerMail!= null){
timerMail.cancel();
}
mLogUtils.e(" 销毁服务", 2);
super.onDestroy();
}
public void logout() {
***
}
}
####思考问题:
这里打断点是没有用的,所以我们只能利用打印log文件的方法去检查程序运行的状态。
这就是仅仅运行了一次之后打印出来的Log,我明明只是在登陆成功后调用了一次StartService方法,为什么OnCreate会被执行两次?
于是,继续测试,这次在打印log的时候在打印出当前进程ID,再来看一下:
//获取进程ID
int mProcessId = android.os.Process.myPid()
####结论猜想:
当应用进程被系统直接清理之后,当前服务也会被一并清理,但是Handler的Message Queue中存在着还没有被来的及处理的Message,于是重新打开一个进程服务,继续处理消息时,发现应用程序已经不在运行了,此时发送退出请求,清理数据,销毁当前Service并将之前的Service在当前进程中一并销毁。
####失败声明:
这个方法在华为的某些手机上是失败的。。。当进程被清理的时候,不存在第二次启动。其他机型上没有做过测试,当然我们的软件是在我们给客户提供的机子上运行的,所以没有做过多的机型适配。(我自己用的锤子科技的M1,测试成功)。