开发者文档
API文档-中文:https://developer.android.google.cn/guide/components/services.html
API文档-英文:https://developer.android.com/reference/android/app/Service.html
什么是服务
service可以说是一个在后台运行的Activity,它不是一个单独的进程,它只需要应用告诉它要在后台做什么就可以了。
它要实现和用户的交互的话需要通过通知栏或则是发送广播,UI去接收显示。它的应用十分广泛,尤其是在框架层,应用更多的是对系统服务的调用。
服务有什么用
它用于处理一些不干扰用户使用的后台操作。如下载,网络获取。播放音乐,他可以通过INTENT来开启,同时也可以绑定到宿主对象(调用者例如ACTIVITY上)来使用。
Service在清单文件中的声明
前面说过Service分为启动状态和绑定状态两种,但无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来,也都需要在AndroidManifest.xml中声明,那么在分析这两种状态之前,我们先来了解一下Service在AndroidManifest.xml中的声明语法,其格式如下:
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
Android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
android:name:对应Service类名
android:permission:是权限声明
android:process:是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。
android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
bind标志位
绑定一个服务,需要设置ServiceConnection和标志位,方法如下:
/**
* 绑定服务
*/
public void bind(View view) {
conn = new MyConn();
intent = new Intent(ServiceActivity.this, MyService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
ServiceConnection可以监听服务的状态,在进行服务绑定的时,其标志位可以为以下几种(这里列出3种):
Context.BIND_AUTO_CREATE
说明:表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁
Context.BIND_DEBUG_UNBIND
说明:通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用
Context.BIND_NOT_FOREGROUND
说明:表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位位于Froyo中引入。
注意:绑定服务的以异步方式运行的。绑定服务必须在当前的上下文环境中运行,某些场景中,通过上下文进行添加绑定接触方法如下:
getApplicationContext().bindService(service, conn, flags)
onStartCommand标志位
请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一:
START_NOT_STICKY
当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
START_STICKY
当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
START_REDELIVER_INTENT
当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,
Service绑定服务
绑定服务是Service的另一种变形,当Service处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件(如 Activity)绑定到服务时(有时我们可能需要从Activity组建中去调用Service中的方法,此时Activity以绑定的方式挂靠到Service后,我们就可以轻松地方法到Service中的指定方法),组件(如Activity)可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即IPC,这个后面再单独分析)。与启动服务不同的是绑定服务的生命周期通常只在为其他应用组件(如Activity)服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如Activity)解除绑定后,绑定服务就会被销毁。那么在提供绑定的服务时,该如何实现呢?实际上我们必须提供一个 IBinder接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:
扩展 Binder 类
如果服务是提供给自有应用专用的,并且Service(服务端)与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中以及Service 中可用的公共方法。如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。 不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。
前面描述过,如果我们的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。其使用开发步骤如下:
创建BindService服务端,继承自Service并在类中,创建一个实现IBinder 接口的实例对象并提供公共方法给客户端调用
从 onBind() 回调方法返回此 Binder 实例。
在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。
注意:此方式只有在客户端和服务位于同一应用和进程内才有效,如对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。另一点之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方式不执行任何跨进程编组。
以下是一个扩展 Binder 类的实例,先看看Service端的实现BindService.Java
package com.zejian.ipctest.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* Created by zejian
* Time 2016/10/2.
* Description:绑定服务简单实例--服务端
*/
public class LocalService extends Service{
private final static String TAG = "wzj";
private int count;
private boolean quit;
private Thread thread;
private LocalBinder binder = new LocalBinder();
/**
* 创建Binder对象,返回给客户端即Activity使用,提供数据交换的接口
*/
public class LocalBinder extends Binder {
// 声明一个方法,getService。(提供给客户端调用)
LocalService getService() {
// 返回当前对象LocalService,这样我们就可在客户端端调用Service的公共方法了
return LocalService.this;
}
}
/**
* 把Binder类返回给客户端
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service is invoke Created");
thread = new Thread(new Runnable() {
@Override
public void run() {
// 每间隔一秒count加1 ,直到quit为true。
while (!quit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
});
thread.start();
}
/**
* 公共方法
* @return
*/
public int getCount(){
return count;
}
/**
* 解除绑定时调用
* @return
*/
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "Service is invoke onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "Service is invoke Destroyed");
this.quit = true;
super.onDestroy();
}
}
BindService类继承自Service,在该类中创建了一个LocalBinder继承自Binder类,LocalBinder中声明了一个getService方法,客户端可访问该方法获取LocalService对象的实例,只要客户端获取到LocalService对象的实例就可调用LocalService服务端的公共方法,如getCount方法,值得注意的是,我们在onBind方法中返回了binder对象,该对象便是LocalBinder的具体实例,而binder对象最终会返回给客户端,客户端通过返回的binder对象便可以与服务端实现交互。接着看看客户端BindActivity的实现:
package com.zejian.ipctest.service;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/**
* Created by zejian
* Time 2016/10/2.
* Description:绑定服务实例--客户端
*/
public class BindActivity extends Activity {
protected static final String TAG = "wzj";
Button btnBind;
Button btnUnBind;
Button btnGetDatas;
/**
* ServiceConnection代表与服务的连接,它只有两个方法,
* onServiceConnected和onServiceDisconnected,
* 前者是在操作者在连接一个服务成功时被调用,而后者是在服务崩溃或被杀死导致的连接中断时被调用
*/
private ServiceConnection conn;
private LocalService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind);
btnBind = (Button) findViewById(R.id.BindService);
btnUnBind = (Button) findViewById(R.id.unBindService);
btnGetDatas = (Button) findViewById(R.id.getServiceDatas);
//创建绑定对象
final Intent intent = new Intent(this, LocalService.class);
// 开启绑定
btnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "绑定调用:bindService");
//调用绑定方法
bindService(intent, conn, Service.BIND_AUTO_CREATE);
}
});
// 解除绑定
btnUnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "解除绑定调用:unbindService");
// 解除绑定
if(mService!=null) {
mService = null;
unbindService(conn);
}
}
});
// 获取数据
btnGetDatas.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mService != null) {
// 通过绑定服务传递的Binder对象,获取Service暴露出来的数据
Log.d(TAG, "从服务端获取数据:" + mService.getCount());
} else {
Log.d(TAG, "还没绑定呢,先绑定,无法从服务端获取数据");
}
}
});
conn = new ServiceConnection() {
/**
* 与服务器端交互的接口方法 绑定服务的时候被回调,在这个方法获取绑定Service传递过来的IBinder对象,
* 通过这个IBinder对象,实现宿主和Service的交互。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "绑定成功调用:onServiceConnected");
// 获取Binder
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
/**
* 当取消绑定的时候被回调。但正常情况下是不被调用的,它的调用时机是当Service服务被意外销毁时,
* 例如内存的资源不足时这个方法才被自动调用。
*/
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
}
}
在客户端中我们创建了一个ServiceConnection对象,该代表与服务的连接,它只有两个方法, onServiceConnected和onServiceDisconnected,其含义如下:
onServiceConnected(ComponentName name, IBinder service)
系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。其中service便是服务端返回的IBinder实现类对象,通过该对象我们便可以调用获取LocalService实例对象,进而调用服务端的公共方法。而ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,如包名,组件描述等信息,较少使用该参数。
onServiceDisconnected(ComponentName name)
Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "绑定成功调用:onServiceConnected");
// 获取Binder
LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
mService = binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService=null;
}
};
在onServiceConnected()被回调前,我们还需先把当前Activity绑定到服务LocalService上,绑定服务是通过通过bindService()方法,解绑服务则使用unbindService()方法,这两个方法解析如下:
bindService(Intent service, ServiceConnection conn, int flags)
该方法执行绑定服务操作,其中Intent是我们要绑定的服务(也就是LocalService)的意图,而ServiceConnection代表与服务的连接,它只有两个方法,前面已分析过,flags则是指定绑定时是否自动创建Service。0代表不自动创建、BIND_AUTO_CREATE则代表自动创建。
unbindService(ServiceConnection conn)
该方法执行解除绑定的操作,其中ServiceConnection代表与服务的连接,它只有两个方法,前面已分析过。
Activity通过bindService()绑定到LocalService后,ServiceConnection#onServiceConnected()便会被回调并可以获取到LocalService实例对象mService,之后我们就可以调用LocalService服务端的公共方法了,最后还需要在清单文件中声明该Service。而客户端布局文件实现如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/BindService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绑定服务器"
/>
<Button
android:id="@+id/unBindService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解除绑定"
/>
<Button
android:id="@+id/getServiceDatas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取服务方数据"
/>
</LinearLayout>
我们运行程序,点击绑定服务并多次点击绑定服务接着多次调用LocalService中的getCount()获取数据,最后调用解除绑定的方法移除服务,其结果如下:
通过Log可知,当我们第一次点击绑定服务时,LocalService服务端的onCreate()、onBind方法会依次被调用,此时客户端的ServiceConnection#onServiceConnected()被调用并返回LocalBinder对象,接着调用LocalBinder#getService方法返回LocalService实例对象,此时客户端便持有了LocalService的实例对象,也就可以任意调用LocalService类中的声明公共方法了。更值得注意的是,我们多次调用bindService方法绑定LocalService服务端,而LocalService得onBind方法只调用了一次,那就是在第一次调用bindService时才会回调onBind方法。接着我们点击获取服务端的数据,从Log中看出我们点击了3次通过getCount()获取了服务端的3个不同数据,最后点击解除绑定,此时LocalService的onUnBind、onDestroy方法依次被回调,并且多次绑定只需一次解绑即可。此情景也就说明了绑定状态下的Service生命周期方法的调用依次为onCreate()、onBind、onUnBind、onDestroy。ok~,以上便是同一应用同一进程中客户端与服务端的绑定回调方式。
使用 Messenger
Messenger可以翻译为信使,通过它可以在不同的进程中共传递Message对象(Handler中的Messager,因此 Handler 是 Messenger 的基础),在Message中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。
前面了解了如何使用IBinder应用内同一进程的通信后,我们接着来了解服务与远程进程(即不同进程间)通信,而不同进程间的通信,最简单的方式就是使用 Messenger 服务提供通信接口,利用此方式,我们无需使用 AIDL 便可执行进程间通信 (IPC)。以下是 Messenger 使用的主要步骤:
1.服务实现一个 Handler,由其接收来自客户端的每个调用的回调
2.Handler 用于创建 Messenger 对象(对 Handler 的引用)
3.Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
4.客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用Messenger将 Message 对象发送给服务
5.服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message
以下是一个使用 Messenger 接口的简单服务示例,服务端进程实现如下:
package com.zejian.ipctest.messenger;
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.util.Log;
/**
* Created by zejian
* Time 2016/10/3.
* Description:Messenger服务端简单实例,服务端进程
*/
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
private static final String TAG ="wzj" ;
/**
* 用于接收从客户端传递过来的数据
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 创建Messenger并传入Handler实例对象
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* 当绑定Service时,该方法被调用,将通过mMessenger返回一个实现
* IBinder接口的实例对象
*/
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Service is invoke onBind");
return mMessenger.getBinder();
}
}
首先我们同样需要创建一个服务类MessengerService继承自Service,同时创建一个继承自Handler的IncomingHandler对象来接收客户端进程发送过来的消息并通过其handleMessage(Message msg)进行消息处理。接着通过IncomingHandler对象创建一个Messenger对象,该对象是与客户端交互的特殊对象,然后在Service的onBind中返回这个Messenger对象的底层Binder即可。下面看看客户端进程的实现:
package com.zejian.ipctest.messenger;
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.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.zejian.ipctest.R;
/**
* Created by zejian
* Time 2016/10/3.
* Description: 与服务器交互的客户端
*/
public class ActivityMessenger extends Activity {
/**
* 与服务端交互的Messenger
*/
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* 实现与服务端链接的对象
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
/**
* 通过服务端传递的IBinder对象,创建相应的Messenger
* 通过该Messenger对象与服务端进行交互
*/
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 创建与服务交互的消息实体Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
//发送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenager);
Button bindService= (Button) findViewById(R.id.bindService);
Button unbindService= (Button) findViewById(R.id.unbindService);
Button sendMsg= (Button) findViewById(R.id.sendMsgToService);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("zj","onClick-->bindService");
//当前Activity绑定服务端
bindService(new Intent(ActivityMessenger.this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
});
//发送消息给服务端
sendMsg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sayHello(v);
}
});
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Unbind from the service
if (mBound) {
Log.d("zj","onClick-->unbindService");
unbindService(mConnection);
mBound = false;
}
}
});
}
}
在客户端进程中,我们需要创建一个ServiceConnection对象,该对象代表与服务端的链接,当调用bindService方法将当前Activity绑定到MessengerService时,onServiceConnected方法被调用,利用服务端传递给来的底层Binder对象构造出与服务端交互的Messenger对象,接着创建与服务交互的消息实体Message,将要发生的信息封装在Message中并通过Messenger实例对象发送给服务端。关于ServiceConnection、bindService方法、unbindService方法,前面已分析过,这里就不重复了,最后我们需要在清单文件声明Service和Activity,由于要测试不同进程的交互,则需要将Service放在单独的进程中,因此Service声明如下:
<service android:name=".messenger.MessengerService"
android:process=":remote"
/>
其中android:process=”:remote”代表该Service在单独的进程中创建,最后我们运行程序,结果如下:
接着多次点击绑定服务,然后发送信息给服务端,最后解除绑定,Log打印如下:
通过上述例子可知Service服务端确实收到了客户端发送的信息,而且在Messenger中进行数据传递必须将数据封装到Message中,因为Message和Messenger都实现了Parcelable接口,可以轻松跨进程传递数据(关于Parcelable接口可以看博主的另一篇文章:序列化与反序列化之Parcelable和Serializable浅析),而Message可以传递的信息载体有,what,arg1,arg2,Bundle以及replyTo,至于object字段,对于同一进程中的数据传递确实很实用,但对于进程间的通信,则显得相当尴尬,在android2.2前,object不支持跨进程传输,但即便是android2.2之后也只能传递android系统提供的实现了Parcelable接口的对象,也就是说我们通过自定义实现Parcelable接口的对象无法通过object字段来传递,因此object字段的实用性在跨进程中也变得相当低了。不过所幸我们还有Bundle对象,Bundle可以支持大量的数据类型。接着从Log我们也看出无论是使用拓展Binder类的实现方式还是使用Messenger的实现方式,它们的生命周期方法的调用顺序基本是一样的,即onCreate()、onBind、onUnBind、onDestroy,而且多次绑定中也只有第一次时才调用onBind()。好~,以上的例子演示了如何在服务端解释客户端发送的消息,但有时候我们可能还需要服务端能回应客户端,这时便需要提供双向消息传递了,下面就来实现一个简单服务端与客户端双向消息传递的简单例子。
先来看看服务端的修改,在服务端,我们只需修改IncomingHandler,收到消息后,给客户端回复一条信息。
/**
* 用于接收从客户端传递过来的数据
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "thanks,Service had receiver message from client!");
//回复客户端信息,该对象由客户端传递过来
Messenger client=msg.replyTo;
//获取回复信息的消息实体
Message replyMsg=Message.obtain(null,MessengerService.MSG_SAY_HELLO);
Bundle bundle=new Bundle();
bundle.putString("reply","ok~,I had receiver message from you! ");
replyMsg.setData(bundle);
//向客户端发送消息
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
接着修改客户端,为了接收服务端的回复,客户端也需要一个接收消息的Messenger和Handler,其实现如下:
/**
* 用于接收服务器返回的信息
*/
private Messenger mRecevierReplyMsg= new Messenger(new ReceiverReplyMsgHandler());
private static class ReceiverReplyMsgHandler extends Handler{
private static final String TAG = "zj";
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
//接收服务端回复
case MessengerService.MSG_SAY_HELLO:
Log.i(TAG, "receiver message from service:"+msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
除了添加以上代码,还需要在发送信息时把接收服务器端的回复的Messenger通过Message的replyTo参数传递给服务端,以便作为同学桥梁,代码如下:
public void sayHello(View v) {
if (!mBound) return;
// 创建与服务交互的消息实体Message
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
//把接收服务器端的回复的Messenger通过Message的replyTo参数传递给服务端
msg.replyTo=mRecevierReplyMsg;
try {
//发送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
ok~,到此服务端与客户端双向消息传递的简单例子修改完成,我们运行一下代码,看看Log打印,如下:
由Log可知,服务端和客户端确实各自收到了信息,到此我们就把采用Messenge进行跨进程通信的方式分析完了,最后为了辅助大家理解,这里提供一张通过Messenge方式进行进程间通信的原理图:
关于绑定服务的注意点
1.多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService() 也启动了该服务)。
2.通常情况下我们应该在客户端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 时刻设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况:
如果只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
如果希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。需要注意的是,这意味着 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当提高该进程的权重时,系统很可能会终止该进程。
3.通常情况下(注意),切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。此外,如果应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在 Activity 的 onResume() 和 onPause()中。
4.我们应该始终捕获 DeadObjectException DeadObjectException 异常,该异常是在连接中断时引发的,表示调用的对象已死亡,也就是Service对象已销毁,这是远程方法引发的唯一异常,DeadObjectException继承自RemoteException,因此我们也可以捕获RemoteException异常。
5.应用组件(客户端)可通过调用 bindService() 绑定到服务,Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder,而该绑定是异步执行的。
关于启动服务与绑定服务间的转换问题
通过前面对两种服务状态的分析,相信大家已对Service的两种状态有了比较清晰的了解,那么现在我们就来分析一下当启动状态和绑定状态同时存在时,又会是怎么的场景?
虽然服务的状态有启动和绑定两种,但实际上一个服务可以同时是这两种状态,也就是说,它既可以是启动服务(以无限期运行),也可以是绑定服务。有点需要注意的是Android系统仅会为一个Service创建一个实例对象,所以不管是启动服务还是绑定服务,操作的是同一个Service实例,而且由于绑定服务或者启动服务执行顺序问题将会出现以下两种情况:
先绑定服务后启动服务
如果当前Service实例先以绑定状态运行,然后再以启动状态运行,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,指定收到调用停止服务或者内存不足时才会销毁该服务。
先启动服务后绑定服务
如果当前Service实例先以启动状态运行,然后再以绑定状态运行,当前启动服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有Context调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。
以上两种情况显示出启动服务的优先级确实比绑定服务高一些。不过无论Service是处于启动状态还是绑定状态,或处于启动并且绑定状态,我们都可以像使用Activity那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 当然,我们也可以通过清单文件将服务声明为私有服务,阻止其他应用访问。最后这里有点需要特殊说明一下的,由于服务在其托管进程的主线程中运行(UI线程),它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何耗时事件或阻止性操作(例如 MP3 播放或联网)时,则应在服务内创建新线程来完成这项工作,简而言之,耗时操作应该另起线程执行。只有通过使用单独的线程,才可以降低发生“应用无响应”(ANR) 错误的风险,这样应用的主线程才能专注于用户与 Activity 之间的交互, 以达到更好的用户体验。
使用 AIDL
由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger 的跨进程方式其底层实现 就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用AIDL必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。
《AIDL解析(一)两个应用之间使用AIDL进行通信的例子》
《AIDL解析(二 )让Service主动调起Activity吧!》
以上3种实现方式,我们可以根据需求自由的选择,但需要注意的是大多数应用“都不会”使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本篇中也不打算阐述如何使用AIDL(后面会另开一篇分析AIDL),接下来我们分别针对扩展 Binder 类和Messenger的使用进行分析。
停止服务
(1)启动服务停止有2种方法:
1. stopSelf() 自我停止服务
2. stopService(Intent name) 被动停止服务
(2)绑定服务的解除绑定方法如下:
unbindService(ServiceConnection conn)
启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。
一旦请求使用 stopSelf() 或 stopService() 停止服务,系统就会尽快销毁服务。
但是,如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand() 的 startId)。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求,ID 就不匹配,服务也就不会停止。
注意:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。
如需了解有关服务生命周期的详细信息,请参阅下面有关管理服务生命周期的部分。
使用细节
在注册服务的时候,为了将service纳入编译系统,必须在AndroidMainfest.xml中对Service进行显式声明。
计算量较大的又不是UI层的工作的话,可以选择放置在Service中进行工作。
通过开发文档你会发现,Android中的Service与宿主(调用者)在同一线程,而不是专门起一条线程,这意味着,如果你的服务要CPU密集型操作(如:MP3播放)或则阻塞操作(如网络)时,必须产生它自己的线程来完成这个工作,否则会造成线程阻塞。在Service的子类里面,IntentService类服务可以作为一个标准的实施,它的工作有其自己的线程。
注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。
基础知识
- 如果在使用Service的时候又使用了广播接收器配合工作,广播如果是动态注册的话,在服务停止的时候记得调用unregisterReceiver(receiver);这个方法来注销掉接收器
拓展
1.如何检查Android后台服务线程(Service类)是否正在运行
Android系统自己提供了一个函数ActivityManager.getRunningServices,可以列出当前正在运行的后台服务线程
private boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if ("com.example.MyService".equals(service.service.getClassName())) {
return true;
}
}
return false;
}
2.Service与UI之间的通信方式
(1)使用直接启动的startService实现信息传递
流程:UI ——>Service
操作:使用Intent进行数据传递,通过服务中的onStartCommand方法进行接受(和Activity间传递方式一样)
(2)使用绑定启动的bindservice实现信息传递(
流程:UI ——>Service
(3)使用Broadcast(广播)进行信息的双向传递
流程:UI <——>Service
操作:注册绑定广播接受器,之后通过广播来进行2者间通信
注意:在服务退出的时候记得unregisterReceiver(receiver);注销广播接收器
(4)使用接口进行信息的双向传递
流程:UI <——>Service
操作:在ServiceConnection中获得IBinder中的Service对象并完成绑定
3.Service和Thread的区别
我们拿服务来进行一个后台长时间的动作,为了不阻塞线程,然而,Thread就可以达到这个效果,为什么我们不直接使用Thread去代替服务呢?(这个问题摘抄至网上,原文地址不是是哪个,所以没写上)
这里提下,
1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
2). Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!
既然这样,那么我们为什么要用 Service 呢?其实这跟 android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。
举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。
因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。
两者概念的迥异
Thread 是程序执行的最小单元,它是分配CPU的基本单位,android系统中UI线程也是线程的一种,当然Thread还可以用于执行一些耗时异步的操作。
Service是Android的一种机制,服务是运行在主线程上的,它是由系统进程托管。它与其他组件之间的通信类似于client和server,是一种轻量级的IPC通信,这种通信的载体是binder,它是在Linux层交换信息的一种IPC,而所谓的Service后台任务只不过是指没有UI的组件罢了。
两者的执行任务迥异
在android系统中,线程一般指的是工作线程(即后台线程),而主线程是一种特殊的工作线程,它负责将事件分派给相应的用户界面小工具,如绘图事件及事件响应,因此为了保证应用 UI 的响应能力主线程上不可执行耗时操作。如果执行的操作不能很快完成,则应确保它们在单独的工作线程执行。
Service 则是android系统中的组件,一般情况下它运行于主线程中,因此在Service中是不可以执行耗时操作的,否则系统会报ANR异常,之所以称Service为后台服务,大部分原因是它本身没有UI,用户无法感知(当然也可以利用某些手段让用户知道),但如果需要让Service执行耗时任务,可在Service中开启单独线程去执行。
两者使用场景
当要执行耗时的网络或者数据库查询以及其他阻塞UI线程或密集使用CPU的任务时,都应该使用工作线程(Thread),这样才能保证UI线程不被占用而影响用户体验。
在应用程序中,如果需要长时间的在后台运行,而且不需要交互的情况下,使用服务。比如播放音乐,通过Service+Notification方式在后台执行同时在通知栏显示着。
两者的最佳使用方式
在大部分情况下,Thread和Service都会结合着使用,比如下载文件,一般会通过Service在后台执行+Notification在通知栏显示+Thread异步下载,再如应用程序会维持一个Service来从网络中获取推送服务。在Android官方看来也是如此,所以官网提供了一个Thread与Service的结合来方便我们执行后台耗时任务,它就是IntentService,(如果想更深入了解IntentService,可以看博主的另一篇文章:Android 多线程之IntentService 完全详解),当然 IntentService并不适用于所有的场景,但它的优点是使用方便、代码简洁,不需要我们创建Service实例并同时也创建线程,某些场景下还是非常赞的!由于IntentService是单个worker thread,所以任务需要排队,因此不适合大多数的多任务情况。
两者的真正关系
两者没有半毛钱关系。
4.扩展 IntentService 类
由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。
IntentService 执行以下操作:
创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
提供 onBind() 的默认实现(返回 null)。
提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。)
具体可见:《IntentService,用完即走》