Service是一个应用组件表现为应用期望执行长时间运行操作并且和用户没有交互或为其它应用提供功能。每个service类必须在它的包AndroidManifest.xml文件中有相应的
带着问题去学习
- 注册Service需要注意什么
- 什么是Service以及描述下它的生命周期。
- Service与Activity怎么实现通信
- Service有哪些启动方法,有什么区别,怎样停用Service?
- Service和Activity在同一个线程吗 main 线程 UI线程
- Service里面可以弹土司么
services需要注意的地方,就像其它应用对象,运行在主进程的主线程中。这意味着,如果你的service占用过多的CPU(例如播放MP3)或阻塞UI(例如访问网络)操作,它应该放到它自己的线程去做。更多信息可以在这Processes and Threads找到。IntentService类作为Service实现的标准,它有自己的线程去完成操作。
本文的主题:
- 什么是Service
- Service生命周期
- 权限
- 进程生命周期
- 本地Service示例
- 远程Messenger Service示例
开发指导
关于怎样创建Service更详细的讨论,阅读Services开发指导。
什么是Service?
对Service类最混乱实际上都是围绕着它不是什么:
- Service不是一个单独的进程。Service对象并不是运行在它自己的进程中;除非另有规定,它和应用程序运行在同一个进程。
- Service不是一个线程。它意味着它不可以做主线程之外的操作(为了避免应用程序无响应的错误)。
因此Service实际上非常简单,提供两个主要特点:
应用程序的一个设施告诉系统它想在后台做一些操作(甚至用户没有直接和应用交互)。对应的是调用Context.startService(),请求系统给Service计划工作,它会一直运行直到Service被明确地终止。
应用程序的一个设施给其它应用暴露它的一些功能。对应的是调用Context.bindService(),为了和它交互它允许和Service保持一个长连接。
当一个Service组件实际被创建后,对于下面的这些原因,系统实际是实例化组件然后调用onCreate() 和在主线程其它合适的回调。它是由Service用合适的行为实现了这些,例如创建第二个线程处理它的工作。
注意由于Service本身很简单,你可以让它和你的交互变得你想要的简单或复杂:把它变成一个本地Java对象你可以直接调用方法(本地Service示例),使用AIDL提供一个完全远程的接口。
Service生命周期
一个Service可以通过系统运行有两个原因。如果有人调用Context.startService()方法系统将会检索Service(如果需要的话创建它并调它的onCreate()方法)然后使用客户端提供的参数调用 onStartCommand(Intent, int, int)方法。Service将会继续运行直到Context.stopService() 或 stopSelf() 被调用。注意多次调用Context.startService() 不会启动多次(它会相应地多次调用onStartCommand()方法),因此无论一个service被开启了多少次,一旦Context.stopService() 或 stopSelf()被调用它就会停止运行;然而,services可以使用它们的 stopSelf(int)方法来确保它不会被停止直到开启的intents被处理完。
对于开启Service,这有两个额外的决定了运行的操作模式,依赖于onStartCommand()方法返回的值:START_STICKY被用于services根据需要显式的开启和停止,START_NOT_STICKY 或 START_REDELIVER_INTENT 被用于services处理完发送给它们的命令应该保持运行。更多信息请查看语义上的链接文档。
客户端也可以使用Context.bindService()获取一个保持长连接的service。这个就像创建service如果它还没有运行(也会调用 onCreate()),但不会调用onStartCommand()。客户端会接收到service从onBind(Intent)方法返回的一个IBinder对象,允许客户端回调service。service会保持运行只要建立了连接(客户端是否还持有service的IBinder的引用)。通常返回的IBinder为用aidl写的复杂的接口。
一个service可以同时被开启并有一个绑定到它的连接。在这种情况下,系统会保持service的运行只要它被开启或有一个或多个使用 Context.BIND_AUTO_CREATE标志连接到它。当没有上述情况时,service的onDestroy()方法会被调用并且service是有效终止。所有的清理(停止线程,注销receivers)应该在onDestroy()方法完成。
权限
当它被声明在清单文件的
Android2.3,当使用Context.startService(Intent),你也可以在Intent上设置Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这会授权Service临时访问权限对于Intent具体的URIs。访问会持续有效直到Service的stopSelf(int)被调用为开始命令或更高的命令,或直到服务被完全停止。这个用于给没有请求保护Service权限的其它应用授权访问,或甚至当Service完全没有被exported。
除此之外,一个service还可以使用权限保护个人的IPC调用,通过在执行实现之前调用 checkCallingPermission(String)方法。
对于更多信息也可以查看安全和权限文档关于权限和安全的说明。
进程生命周期
只要service被启动或客户端已绑定service,Android系统就会尝试维护托管服务进程。当运行在低内存并且需要杀死已存在的进程,如果是下面的情况托管服务进程的优先级就越高:
- 如果service执行的代码在它的 onCreate(), onStartCommand(), 或 onDestroy()方法中,那么托管进程将是一个前台进程确保这些代码可以执行不会被杀死。
如果服务已经被启动,然后托管进程被认为比用户当前可见的任何进程的重要性要低,但要比其它不可见的进程重要性要高。 因此通常只有少数进程是用户可见的,这意味着service不应该被杀死除了低内存的情况下。然而,由于用户不能直接意识到后台service,在这种状态下它就被认为是一个有效的被杀死的候选,并且你应该为这做好准备。特别地,长时间运行的service被杀死的可能性越大并且如果它们保留的时间足够长可以放心地被杀死(并且如果合适的话重启)。
如果是客户端绑定到service,然后service托管进程没有客户端更重要。那是如果它的客户端中的一个对用户是可见的,那么service本身被认为是可见的。客户端重要性影响service的重要性的方式可以通过BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, BIND_WAIVE_PRIORITY, BIND_IMPORTANT, 和 BIND_ADJUST_WITH_ACTIVITY被调节。
- 一个开启的service可以使用startForeground(int, Notification)把service置为前台的状态,系统认为它对用户是积极的并且当低内存时不会是被杀死的候选。(从理论上来讲,在内存极端压力下service还是有可能会被杀死从前台应用程序,但在实践中这不应该值得关注。)
注意这意味着大多数时间你的service在运行,如果它在严重内存压力下有可能被系统杀死。如果发生这种情况,系统将在稍后尝试重启service。这是一个重要的结果,如果你实现了onStartCommand()去计划异步完成任务或在其它线程,然后你可能想要使用START_FLAG_REDELIVERY去接收系统重新分发的Intent,因此如果你的service在处理过程中被杀死Intent不会被丢失。
其它应用组件(例如Activity)和service运行在同一个进程,当然了,除了service本身的重要性还要增加整体进程的重要性。
本地service示例
一个service最常见的用途是作为一个辅助组件,运行在一个应用程序的其他部分,在同一进程中作为其余组件。一个.apk的所有组件运行在同一进程除非另有明确说明,所以这是一个典型的情况。
当使用这种方式时,假设组件运行在同一进程,你可以大大简化它们之间的交互:service的客户端可以简单地转化它们接收到的IBinder到service发布的混合类。
这有一个Service使用示例。首先是service本身,当绑定时返回一个自定义的类: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
75public class LocalService extends Service {
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_service_started;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
// Tell the user we stopped.
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, LocalServiceActivities.Controller.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
写完上面的代码,现在可以写一个客户端代码直接访问正在运行的service,例如: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
50private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(Binding.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
远程Messenger Service示例
如果你需要写一个在远程进程用客户端可以执行复杂的通信的Service(除了简单地使用context.startservice发送命令给它),你可以使用Messenger类而不是写一个完整的AIDL文件。
一个使用Messenger作为它的客户端接口的Service示例。首先是Service本身,当绑定时返回一个Messenger到内部Handler: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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119public class MessengerService extends Service {
/** For showing and hiding our notification. */
NotificationManager mNM;
/** Keeps track of all current registered clients. */
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** Holds last value set by a client. */
int mValue = 0;
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* Command to service to set a new value. This can be sent to the
* service to supply a new value, and will be sent by the service to
* any registered clients with the new value.
*/
static final int MSG_SET_VALUE = 3;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i=mClients.size()-1; i>=0; i--) {
try {
mClients.get(i).send(Message.obtain(null,
MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting.
showNotification();
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.remote_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Controller.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
// We use a string id because it is a unique number. We use it later to cancel.
mNM.notify(R.string.remote_service_started, notification);
}
}
如果我们想让这个service运行在远程进程(而不是.apk中标准的一个),在清单文件中我们可以使用android:process标签指定一个:1
2<service android:name=".app.MessengerService"
android:process=":remote" />
注意 “remote” 名称是任意取的,如果你想要额外的进程你可以使用其它名称。’:’前缀表示是追加到包名后的名称(注:如果以’:’开头,则进程是应用程序私有的,若以小写字母开头则为系统全局共享的进程)。
完成了上面的代码,客户端可以绑定到service并且发消息给它。注意这允许客户端进行注册接收和返回消息:
1 | /** Messenger for communicating with service. */ |
完整代码:https://github.com/yuweiguocn/ServiceDemo