作为Android四大组件之一的Service在Android中地位又多重要就不说了,单单能常驻系统后台就已经可以看出它的作用,什么下载器、音乐播放器都是在后台运行,前台供用户使用其他的app,总不可能让用户盯着进度条无聊的发呆吧!
但是对于初学Service的同学来说,看到Service居然有startService和bindService两种启动方式,立马就懵逼了!只需要一个服务,居然有两种启动方式,他们有什么区别?
Service的创建
Android项目中Service的创建很简单,只要两步走就可以了。
第一:创建一个类,继承自Service,实现继承的方法,并重写onCreate和onStartCommand方法,代码如下。
public class TestService extends Service {
@Override
public IBinder onBind(Intent intent) {
System.out.println("我在onbind");
return new Mybind();
}
@Override
public void onCreate() {
System.out.println("我在oncreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("我在onstartConnand");
return super.onStartCommand(intent, flags, startId);
}
}
第二:在AndroidManifest中的<application> </application>节点中注册Service,需要注意的是Android四大组件都是需要在AndroidManifest中注册的。
<service android:name="com.example.testservie.TestService"></service>
好了,走这两步,一个Service就已经创建好了,这只是一个套路、流程而已。
startService和bindService的不同
好了,轮到本文的重点了,研究启动Service的两种方式的不同之处了。
startService启动服务
首先我们来研究startService启动服务。首先在MainActivity中定义四个按键,start_service和stop_service按键,以及bind_service和unbind_service,点击start_service就用startService开启服务,点击stop_service就用stopService关闭服务;点击bind_service就用bindService绑定服务,点击unbind_service就用unbindService解绑服务
然后还定义了一个Intent的全局变量service,代码如下:
public class MainActivity extends Activity implements OnClickListener{
private Intent servier = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startserver = (Button) findViewById(R.id.start_server);
startserver.setOnClickListener(this);
Button stopserver = (Button) findViewById(R.id.stop_server);
stopserver.setOnClickListener(this);
Button bindserver = (Button) findViewById(R.id.bind_server);
bindserver.setOnClickListener(this);
Button unbindserver = (Button) findViewById(R.id.unbind_server);
unbindserver.setOnClickListener(this);
servier = new Intent(this, TestService.class);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.start_server:
startService(servier);
System.out.println("MainActivity"+ "开启服务成功");
break;
case R.id.stop_server:
stopService(servier);
System.out.println("MainActivity"+ "取消服务成功");
break;
case R.id.bind_server:
break;
case R.id.unbind_server:
break;
}
}
}
现在我们点击start_server开启服务,我们看logCat,发现调用了TestService类中的onCreate和onStartCommand方法,打印了:
06-02 03:45:33.373: I/System.out(1250): 我在oncreate
06-02 03:45:33.383: I/System.out(1250): 我在onstartConnand
然后按返回键,退出TestService打开setting-apps,滑动到running中,看到TestService仍然后台运行,并且有一个进程和一个服务,如图:
start.pngstartService启动服务就是如此简单。
bindService启动服务
现在使用bindService启动服务,它需要传入三个参数,分别是Intent对象,ServiceConnection对象以及一个flags。
Intent我们使用之前的Intent对象。
ServiceConnection对象我们创建一个内部类继承ServiceConnection,重新继承的两个方法。
flags直接传入BIND_AUTO_CREATE,表示如果没有bindservice则自动创建bind。
最后MainAcitivty代码如下:
public class MainActivity extends Activity implements OnClickListener{
private Intent servier = null;
private connection conn = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startserver = (Button) findViewById(R.id.start_server);
startserver.setOnClickListener(this);
Button stopserver = (Button) findViewById(R.id.stop_server);
stopserver.setOnClickListener(this);
Button bindserver = (Button) findViewById(R.id.bind_server);
bindserver.setOnClickListener(this);
Button unbindserver = (Button) findViewById(R.id.unbind_server);
unbindserver.setOnClickListener(this);
servier = new Intent(this, TestService.class);
conn = new connection();
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.start_server:
startService(servier);
Log.i("MainActivity", "开启服务成功");
System.out.println("MainActivity"+ "开启服务成功");
Toast.makeText(getApplicationContext(), "开启服务了", Toast.LENGTH_SHORT).show();
break;
case R.id.stop_server:
stopService(servier);
Log.i("MainActivity", "取消服务成功");
System.out.println("MainActivity"+ "取消服务成功");
break;
case R.id.bind_server:
bindService(servier, conn, BIND_AUTO_CREATE);
break;
case R.id.unbind_server:
unbindService(conn);
System.out.println("MainActivity"+ "取消服务成功按钮");
break;
}
}
private class connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
当我们点击bind_server使用bindService启动服务的时候,我们在logcat发现它调用的是onCreate和onBind两种方法,
06-02 04:11:11.533: I/System.out(1356): 我在oncreate
06-02 04:11:11.533: I/System.out(1356): 我在onbind
区别于startService走的是onCreate和onStartCommand两种方法。
好了,现在我们知道两者启动服务出来传入的参数不同外,还有调用的方法不同。
但是等等,在使用bindService按返回键退出app时,我们发现logcat中报了一片红色的错误,经过仔细分析发现,原来使用bindService启动服务,退出时要用unbindService关闭服务。
** 由此可知第二个不同之处,就是bindService在Activity销毁的时候要使用unbindService,而是用startService则不用,startService会在Android内存存在压力的时候才调用stopService,又或者开发者主动调用stopService**
可是关闭服务那不是不能常驻后台吗?
带着这个疑问,我们再次启动TestService,使用bindService启动服务,然后点击home将app挂起,然后打开setting-apps,滑动到running中,发现居然找不到运行中的服务!!
所以第三个不同之处在于bindService是个隐藏的服务,无法在运行的进程中找到,而startService则可以在运行的进程与服务中找到
bindService的用处
说了这么多,大家或许还是云里雾里的,因为除了这些不同之处外,没看到两种启动服务的方式各自发挥什么作用呀!
事实上谷歌工程师增加bindService方法为的是使开发者能够调用继承Service的类中自己定义的方法,在本文指的就是TestService类。
在TestService添加一个方法,它仅仅只是用Toast弹出一个提示,代码如下:
public void eat() {
Toast.makeText(getApplicationContext(), "toast开始吃东西了", Toast.LENGTH_SHORT).show();
}
然后在MainActivity中创建一个TestService对象,调用eat(),但是我们发现logcat报了一个空指针。
这是因为如果自己创建一个TestService对象,那么这个对象就只是一个普通的class对象,不是一个Service,因此无法使用getApplicationContext(),必须自己传入一个Context对象。
那么为什么Service可以直接使用getApplicationContext()获得Context对象,如果自己从源码查看,可以得到Service直接继承自ContextWrapper,而Activity继承自ContextThemeWrapper,而ContextThemeWrapper又继承自ContextWrapper,所以Service和Activity都能自己使用Context对象。
言归正传,如果想要直接调用Service中的方法应该怎么办?
上面我们知道了bindService走的是onCreate和onBind方法,我们仔细瞧onBind方法,它是可以返回一个IBinder对象的,因此我们可以返回IBinder或者其子类的对象。
于是我们创建一个Mybind继承自IBinder的子类Binder,在Mybind中直接调用上面的eat()方法,代码如下:
public class TestService extends Service {
@Override
public IBinder onBind(Intent intent) {
System.out.println("我在onbind");
return new Mybind();
}
······
public void eat() {
Toast.makeText(getApplicationContext(), "toast开始吃东西了", Toast.LENGTH_SHORT).show();
}
public class Mybind extends Binder{
public void calleat() {
eat();
}
}
}
然后我们返回MainActivity,发现binderService需要传入的第二个参数,即继承自ServiceConnection的子类中重写的onServiceConnected方法,其中有传入一个IBinder对象,没错,这个就是TestService中onBind所返回的IBinder对象。于是我们将MainActivity代码修改为:
public class MainActivity extends Activity implements OnClickListener{
private Intent servier = null;
private connection conn = null;
private Mybind mybind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
······
······
servier = new Intent(this, TestService.class);
conn = new connection();
bindService(servier, conn, BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
······
case R.id.bind_server:
mybind.calleat();
break;
case R.id.unbind_server:
unbindService(conn);
System.out.println("MainActivity"+ "取消服务成功按钮");
break;
}
}
private class connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mybind = (Mybind) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
这时我们发现在点击bind_service按键时可以弹出Toast,调用TestService中的eat()方法了!
需要注意的是在这里将bindService方法反正onCreate方法中,在开启MainActivity时直接绑定服务。如果仍然把bindService方法在按下时bind_service调用,然后直接使用mybind.calleat(); 调用TestService中的方法,会出现空指针报错的现象,估计是因为使用bindService回调serviceConnected方法拿binder对象需要一定的时间,无法即时反应导致的空指针。
(拓展)通过接口调用服务中的方法
在上面使用bindService来调用服务中的方法确实是不错,现在我们在TestService中添加一个新的方法,如play()方法,代码如下:
public void play() {
Toast.makeText(getApplicationContext(), "我要出去玩", Toast.LENGTH_SHORT).show();
}
然后将调用这个方法的callplay()也放在Mybind类中去,这样也可以在MainActivity中调用了,代码如下:
public void callplay() {
play()
}
可是过两天我们发现这个代码不行,觉得把play()方法提供给外部调用不好,我们只想提供eat()方法给外部调用,这该如何处理呢?
这是我们可以使用接口,将想要提供给外部调用的方法暴露出来,接口类代码:
public interface Iservice {
//把想暴露的方法 都定义在接口里面
public void calleat();
}
然后将Mybind类的修饰符改为private,并且介入Iservice接口,Mybind代码如下:
private class MyBinder extends Binder implements Iservice{
// 重写Iservice中的calleat()方法
public void calleat(){
eat();
}
// 调用TestService中的play()方法
public void callPlay{
play();
}
}
现在问题来了,Mybind已经被限定为private了,也就是只能限定在TestService中使用,在MainActivity中已经无法创建出实例了,那么我们怎么使用calleat()方法呢?
这时便要使用到java中多态的知识了。由于我们Mybind接入了Iservice接口类,那么我们就可以直接使用父类引用子类对象,也就是使用Iservice引用Mybind对象。
当TestService的onBind方法中直接返回一个Mybind对象,然后我们可以在MainActivity中的内部类connection的onServiceConnected()方法中获得,详细代码如下:
public class MainActivity extends Activity implements OnClickListener{
private Intent servier = null;
private connection conn = null;
private Iservice mybind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
······
······
}
@Override
public void onClick(View v) {
switch(v.getId()) {
······
case R.id.bind_server:
mybind.calleat();
break;
······
}
}
private class connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mybind = (Iservice) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
改变之后我们同样可以调用calleat()方法,但是当我们直接调用callplay()方法却会出错。
我们只增加了一个接口类Iservice变实现了动态的决定是否暴露TestService中的方法,当我们需要将Mybind中的callpaly()方法暴露出去,只需在Iservice中添加callplay()方法便可以了!
总结
好了,startService和bindService两种启动服务的方法就说到这里了,相信大家应该都能明白两种方法的不同之处了。
最后要提示的是这两种方法是可以一起使用的,但是必须要先使用startService方法,然后在使用bindService方法,当然了,当app销毁的时候还是需要调用unbindService方法解绑的!
网友评论
bindService这个方法是android给应用开发者提供的一种匿名binder启动获取机制。这样客户端如果想用服务端提供的方法,服务端就不需要注册到ServiceManager,只需要提供正确的intent就能通过bindService机制获得binder服务的bp对象。
想想框架中客户进程是怎么用binder服务的,要么是获取实名注册到ServiceManager的binder服务,要么是直接由服务进程将IBinder回调给客户进程以获得binder的bp对象。
当然bindService的获取IBinder对象的过程本质上和框架里面的用法是一样的,但是前者显然方便多了。