什么是Android中的服务,浅析Android服务中的startService和bindService

作为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中的节点中注册Service,需要注意的是Android四大组件都是需要在AndroidManifest中注册的。

好了,走这两步,一个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仍然后台运行,并且有一个进程和一个服务,如图:

0dfc6e9d82b3

start.png

startService启动服务就是如此简单。

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方法解绑的!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值