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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容