简介
Service 是Android的四大组件之一,当应用程序希望在应用程序空闲的时候去运行一个耗时较长的操作,或者为其他应用提供功能实现的时候可以使用Service来完成。每个service都必须在AndroidManifest.xml中有一个相应的声明,Services 可以通过Context.bindService() 或者Context.startService()来启动Service。
需要注意的是,Service与其他应用程序的对象(UI Activity...)一样是运行在主进程的main Thread中,这就意味着,如果你的service打算做密集的CPU运算(比如播放媒体文件)或者做阻塞的操作(如联网),都应该在Service中单独开启一个子线程来处理。而IntentService 继承了Service,当你希望Service在一个单独的线程中运行时,IntentService是一个标准的实现。
这篇文章涉及的主题:
- 什么是Service?
- Service的生命周期
- Service 权限的定义与声明
- 进程的声明周期
- Local Service 范例
- Remote Messenger Service 范例
1.什么是Service?
实际上,大多数的困惑都是在说Service 不是什么:
1)Service不是一个单独的进程,Service的对象实体并不一定运行在自己特有的进程中,它作为应用程序的一部分与应用程序运行在同一个进程空间。
如果希望Service运行在自己独立的进程:
a.可以在AndroidManifest.xml中通过android:process="" 为Service 指定一个进程名称,则Service会重新开启一个进程。
b.即便是不在AndroidManifest.xml指定android:process="",如果某个应用程序A期望使用其他应用程序B中的Service,那么B的中Service与A 肯定不在同一个进程中。
2)Service 不是一个线程,这就意味着它并不能脱离主线程(主要是为了避免应用程序没有反应的错误发生)
所以Service实际上非常简单,提供了两个主要的特性:
1)某个应用程序的功能告诉系统需要将一些事物放在后台运行,只要调用了 Context.startService(),系统就会安排时间让Service在后台处理这些请求,除非你明确的去关闭Service,否则Service不会停止。
2)如果说应用程序A希望使用应用程序B中的Service完成某件事,需要调用Context.bindService()来建立连接,bindService()允许一种长期的连接来保证B中的Service能正常与它交互。
当Service这个组件已经创建,之后何时调用onCreate()和onBind()完全取决于Service自己,系统会让Service在合适的时间点去调用这些方法
需要注意的是,因为Service本身的设计非常简单,你可以按照具体的需求来完善Service:让它成为一个本地的Java对象然后直接调用它的方法(可以参照下文中的 Local Service 范例),也可以通过AIDL提供一个完全远程的接口。
2.Service 的生命周期
启动Service的方式有两种:
1)Context.startService()
context.startService() -> onCreate() -> onStartCommand(可能多次调用) -> Service running -> context.stopService() -> onDestroy() -> Service stop
如果Service尚未运行,Client执行context.startService()时,则调用吮吸onCreate() -> onStartCommand()。
如果Service已经运行,当Client多次执行context.startService(Intent service)时则只执行onStartCommand(),Client的数据可放在intent中传递到Service。
无论Client执行多少次context.startService(),Client只需要执行一次context.stopService()就能关闭Service(调用onDestroy())。
即使多次调用startService(Intent service),但是只要你Intent中启动的是同一个Service,则不会重新建立一个Service。
- onStartCommand() 方法是在2.0 以后才引入的,代替了已经不建议使用的onStart()。它提供了和onStart()一样的功能,但是还允许你告诉系统,如果系统在显示调用stopService或stopSelf之前终止了Service,该如何重启Service。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startBackgroundTask(intent,startId);
// return Service.START_STICKY;
// or
// return Service.START_NOT_STICKY;
// or
return Service.START_REDELIVER_INTENT;
}
2)Context.bindService()
context.bindService() -> onCreate() -> onBind()(只执行一次) -> Service running -> onUnbind() -> onDestroy() -> Service stop
onBind()将返回给客户端一个IBind接口实例,IBind 实际上可以简单理解成某个定义在Service中的类A(这个接口至少需要extend IBind),它是通向Service的入口。当Client 拿到IBind之后可以将其向下转型成类A,然后回调类A中的方法,类A中的方法就可以任意操作Service了。
bindService()会让Client与Service绑定在一起,当所有连接到Service的Client退出时,Service也会退出。
Client接口IBind允许客户端回调服务的方法,比如得到Service的实例、运行状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。
3.Service 权限的定义与声明
当Service 在它的AndroidManifest.xml <service> 标签下声明了android:permission="",则其他app在访问该Service的时候必须在各自的AndroidManifest.xml <uses-permission>中声明访问权限,才能去start, stop, or bind service。
1)权限的定义
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
<permission
android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
android:description="@string/permdesc_deadlyActivity"
android:label="@string/permlab_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>
2)权限的声明
<application>
...
<service
android:name="PrivService"
android:permission="com.me.app.myapp.permission.DEADLY_SERVICE" />
...
</application>
另外,Service也可以通过上述方式来保护它的IPC方法调用,在调用service 的某个方法前可以调用contex.checkCallingPermission()来检查是否拥有执行权限。
4.进程的生命周期
只要一个进程的Service已经启动或者有客户端绑定到该Service,那么Android 系统会尽可能保证该进程能正常运行。当系统的内存不足需要杀死一些现有的进程来释放资源的时候,Service所在进程的优先级只有在下面几种情况下才会更高一些:
- 如果Service正在执行onCreate(), onStartCommand(), or onDestroy()这些方法,Service所在的进程的优先级会提高成前台进程,以保证这些代码能正常执行。
- 如果Service 已经启动,Service所在的进程的重要性只比用户可见的进程低,但是比其他任何不可见的进程的优先级要高。因为很少有进程对用户来说是可见的(比如UI进程是可见的),所以只有内存极端缺乏的时候才会终止Service所在的进程。
- 一旦有客户端绑定到Service,只要绑定到service中的其中一个客户端变成对用户可见的进程,那么也可以认为service也是可见的。
一个已经启动的service可以使用startForeground(int, Notification)方法将service切换到前台线程状态,这样当系统内存不足时不会不会终止此service(但是理论上如果在内存极端缺乏的情况下还是会终止service进程)。 - 需要注意的是,如果你的service长时间运行,当系统出现内存紧张的情况下系统可能终止service的运行。如果发生这样的情况,系统之后会重新启动service。如果你重写了onStartCommand()方法,并在此方法中去新开线程或者异步的执行一些操作,为了避免service在运行的过程中被终止后重启丢失intent数据,你可以在onStartCommand()中return Service.START_FLAG_REDELIVERY,这样在系统重启Service的时候会重新传递intent数据(否则重新启动时inten数据的值为null)。
5.Local Service 范例:
1)界面
package sunmedia.chenjian.localservice;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBindService();// 与service建立连接
}
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((LocalService.LocalBinder) service).getService();
Toast.makeText(MainActivity.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
Toast.makeText(MainActivity.this,
R.string.local_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
};
private boolean mIsBound;
void doBindService() {
// 由于事先知道service与client运行在同一进程,所以可以直接指定类名来启动service
bindService(new Intent(MainActivity.this, LocalService.class),
mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// 断开连接
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
2)Service
package sunmedia.chenjian.localservice;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class LocalService extends Service {
public static final String NAME = "sunmedia.chen.jian.localservice";
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;
/**
* 为client提供访问,因为事先知道service与client运行在同一进程,所以不需要 考虑IPC的影响
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 显示service的启动状态,启动时会在状态现显示一个图标
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
// 由于我们希望service一直在运行,除非调用stop终止进程,所以返回值可以设置成
// START_STICKY
return START_STICKY;
}
@Override
public void onDestroy() {
// 取消在状态栏的提示
mNM.cancel(NOTIFICATION);
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT)
.show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// 用于与client交互,可以理解为service的入口
private final IBinder mBinder = new LocalBinder();
/** * Show a notification while this service is running. */
private void showNotification() {
// 获取用于状态栏提示的字串
CharSequence text = getText(R.string.local_service_started);
// 设置状态栏提示用的参数资源(图片/文字/时间戳)
Notification notification = new Notification(R.drawable.stat_sample,
text, System.currentTimeMillis());
// PendingIntent 预先设置状态被点击时的动作
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Operation.class), 0);
// 设置状态栏中持续显示时的参数
notification.setLatestEventInfo(this,
getText(R.string.local_service_label), text, contentIntent);
// 发送Notification
mNM.notify(NOTIFICATION, notification);
}
}
3)operation
package sunmedia.chenjian.localservice;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
//用于测试状态栏被点击时的动作
public class Operation extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.operation);
Toast.makeText(this, "Operation Create", Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
}
4)AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sunmedia.chenjian.localservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="sunmedia.chenjian.localservice.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="sunmedia.chenjian.localservice.Operation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="sunmedia.chenjian.localservice.LocalService"
android:enabled="true"
android:exported="false" >
<intent-filter android:name="LocalService" >
</intent-filter>
</service>
</application>
</manifest>
6.Remote Mesager Service范例
1)界面
package sunmedia.chenjian.remoteservice;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCallbackText = (TextView)findViewById(R.id.TextView);
doBindService();
}
/** 用于与Service交互*/
Messenger mService = null;
/** 用于表示是否执行过bindService() */
boolean mIsBound;
/** 用于显示当前连接的状态 */
TextView mCallbackText;
/** 用于处理从Service 接收到的消息*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/** 用于发布消息到客户端. */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/** 用于指示 Service 连接状态 */
private ServiceConnection mConnection = new ServiceConnection() {
/** 当Service建立完后后会调用此方法,并将Service作为参数传递进来,需要注意的是
* ServiceConnection中的方法都是在Activity所在的主线程调用的,也就是说其实是客
* 户端的主线程不断的轮询Service的状态再调用ServiceConnection中的方法。
* 导致不能连接的情况有几种:
* 1)Service 没有在AndroidManifest.xml中声明或者启动Service时名称不正确
* 2)如果当前Activity所在的主线程被卡主,则无法正常调用此方法
* */
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
mCallbackText.setText("Attached.");
//将mMessenger传递到Service方便Service发消息到客户端
try {
Message msg = Message.obtain(null,
MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
//传递一个标识到Service,方便其测试连接
msg = Message.obtain(null, MessengerService.MSG_SET_VALUE,
this.hashCode(), 0);
mService.send(msg);
} catch (RemoteException e) {
//Service异常终止,可以选择重新连接策略
}
Toast.makeText(MainActivity.this,
R.string.remote_service_connected, Toast.LENGTH_SHORT)
.show();
}
/**
* 当Service被异常终止的时,客户端所在的主线程会调用此方法,之后系统会重新启动
* Service并再次调用onServiceConnected()。
*/
public void onServiceDisconnected(ComponentName className) {
mService = null;
mCallbackText.setText("Disconnected.");
Toast.makeText(MainActivity.this,
R.string.remote_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
};
void doBindService() {
Intent intent = new Intent(MessengerService.NAME);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
//如果客户端正常退出,则需要注销与Service的连接
if (mService != null) {
try {
Message msg = Message.obtain(null,
MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
//Service已经异常终止
}
}
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
}
2)Service
package sunmedia.chenjian.remoteservice;
import java.util.ArrayList;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;
public class MessengerService extends Service {
static final String NAME = "MyMessengerService";
/**用于在状态栏显示通知. */
NotificationManager mNM;
/** 用于记录客户端的连接情况*/
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** 用于存储最后一次连接的客户端标识符 */
int mValue = 0;
/**
* 此命令用于注册客户端,Message的 replyTo栏位需要放置客户端的Messenger,用于
* Service发消息到客户端
* */
static final int MSG_REGISTER_CLIENT = 1;
/**
* 此命令用于注销客户端,Message的 replyTo栏位需要放置客户端的Messenger,用于
* 将Messenger从记录中移除。
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* 此命令主要用于测试Service与客户端的连接
*/
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) {
//如果发送消息给客户端失败,则认为客户端异常终止,将客户端从
//记录中移除
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/** 用于与客户端之间的信息交互 */
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();
}
/**
* 将服务端的Messenger发送给客户端,方便客户端传递消息给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);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.stat_sample,
text, System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this
// notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Operation.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this,
getText(R.string.remote_service_label), text, contentIntent);
// 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);
}
}
3)AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sunmedia.chenjian.remoteservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="sunmedia.chenjian.remoteservice.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="sunmedia.chenjian.remoteservice.Operation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="sunmedia.chenjian.remoteservice.MessengerService"
android:enabled="true"
android:exported="true"
android:process=":remote_service" >
<intent-filter>
<action android:name="MyMessengerService" />
</intent-filter>
</service>
</application>
</manifest>
个人原创:转载请注明出处:http://blog.csdn.net/chenjianjk/article/details/9499271
Local Service、Remote Service代码下载地址(中文的编码格式为UTF-8):
http://download.csdn.net/detail/chenjianjk/5826495