标签:
它是android App的四大组件之一,在开发中我们有时需要做一些耗时的但不需要与用户建立界面交互的操作可以考虑使用service
。比如:比如我们需要在后台进行上传或下载的操作;杀毒或是监控软件可能希望service
常驻后台,并可被Intent
来驱动开始进行杀毒;聊天或是社交等即时通讯类应用,需要在后台定时地与服务发送“心跳”,来标识自己的在线状态等。
需要注意的是:它不是一个单独的进程,默认不会运行在一个单独的进程,除非做出明确的定义来实现远程Service
,它作为App的一部分与App运行在相同的进程里。它也不是一个子线程,它是在主线程里执行的,这就意味这它不能执行耗时操作。
它有两个主要特点,也就是它的两种启动方式:1. 一个是使用Context.startService()
,告诉系统有需要在后台执行的任务,请求系统给service
安排这些任务。2. 另一个是使用Context.bindService()
,这种方式可以通过暴露一些功能给其他client
,通过建立一个较长的连接,使service可以和其建立一些通信交互。
下面是两种启动方式下,Service的生命周期对比图:
采用Context.startService()
方法启动服务,访问者与服务之间没有产生关连,即使访问者退出了,服务仍然运行。只能调用Context.stopService()
方法结束服务,服务结束时会调用onDestroy()
方法,适用于服务和访问者之间没有交互的情况。
在服务被创建时调用
onCreate()
方法,创建之后重复调用Context.startService()
,不会再触发onCreate()
方法,不会多次创建服务,但每次会触发onStartCommand()
方法之后会执行onStart()
方法。
采用Context.bindService()
方法启动服务,访问者与服务绑定在了一起,访问者一旦退出,服务也就终止,适用于服务和访问者之间需要方法调用或进行参数传递。
在服务未被创建时,系统会先调用服务的
onCreate()
方法,接着调用onBind()
方法,这个时候访问者和服务绑定在一起。如果访问者要与服务进行通信,那么onBind()
方法必须返回Ibinder
对象。如果访问者退出了,系统就会先调用服务onUnbind()
方法,接着调用onDestroy()
方法。如果在调用bindService()
方法前服务已经被绑定,多次调用bindService()
方法并不会导致多次创建服务及绑定(也就是说onCreate()
和onBind()
方法并不会被多次调用)。如果访问者希望与正在绑定的服务解除绑定,可以调用unbindService()
方法,调用该方法也会导致系统调用服务的onUnbind()
->onDestroy()
方法,如果再次调用unbindService()
会发生异常java.lang.IllegalArgumentException: Service not registered
,所以如果需要在activity可见时与service交互,应该在activity的onStart()中绑定并在onStop()中解除绑定。如果想让activity即使在它停止时也能接收回应,那么可以在onCreate()中绑定并在onDestroy()中解除绑定.注意这意味着activity需要使用在自己整个运行期间使用service(即使位于后台),所以如果service在另一个进程中,那么你增加了这个进程的负担而使它变得更容易被系统杀掉。
如果对同一个service
分别使用context.startSerivce()
和context.bindSerivce()
,也只会执行一次onCreate()
方法,该service
只会被创建一次,而这种情况下,只有执行了unBindService()
之后该service
才可以被销毁(调用onDestroy()
)方法.
client:startSerivce()->bindService()->stopService()/unBindService->unBindService()/stopService()
service:onCreate()->onStartCommand()->onStart()->onBind()->onServiceConnected()->onUnBind()->onDestory()
client:bindService()->startSerivce()->stopService()/unBindService->unBindService()/stopService():
service:onCreate()->onBind()->onServiceConnected()->onStartCommand()->onUnBind()->onDestory()
在上面的调用过程和对应的service生命周期过程中需要注意的:
1.如果先bindService,再startService:
在bind的Activity退出的时候,Service会执行unBind方法而不执行onDestory方法,因为有startService方法调用过,所以Activity与Service解除绑定后会有一个与调用者没有关连的Service存在
2.如果先bindService,再startService,再调用Context.stopService
Service的onDestory方法不会立刻执行,因为有一个与Service绑定的Activity,但是在Activity退出的时候,会执行onDestory,如果要立刻执行stopService,就得先解除绑定
在
public int onStartCommand (Intent intent, int flags, int startId)
方法中:参数flags默认情况下是0,对应的常量名为START_STICKY_COMPATIBILITY。startId是一个唯一的整型,用于表示此次Client执行startService()
的请求请求标识,在多次startService()
的情况下,呈现0,1,2….递增。另外,此函数具有一个int型的返回值,具体的可选值及含义如下:
START_NOT_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService()
启动此Service。
START_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand()
方法,但其中的Intent将是null,pendingintent除外。
START_REDELIVER_INTENT:与START_STICKY唯一不同的是,回调onStartCommand()
方法时,其中的Intent将是非空,将是最后一次调用startService()
中的intent。
import android.app.Service;
import android.content.Intent;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
public class DemoService extends Service {
private static final String TAG = "DemoService";
private static final int NOTIFICATION = 101;
private NotificationManager mNM;
private DemoBinder demoBinder = new DemoBinder();
public class DemoBinder extends Binder {
public DemoService getService() {
return DemoService.this;
}
public void upload() {
Log.d(TAG, "upload: ");
}
}
//如果允许绑定,需要返回一个IBinder对象供client能够使用它与service通讯。如果不允许绑定,那么应返回null。
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: ");
return demoBinder;
}
//在service第一次创建时执行此方法,可以在这里进行只运行一次的初始化工作。如果service已经运行,这个方法不会被调用.
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: ");
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
//应该告诉用户你开启了一个后台服务,这个服务是用来干什么的提示通知。
showNotification();
}
//通过调用startService()请求service启动时调用这个方法.
//一旦这个方法执行,service就启动并且在后台长期运行,需要在service完成任务时通过调用stopSelf()或stopService()停止它。(如果只想提供绑定,则不需实现此方法).
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.d(TAG, "onStart: ");
}
//当所有client均从service发布的接口断开的时候被调用。默认实现不执行任何操作,并返回false。
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
//在service不再被使用并要销毁时调用此方法。应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等。这是service收到的最后一个调用
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
mNM.cancel(NOTIFICATION);
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.d(TAG, "onRebind: ");
}
/** 在Service开始运行时 显示个通知告诉用户*/
private void showNotification() {
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher) // the status icon
.setTicker("Service开启了") // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle("DemoService正在运行") // the label of the entry
.setContentText("测试Service") // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.getNotification(); // Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
还要在清单文件AndroidManifest.xml中进行注册:
<service android:name=".DemoService"
android:enabled="true"
android:exported="true" />
在 AndroidManifest.xml 里 Service 元素的常见选项:
选项名 | 作用 |
---|---|
android:name | 服务类名 |
android:label | 服务的名字,如果此项不设置,那么默认显示的服务名则为类名 |
android:icon | 服务的图标 |
android:permission | 申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务 |
android:process | 表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字 |
android:enabled | 如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false |
android:exported | 表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false |
访问者client可以通过context.bindService()
方法public boolean bindService (Intent service, ServiceConnection conn, int flags)
中的conn来建立与Service的联系。
public class MainActivity extends AppCompatActivity {
... 省略...
public void clickService(View view) {
switch (view.getId()) {
case R.id.btn_bind:
bindService(new Intent(this, DemoService.class), conn, BIND_AUTO_CREATE);
break;
case R.id.btn_unbind:
unbindService(conn);
break;
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: ");
//这个service就是Service类中onBind() 方法中返回的对象
DemoService.DemoBinder demoBinder = (DemoService.DemoBinder) service;
demoBinder.upload();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: ");
}
};
}
实例参见:Bound Services. A way to interact with the Service
2 . 通过 Intent 通信
在启动service
时,将要传递的数据放到intent
中,在前面提到的两种启动方式中,可以分别在onStartCommand()
和onBind()
方法中获取相应的intent,进而可以获取要传递的数据和参数,然后可以开启新的线程执行耗时操作等。要注意的是如果是context.startService()
方式启动,会每次唤起onStartCommand()
,而context.bindService()
方式,如果已经绑定,只会绑定一次,也就是只会调用一次onBind()
方法。
既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况
3.Remote Messenger通信
如果需要和service进行跨进程的通信,可以使用Messenger而不需要写一些AIDL文件。下面是一个service使用Messenger作为client接口的例子,当被绑定时将发送Messenger到内部的Handler。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
NotificationManager mNM;
/**
* 缓存所有注册到这个Service的client
*/
ArrayList<Messenger> mClients = new ArrayList<>();
/**
* client发送过来的最新的值
*/
int mValue = 0;
/**
* client发送的注册到这个service的消息标记
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* 解除注册,停止接收来自这个Service的回调。
* 发送这个消息标记的client必须是上一个发送注册消息的client。 */
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* 发送一个新的值到service或者是service发送到所有注册的client的一个新值。
*/
static final int MSG_SET_VALUE = 3;
/**
* 接收来自clients的消息的handler
*/
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;
Log.d(TAG, "handleMessage: " + mValue);
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) {
//这个client已经关闭或销毁,把它从client缓存中移除,这个遍历是从后往前的,所以删除操作是安全的。
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* clients发送消息到IncomingHandler的消息通道
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
showNotification();
}
/**
* 当绑定到这个service时,返回一个messenger的接口,client通过它发送消息到service。
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public void onDestroy() {
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* 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, ContentActivity.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher) // 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
.getNotification(); // 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的属性:
<service android:name=".app.MessengerService"
android:process=":remote" />//这里的remote是可选的,也可以使用其他的名字。
那么client可以按下面的方式来与service建立message的通信关系:
---省略---
/**
* 与service进行通信的messenger。
*/
Messenger mService = null;
/**
* 标记是否已经绑定到service
*/
boolean mIsBound;
/**
* 处理来自service的消息的handler
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
Log.d(TAG, "handleMessage: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
Log.d(TAG, "onServiceConnected: attached");
// 只要我们和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, 202, 0);
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service has crashed.
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
Log.d(TAG, "onServiceDisconnected: disconnected");
}
};
void doBindService() {
bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
Log.d(TAG, "doBindService: ");
}
void doUnbindService() {
if (mIsBound) {
if (mService != null) {
try {
Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service has crashed.
}
}
unbindService(mConnection);
mIsBound = false;
Log.d(TAG, "doUnbindService: ");
}
}
}
Foreground Service(意译为前台服务)并不是工作在前台的Service,实际上始终是工作在后台的。由于Service
工作在后台的原因,用户并不知道它在运行,有时候需要使用者知道某个Service
在运行时,就需要实现Foreground Service。其实就是在Service
开启的时候使用通知Notification
,这也是Android官方推荐的方式。只要在程序开启了Service,则使用一个常驻通知栏的Notification
表明服务正在运行,比如在后台Service进行音乐播放。 除了自己处理通知的方法外,Google在Android 2.0(SDK level 5)以上的SDK提供了一个直接而简单的方法,使用Service.startForeground()
和Service.stopForeground()
进行处理。
/ * 让service成为Foreground Service,并且产生一个“正在运行”的通知。
* 默认情况下,service是后台的,这意味着service在系统
* 回收内存(比如在浏览器里显示大图片)的时候可以被毫无顾忌的kill掉。
* 如果你比较在意这个service的挂掉,比如像后台音乐播放器这种突然挂了会影响用户的情况,就可以使用Foreground Service来提示用户。
*
* 参数
* id The identifier for this notification as per NotificationManager.notify(int, Notification).
* notification The Notification to be displayed.
*/
public final void startForeground (
int id, Notification notification)
/**
* 去掉service的foreground属性,允许在低内存时被kill掉
*
* Parameters
* removeNotification If true, the notification previously provided to
* startForeground(int, Notification)will be removed. Otherwise it will
* remain until a later call removes it (or the service is destroyed).
*/
public final void stopForeground (boolean removeNotification)
例如,一个从service
播放音乐的音乐播放器,应被设置为前台运行,因为用户会明确地注意它的运行.在状态栏中的通知可能会显示当前的歌曲并且允许用户启动一个activity
来与音乐播放器交互.
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());
Intent notificationIntent = new Intent(this, MusicActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);//这些方法都过时了
startForeground(ONGOING_NOTIFICATION, notification);
调用stopForeground()
,可以从前台移除service
,这个方法有boolean型参数,表明是否也从状态栏删除对应的通知.这个方法不会停掉service
.然而,如果你停止了正在前台运行的service
,这个通知也会被删除。
根据前面的总结,知道Service是运行在主线程,所以是不能直接进行耗时操作的。如果有耗时操作,还是需要放到子线程中,需要手动开启子线程。在Android 中还提供的一个非常简便的类 IntentService
,这是一个Service的子类,使用一个工作线程来处理所有的启动请求,一次处理一个。
使用姿势:继承IntentService
,实现onHandleIntent()
,这个方法接收每次启动请求发来的intent,而且它是在子线程里执行的哦。
IntentService
使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent,对于异步的startService请求,IntentService
会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程。这里就给我们提供了一个思路,如果有耗时的操作与其在Service里面开启新线程还不如使用IntentService
来处理耗时操作。而在一般的继承Service里面如果要进行耗时操作就必须另开线程,但是使用IntentService
就可以直接在里面进行耗时操作,因为默认实现了一个worker thread。对于异步的startService
请求,IntentService
会处理完成一个之后再处理第二个。
使用 IntentService
需要注意几点:
不可以直接和UI做交互。为了把它执行的结果体现在UI上,需要把结果返回给Activity。
工作任务队列是顺序执行的,如果一个任务正在IntentService
中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。
正在执行的任务无法打断。
IntentService源码分析
IntentService的使用和源码分析
标签:
原文地址:http://blog.csdn.net/zhsheng26/article/details/51366067