浅析Android服务中的startService和bindSe

作者: 宝塔山上的猫 | 来源:发表于2016-06-02 17:06 被阅读2201次

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

    相关文章

      网友评论

      • 797d3c86b9d7:如果了解binder的话,应该更能吃透bindService的本质吧。
        bindService这个方法是android给应用开发者提供的一种匿名binder启动获取机制。这样客户端如果想用服务端提供的方法,服务端就不需要注册到ServiceManager,只需要提供正确的intent就能通过bindService机制获得binder服务的bp对象。
        想想框架中客户进程是怎么用binder服务的,要么是获取实名注册到ServiceManager的binder服务,要么是直接由服务进程将IBinder回调给客户进程以获得binder的bp对象。
        当然bindService的获取IBinder对象的过程本质上和框架里面的用法是一样的,但是前者显然方便多了。
      • Nofearinmyheart:如何在Application中绑定与解绑服务呢?
        宝塔山上的猫:@4e70eccc14c8 正常在Activity中怎么调用,在Applicaiton中就怎么调用

      本文标题:浅析Android服务中的startService和bindSe

      本文链接:https://www.haomeiwen.com/subject/fmqkdttx.html