神奇BUG——Activity变得不可见时服务就停止
本篇主要讲当后台Service和Activity生命周期相关联时的一种特殊情况
在各种讲解Activity生命周期相关的文章里下面的这张图一定是不会少的:
生命周期.png
从上图中我们可以看出,当Activity从创建开始会依次执行onCreate(...)、onStart()、onResume()等函数,当Activity 变得可见而不可操作时比如弹出了一个非全屏透明的PopWindown此时任然能够看到部分的Activity,那么Activity会回调onPause()而不会回调onStop(...),如果当用户按手机HOME键回到桌面,此时Activity会马上回调onStop(...)然后当用户点击App图标进入应用时此时Activity便会依次回调onRestart(....)、onStart()、onResume(...)等生命周期函数,同样也正是由于onStart(...)和onStop(...)回调特性,我们一般会在onStart(...)中注册广播或者绑定服务,然后在onStop(...)中取消注册的广播以及解绑服务等。但是这样也可能造成一些 匪夷所思的BUG。
假如我们有一个音乐播放器,其中有一个后台播放音乐的service叫MediaService,然后我们Activity需要和Service进行交互,一般情况下我们都是在基类Activity(一般为抽象类,名字为BaseActivity)的onCreate(...)中启动服务,然后在onStart(...)中绑定服务就像下面这样:
``
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(mContext, MediaService.class);
startService(intent);
、、、、、、、、、、、
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, MediaService.class);
bindService(intent, mServiceConnect,BIND_AUTO_CREATE);
}
``
然后通过ServiceConnection返回的IBinder对象与后台Service进行交互
但是,我们假设有这么一种情况,当用户进入APP后成功创建了后台服务并绑定成功后,用户下拉通知栏通过点击通知栏上的关闭服务将播放的服务手动停止后,
然后按HOME键回到手机主界面,在点击APP图标进入APP点击播放按钮播放然后再按HOME键回到手机主界面,此时你会发现后台播放器同时也停止播放了。让我们回顾一下这个操作流程和上面的Activity生命周期介绍,梳理出Activity在这个过程中的生命周期回调:
刚开始创建Activity——>onCreate(...)——>onStart()——>onResume()
——>下拉通知栏——>onPause(......)——>第一次按HOME键回到手机主界面
——>onStop(....)——>点击App图标进入APP——>onRestart()——>onStart()——>onResume(...)——>第二次按HOOME键回到手机界面——>onPause(...)——>onStop(....)
其中我们在下拉通知栏时点击了停止服务的按钮,终止了Service;
回顾上述生命周期我们发现onCreate(..)只执行了一次即只调用了一次Context.startService(Intent),而onStart()执行了2次,当我们在点击下拉通知栏时关闭了Service,而后面我们并没有再次启动Service,全是在onStart()中绑定Service,但是通过bindService(...)启动的服务,其生命周期会受到宿主的影响,在本例中就是Activity,由于我们在Activity的onStop(....)中解绑了服务,所以就会导致我们的Activity一旦变得不可见时,Service就会终止服务。
这样我们就需要找到一个方法能够在Activity从不可见变为可见时,在onStart(...)回调之前,调用Context.startService(Intent)将服务重新拉起来,分析最开始的那张图,我们发现当Activity从第一次创建成功后,变为可见然后经历不可见,可见时,onRestart(...)会回调,且回调时期在onStart(...)之前,SO,我们可以在Activity 的onRestart(...)中再次拉起服务。如下:
``
@Override
protected void onRestart() {
super.onRestart();
//判断服务是否在运行,如果不在运行则拉起服务
if (!serviceIsRunning(mContext,MediaService.class.getName())){
Intent intent = new Intent(mContext, MediaService.class);
startService(intent);
LogUtil.d(TAG,"onRestart() 重启Service");
}
}
``
综上所述我们可以分别在onCreate(...)和onResatart(....)中启动服务,然后在onStart(....)和onStop中分别绑定服务,与解绑服务.这样就能解决上述的bug了。