浅析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仍然后台运行,并且有一个进程和一个服务,如图:

浅析Android服务中的startService和bindService_第1张图片
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方法解绑的!

你可能感兴趣的:(浅析Android服务中的startService和bindService)