上篇文章,我们只是翻译了Android官方文档,具体详情请看,Android Service(一) 初识。今天就通过实例来演示Service的启动以及生命周期等特征。
一、实战演示。
1.新建工程,创建项目。
2.新建一个类,继承自Service,实现它的onBind()方法,顺便重写它的几个方法,
public class MyService extends Service {
private static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "--------->onCreate: ");
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.e(TAG, "--------->onStart: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "--------->onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "--------->onDestroy: ");
super.onDestroy();
}
}
在几个方法中添加了打印log,接着需要在AndroidManifest.xml中注册,如需访问网络,还需要添加网络请求权限。
...
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService">
<intent-filter>
<action android:name="cn.xinxing.service" />
</intent-filter>
</service>
</application>
...
在主界面中添加一个启动服务按钮和停止服务按钮,点击启动服务按钮,启动一个Service,
public class MainActivity extends AppCompatActivity {
Intent intent = new Intent("cn.xinxing.service");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(intent);
}
});
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopService(intent);
}
});
}
}
代码都比较简单,就不多解释了。好了,下面就可以运行该程序了,
运行后,点击启动服务按钮,可以看到输出了日志,分别依次调用了onCreate()->onStartCommand()->onStart(),
当我们再点击启动服务按钮,分别依次调用了onStartCommand()->onStart(),再次点击服务启动按钮,依旧还是只调用onStartCommand()->onStart()。
当点击停止服务按钮时,会调用Service的onDestroy()方法,Service就停止了,
此时,我们再次点击启动服务按钮,大家想一想,会调用什么方法呢?
和你想的一样吧!重新启动了一个新的Service。后面不管做什么操作,就和前面所讲的一样了!
小结:
1.通过startService()启动Service,会依次调用onCreate()->onStartCommand()->onStart(),一旦Service启动成功,再次调用startService()时,只会调用onStartCommand()和onStart(),不会再调用onCreate(),也就是说onCreate()只会执行一次(该Service没有销毁时)。通过stopService()方法停止Service,会调用Service的onDestroy()方法。
2.onStartCommand()和onStart()的有什么关系。
在API 2.0之前,只有onStart()方法,而2.0之后,推荐使用onStartCommand()方法,其实onStartCommand()内部也是调用了onStart()方法,所以我们在开发中只重写onStartCommand()就可以了,而onStartCommand()方法的作用是告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止 等等。
下面给出一张完整的log输出截图,
3.下面我们在Service的onCreate()方法中模拟一个耗时操作,看看会发生什么,
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "--------->onCreate: ");
try {
Thread.sleep(50*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
开启了一个模拟耗时操作,结果程序最终就崩溃了!下面是日志截图,可以看到ANR了,并且错误日志保存在“/data/anr/traces.txt”中,
可以看到,我们在Service的onCreate()方法中模拟一个耗时操作,结果导致ANR,所以,在Service当中,如果有耗时操作,请开始子线程来执行。
4.通过bindService()启动Service。
使用bindService()启动Service,这种模式是客户端-服务端模式(client-server),即服务端是Service,客户端是调用者,例如是某个Activity,客户端-服务端是可以相互通信的。
bindService(Intent service, ServiceConnection conn,int flags)
第一个参数:Intent指示对应的Service对象;
第二个参数:实现了 ServiceConnection接口的对象,conn是一个代表与service连接状态的类,当我们连接service成功或失败时,会主动触发其内部的onServiceConnected或onServiceDisconnected方法。如果我们想要访问service中的数据,可以在onServiceConnected()方法中进行实现;
第三个参数:Flags,在进行服务绑定时,其标志位可以为BIND_AUTO_CREATE、BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND等。其中BIND_AUTO_CREATE表示当收到绑定请求时,如果服务尚未创建,则即刻创建,在系统内存不足,需要先销毁优先级组件来释放内存,且只有驻留该服务的进程成为被销毁对象时,服务才可被销毁;BIND_DEBUG_UNBIND通常用于调试场景中判断绑定的服务是否正确,但其会引起内存泄漏,因此非调试目的不建议使用;BIND_NOT_FOREGROUND表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位在Froyo中引入;
下面还是看具体实例吧!首先是MyService的代码,
...
//通过binder实现调用者client与Service之间的通信
private MyBinder binder = new MyBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "--------->onBind: ");
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "--------->onUnbind: ");
return super.onUnbind(intent);
}
class MyBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
...
在MyService中增加了一个内部类MyBinder,为了测试,MyBinder类只是简单实现,返回了MyService自身,并且重写了onBind()和onUnbind()方法,onBind()返回了MyBinder对象。事实上,在实际开发中,我们需要在MyBinder类,去执行一些任务,向客户端暴露一些方法等。如果了解AIDL的话,这一块是很容易理解的。如果想了解AIDL,详情请看
Android IPC之AIDL浅谈。
接着看看MainActivity的实现,
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
});
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unbindService(serviceConnection);
}
});
}
MyService myService;
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "-------------->onServiceConnected");
myService = ((MyService.MyBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "-------------->onServiceDisconnected");
myService = null;
}
};
...
运行后,点击启动服务按钮,下面是截图,
调用bindService()后,可以看到,执行了Service的onCreate()->onBind()->onServiceConnected(),调用者和服务端已经绑定成功,建立连接。
此时,如果我们再次点击启动服务按钮,会有什么发生吗?还是上截图,
咦,什么都没有!这个逻辑时这样的,我们在绑定Service的时候,如果service没被创建,那么调用一次onCreate(),然后调用onBind(),多次绑定时,不会多次调用onBind()。
我们点击停止服务按钮,会发生什么呢?不多说了,上截图,
调用unbindService()后,可以看到,执行了Service的onUnbind()->onDestroy()。此时说明,Service已经停止了。细心的你,有没有发现有个方法没有调用?你知道是哪个方法吗?是----->onServiceDisconnected()方法,为何我们解除绑定了,该回调方法没有调用呢?
这是因为onServiceDisconnected()方法在正常情况下是不被调用的,它的调用时机是当Service服务被异外销毁时,例如内存的资源不足时这个方法才被自动调用。
如果这个时候,不小心再点一下停止服务按钮,应该发生什么呢?
咦,应用报错了!根据log信息“Service not registered: cn.xinxing.service.MainActivity$3@420946f0”,可以得知,提示说Service没有注册。因此,我们在解除绑定(调用unbindService())时,需要多注意,具体改动,详见下文。
如果,我们绑定了,然后直接点击返回键,会出现什么情况呢?看截图,
可以看到log信息输出,说leaked ServiceConnection,并且最终又调用了onUnbind()->onDestroy()方法。因此,如果绑定过Service 记得要解除,虽然应用没有崩溃,但是这是一种好习惯!
5.通过startService()和bindService()同时启动Service。
如果我们同时通过startService()和bindService()启动Service,那么具体的处理逻辑是什么呢?那么,还是照旧,通过代码实例来演示具体的执行过程。
首先我们看看MainActivity的主要代码,
...
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService();
}
});
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopService();
}
});
findViewById(R.id.btn_unbind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doUnbindService();
}
});
...
...
void startService() {
startService(intent);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void stopService(){
stopService(intent);
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(serviceConnection);
mIsBound = false;
}
}
...
当点击启动按钮时,同时调用了startService()和bindService(),log输入日志截图如下,
可以看到,log输出的内容和我们前面讲解的一致,就是分别调用startService()和bindService()时调用的方法。
(1).那么此时,当点击stop Service按钮时,会出现什么情况呢!我们还是看看log会输入什么内容!什么东东都没有?这是什么情况?为何这样呢?
这是因为,我们此时启动的Service,已经和启动者绑定了,如果要停止该Service,不仅要调用 stopService(intent)方法,还需要调用 unbindService(serviceConnection)方法,所以当我们只调用stopService(intent)方法时,是不会有输出的。那么如果此时我们再点击unbind Service按钮,会是什么情况呢?依旧上截图,
可以看到,Service已经解绑并且已经停止了。
(2).如果我们先点击unbind Service按钮,最后再点击stop Service按钮会发生什么呢?好期待哦!接着看下文吧!
先点击unbind Service按钮,看截图,
可以看到只是调用了解绑功能,Service还未停止,那么此时,再点击stop Service按钮,
可以看到Service已经停止了。
通过上面的实例,我们可以看出,如果我们在启动一个Service时,同时调用了startService()和bindService(),那么在停止该Service时,也要同时调用 stopService(intent)和 unbindService(serviceConnection);否则,Service可能会未正常停止。
二.startService()和bindService()启动Service的区别。
1.首先两者的生命周期是不一样的,
通过这张图,我们可以很明显的看出二者整个生命周期的过程是不一样的。
2.两者和启动者的关系。
startService()启动的Service和启动者的生命周期无关,必须要显示调用stopService(intent)方法,才能停止该Service;
bindService()启动的Service和启动者的生命周期有关,当启动者销毁时,会自动销毁该Service。
3.两者的具体使用场景。
startService(),常用于启动一个后台服务,例如推送服务、心跳服务,保持一个长久的链接;
bindService(),常用于调用一个远程连接服务,例如AIDL,使用远程服务暴露的功能。
ps:
最后展示修改过MainActivity的代码,
public class MainActivity extends AppCompatActivity {
Intent intent = new Intent("cn.xinxing.service");
private static final String TAG = "MainActivity";
private boolean mIsBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doBindService();
}
});
findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doUnbindService();
}
});
}
MyService myService;
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "-------------->onServiceConnected");
myService = ((MyService.MyBinder) service).getService();
mIsBound=true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "-------------->onServiceDisconnected");
myService = null;
}
};
void doBindService() {
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(serviceConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
在绑定过程中加入标志判断,这样在解绑就不会发生异常了!看过上篇文章的话 ,应该对这些代码不会陌生的。这些代码是来自官网。
小结:
1.Context.BIND_AUTO_CREATE表明只要绑定存在,就自动建立Service;同时也告知Android系统,这个Service的重要程度与调用者相同,除非考虑终止调用者,否则不要关闭这个Service;
2.如果service没被创建,那么调用一次onCreate(),然后调用onBind(),多次绑定时,不会多次调用onBind();
3.通过unbindService()函数取消绑定Servcie时,onUnbind()函数将被调用;
4.如果onUnbind()函数的返回true,则表示在调用者绑定新服务时, onRebind()函数将被调用;
5.取消绑定仅需要使用unbindService()方法,并将ServiceConnnection传递给unbindService()方法需注意的是,unbindService()方法成功后,系统并不会调用onServiceDisconnected(),因为onServiceDisconnected()仅在意外断开绑定时才被调用;
6.当bindService后,不能stopService,需要通过unBindService()来解除绑定。