Android开发日记(三)——Android四大组件之Service

本系列原本写于博客园,现移植到自己的博客上并重新编辑。

Android四大组件之Service

Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通。它跟Activity的级别差不多,但不能自己运行只能后台运行。service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等, 总之服务总是藏在后台的。

一、注册Service

service的注册跟activity的注册类似,同样是要在AndroidManifest.xml的文件里注册。

1
2
3
4
5
6
<service android:name=".MinaService"><!-- 你自定义的service文件   (在<application></application>里面加)-->
<intent-filter>
<action android:name="com.MinaService" /><!-- 用intent启动时的快捷名(也可以用常规的方式启动) -->
<category android:name="android.intent.category.default" />
</intent-filter>
</service>

二、Service的两种模式

service有两种模式,本地服务和远程服务。我们一般开发应用都是用的本地服务,而远程服务经常在做系统开发时被用到。所以今天我会主要讲本地的服务,远程服务放着以后再讲吧
本地服务依附在主进程上,当主进程被kill后,服务便会终止,常见的应用如:HTC的音乐播放服务,天天动听音乐播放服务。
远程服务是独立的进程,对应进程名为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。常见的应用为一些提供系统服务的Service,这种Service就是常驻的。
service周期表

三、Service的生命周期及两种启动方式

service的生命周期比activity简单多了,原因是service是在后台运行的,它是一直运行的,所以不需要那么多的状态判断。它只继承了onCreate()、onStart()或者说是onStartCommand()、onDestroy()三个方法。
// 2.0 API level之后,实现onStart等同于重写onStartCommand并返回。–>关于onStartCommon()的详解

服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。

使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。 如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。 采用startService()方法启动的服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。
使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。采用Context.bindService()方法启动服务时只能调用onUnbind()方法解除调用者与服务解除,服务结束时会调用onDestroy()方法。

四、Service实例

接下来我会写一个关于service的实例demo,希望看了之后会对大家有所帮助。

建立Service

先新建一个项目,并新建一个class文件,命名为MinaService,继承于service(不要忘记在AndroidManifest.xml文件里注册),其中代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class MinaService extends Service{
private String msg = "service bind success";

private final IBinder mBinder = new LocalBinder();

@Override
public IBinder onBind(Intent intent) {
Log.d("TEST", "onbind");
//onBind service时会调用此方法,用于绑定Activity
return mBinder;
}

@Override
public void onCreate() {
//开启服务时会首先调用该方法
Log.d("TEST", "onCreate");
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//start service时会在onCreate后调用该方法
Log.d("TEST", "start");
return START_STICKY;
}

@Override
public void onDestroy() {
// 停止service后会调用此方法
Log.d("TEST", "destroy");
super.onDestroy();
}

@Override
public boolean onUnbind(Intent intent) {
//取消Activity与service绑定时要调用此方法
Log.d("TEST", "onunbind");
return super.onUnbind(intent);
}

@Override
public void onRebind(Intent intent) {
// 重新绑定时调用此方法
Log.d("TEST", "onrebind");
super.onRebind(intent);
}


/**
* @author cpacm
* 通过Binder返回service的引用到Activity中
*/

public class LocalBinder extends Binder {
MinaService getService() {
return MinaService.this;
}
}

//普通方法,证明service在后台运行
public String getMsg(){
return msg;
}
public void setMsg(String m){
this.msg = m;
}

}

对Activity进行处理

回到MainActivity中,来看一下我们需要做的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public class MainActivity extends Activity implements OnClickListener{

private MinaService ms = null;
private Button b1,b2,b3,b4,b5,b6;
private TextView textView;
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获得界面的控件
textView = (TextView) findViewById(R.id.textView1);
editText = (EditText) findViewById(R.id.editText1);
b1 = (Button) findViewById(R.id.button1);
b1.setOnClickListener(this);
b2 = (Button) findViewById(R.id.button2);
b2.setOnClickListener(this);
b3 = (Button) findViewById(R.id.button3);
b3.setOnClickListener(this);
b4 = (Button) findViewById(R.id.button4);
b4.setOnClickListener(this);
b5 = (Button) findViewById(R.id.button5);
b5.setOnClickListener(this);
b6 = (Button) findViewById(R.id.button6);
b6.setOnClickListener(this);
Log.d("TEST","===初始化完成===");
}

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.button1:{//按钮一:用startService开启服务
Intent i = new Intent();
i.setClass(MainActivity.this, MinaService.class);
startService(i);
break;
}
case R.id.button2:{//按钮二:停止服务
Intent i = new Intent();
i.setClass(MainActivity.this, MinaService.class);
stopService(i);
break;
}
case R.id.button3:{//按钮三:用bindService来绑定Service和Activity
bindService(new Intent(MainActivity.this,
MinaService.class), mConnection, Context.BIND_AUTO_CREATE);
break;
}
case R.id.button4:{//取消绑定
unbindService(mConnection);
break;
}
case R.id.button5:{//跳转到下一个Activity
Intent i = new Intent();
i.setClass(MainActivity.this, SecondActivity.class);
startActivity(i);

break;
}
case R.id.button6:{//显示Service里面的信息
textView.setText(ms.getMsg());
ms.setMsg(editText.getText().toString());
break;
}
}
}


public void show(String str){
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
}

private ServiceConnection mConnection = new ServiceConnection(){


/**
* 绑定Service和Activity时会用到这个函数,所以可以在这里获取到Service的引用对象
* @see android.content.ServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder)
**/

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取Service的引用对象
ms = ((MinaService.LocalBinder)service).getService();
Toast.makeText(MainActivity.this, "connect",
Toast.LENGTH_SHORT).show();
}

/**
* 这个函数是在绑定异常时调用,平时不会使用到这个函数
* @see android.content.ServiceConnection#onServiceDisconnected(android.content.ComponentName)
**/

@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
ms = null;
Toast.makeText(MainActivity.this, "cutdown",
Toast.LENGTH_SHORT).show();
}

};

}

SecondActivity

再新建一个SecondActivity(不要忘记在AndroidManifest.xml里注册Activity),里面代码基本和MainActivity里一样,改一下这里

1
2
3
4
5
6
case R.id.button5:{
Intent i = new Intent();
i.setClass(SecondActivity.this, MainActivity.class);
startActivity(i);
break;
}

以上就是Demo里面的组成部分了。接下来我们来看一下运行结果。首先我们先启动项目,按下第一个按钮(start service),我们可以看到在LogCat里打印出
Log输出
可以看出项目先后调用了onCreate()和onStartCommand()。接下来按下第二个按钮(stop service)
Log输出
发现直接将service给destroy掉了。
接下来我们用BindService来启动Service依次按下第三个和第四个按钮,结果如图
Log输出
可以发现BindService不会调用onStartCommand()方法,它会使用onbind来代替。unbind后,Service也是会摧毁。

1
2
//该方法的第1个参数表示与服务类相关联的Intent对象,第2个参数是一个ServiceConnection类型的变量,负责连接Intent对象指定的服务。通过ServiceConnection对象可以获得连接成功或失败的状态,并可以获得连接后的服务对象。第3个参数是一个标志位,一般设为 Context.BIND_AUTO_CREATE。
public boolean bindService(Intent service, ServiceConnection conn, int flags)

如果我们想保持和 Service 的通信,又不想让 Service 随着 Activity 退出而退出呢?

你可以先 startService() 然后再 bindService() 。当你不需要绑定的时候就执行 unbindService() 方法,执行这个方法只会触发 Service 的 onUnbind() 而不会把这个 Service 销毁。这样就可以既保持和 Service 的通信,也不会随着 Activity 销毁而销毁了。(销毁Service还是要调用StopService)

在应用中,我们先按下第一个按钮,再按下第三个按钮,这样activity就获得了service的对象。现在我们改变service里的值,在输入框输入“Activity与Service通信”,按下第六个按钮。然后按下第五个按钮跳转到下一个Activity。在跳转到的Activity中,我们按下第三个按钮(onBind),在按下第六个按钮,你会发现文本框会出现“Activity与Service通信”的字符串。
First Activity SecondActivity
从截图上看,第二个activity成功获得了service里面的值。(注意,跳转到下一个Activity前一定要unbind,不然会报错)
关于service的一些技巧:
(1)可以在一个Activity启动Service,再在另一个Activity绑定。
(2)可以多次start同一个Service(通过该方法,可以经由intent传递信息到Service中)
(3)start->bind->stop,此时service还是存在的不会被销毁,但在调用unbind后,Service会被销毁。
可能还会有更多的可能性组合,大家可以自己写个demo来好好研究。

五、结语


Service不仅可以与前端界面组件建立双向连接、提供数据和功能支持,也可以单向接受Intent对象的请求,进行数据的分析处理和功能调度。在不同的使用方式下,Service服务组件扮演的角色和开发模式完全不同。这种设计,也为理解Service带来了一定的难度。所以理解和习惯Service的使用显得非常重要。
参考文章:Service学习之本地服务
源码:Service DEMO(Github)