1.何为Service
Service是一个可以在后台长时间执行而不使用用户界面的Component,可以与其他的应用组件形成联系,从而可以根据传来的信息在合适的时候执行合适的操作。
而这种联系可以分为两种:startServie和bindService。这两者有着许多区别。
2.如何创建一个Service
1.创建一个类继承自Service(或它的子类,如IntentService),重写里面的一些关键的回调方法,如onStartCommand(),onBind()等
2.在Manifests文件里面为其声明,并根据需要配置一些其他属性。
里面有一些关键方法
- onCreate()
在每个service的生命周期中这个方法会且仅会调用一次,并且它的调用在onStartCommand()以及onBond()之前,我们可以在这个方法中进行一些一次性的初始化工作。 - onStartCommand()
当其他组件通过startService()方法启动service时,此方法将会被调用。 - onBind()
当其他组件通过bindService()方法与service相绑定之后,此方法将会被调用。这个方法有一个IBinder的返回值,这意味着在重写它的时候必须返回一个IBinder对象,它是用来支撑其他组件与service之间的通信的——另外,如果你不想让这个service被其他组件所绑定,可以通过在这个方法返回一个null值来实现。 - onDestroy()
这是service一生中调用的最后一个方法,当这个方法被调用之后,service就会被销毁。所以我们应当在这个方法里面进行一些资源的清理,比如注册的一些监听器什么的。
在Mainfest文件中声明Service时,只有Android:name属性是必须要有的,其他的属性可以没有,但是适当的配置可以让我们的开发更加的顺利
<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:enabled : 如果为true,则这个service可以被系统实例化,如果为false,则不行。默认为true
- android:exported : 如果为true,则其他应用的组件也可以调用这个service并且可以与它进行互动,如果为false,则只有与service同一个应用或者相同user ID的应用可以开启或绑定此service。它的默认值取决于service是否有intent filters。如果一个filter都没有,就意味着只有指定了service的准确的类名才能调用,也就是说这个service只能应用内部使用——其他的应用不知道它的类名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着service是考虑到外界使用的情况的,这时exported的默认值就为true
- android:icon : 一个象征着这个service的icon
- android:isolatedProcess : 如果设置为true,这个service将运行在一个从系统中其他部分分离出来的特殊进程中,我们只能通过Service API来与它进行交流。默认为false。
*android:label : 显示给用户的这个service的名字。如果不设置,将会默认使用的label属性。 - android:name : 这个service的路径名,例如“com.lypeer.demo.MyService”。这个属性是唯一一个必须填的属性。
- android:permission : 其他组件必须具有所填的权限才能启动这个service。
- android:process : service运行的进程的name。默认启动的service是运行在主进程中的
demo 如下
public class ServiceDemo extends Service {
private static final String TAG = "ServiceDome";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
//只在service创建的时候调用一次,可以在此进行一些一次性的初始化操作
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
//当其他组件调用startService()方法时,此方法将会被调用
//在这里进行这个service主要的操作
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
//当其他组件调用bindService()方法时,此方法将会被调用
//如果不想让这个service被绑定,在此返回null即可
return null;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
//service调用的最后一个方法
//在此进行资源的回收
super.onDestroy();
}
}
3.启动一个Service
3.1.rtService()
另一个组件通过调用startService()方法,就可以启动一个特定的service,并且这将导致service中的onStartCommand()方法被调用。在调用startService()方法的时候,其他组件需要在方法中传递一个intent参数,然后service将会在onStartCommand()中接收这个intent,并获取一些数据。比如此时某个Activity要将一些数据存入数据库中,我就可以通过intent把数据传入service,然后让service去进行连接数据库,存储数据等操作,而此时用户可以执行其他的任何操作——甚至包括销毁那个Activity——这并不会影响service存储数据这件事。
当一个service通过这种方式启动之后,它的生命周期就已经不受启动它的组件影响了,它可以在后台无限期的运行下去,只要service自身没有调用stopSelf()并且其他的组件没有调用针对它的stopService()。
另外,如果确定了使用这种方式启动service并且不希望这个service被绑定的话,那么也许除了传统的创建一个类继承service之外我们有一个更好的选择——继承IntentService。
如果是扩建Service类的话,通常情况下我们需要新建一个用于执行工作的新线程,因为默认情况下service将工作于应用的主线程,而这将会降低所有正在运行的Activity的性能。而IntentService就不同了。它是Service的子类,它使用工作线程来注意的处理所有的startService请求。如果你不要求这个service要同时处理多个请求,那么继承这个类显然要比直接继承Service好到不知道哪里去了——IntentService已经做了这些事:
- 默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
- 工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样的话就永远不必担心多线程问题了
- 完所有启动请求后停止服务,从此妈妈再也不用担心我忘记调用 stopSelf() 了
- onBind() 的默认实现(返回 null)
- onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现
- 我们只需要实现onHandleIntent()方法来完成具体的功能逻辑就可以了。
demo如下
//现在在一个Activity里面
Intent intent = new Intent(MainActivity.this , IntentServiceDemo.class);
startService(intent);
public class IntentServiceDemo extends IntentService {
public IntentServiceDemo(String name) {
super(name);
//构造方法
}
@Override
protected void onHandleIntent(Intent intent) {
//在这里根据intent进行操作
}
}
相比上面的继承service实现,这个确实要简单许多。但是要注意的是,如果需要重写其他的方法,比如onDestroy()方法,一定不要删掉它的超类实现!因为它的超类实现里面也许包括了对工作线程还有工作队列的初始化以及销毁等操作,如果没有了的话很容易出问题。
但是,如果你有让service同时处理多个请求的需求,这个时候就只能去继承Service了。这个时候就要自己去处理工作线程那些事。下面是一个官方的栗子:
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
3.bindService()
这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件成为客户端,而称它为服务器。
如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式:继承Binder类,使用Messenger类,使用AIDL。
4.bindService()
要完成客户端与服务端的绑定,有两件事要做。一是在客户端完成bindService的调用以及相关配置,二是在服务端里面实现onBind()方法的重写,返回一个用做信息交互的IBinder接口。接下来我们就一块一块的来看它的实现方法。
4.1客户端的配置
客户端原则上来讲调用bindService()方法就可以了,然而事实并没有这么简单。原因就出在bindService()这个方法身上。下面我们来详细的了解一下这个方法:
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return mBase.bindService(service, conn, flags);
}
可以看到,bindService()方法需要三个参数,第一个是一个intent,我们都很熟悉——它和startService()里面那个intent是一样的,用来指定启动哪一个service以及传递一些数据过去。第二个参数可能就有点陌生了,这是个啥?这是实现客户端与服务端通信的一个关键类。要想实现它,就必须重写两个回调方法:onServiceConnected()以及onServiceDisconnected(),而我们可以通过这两个回调方法得到服务端里面的IBinder对象,从而达到通信的目的(下文对此会有更加详细的介绍)。下面是一个例子:
ServiceDemo mService;
//BinderDemo是在ServiceDemo里面的一个继承了Binder的内部类,这是一种得到IBinder接口的方式
//下文会有详述
ServiceDemo.BinderDemo mBinder;
private ServiceConnection mConnection = new ServiceConnection() {
//系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当系统调用 onServiceConnected() 回调方法时,我们可以使用接口定义的方法开始调用服务。
mBinder = (ServiceDemo.BinderDemo) service;
//getService()是BinderDemo中的一个方法
mService = mBinder.getService();
//在此处可以利用得到的ServiceDemo对象调用该类中的构造方法
Log.d(this.getClass().getSimpleName(), "onServiceConnected");
}
//Android系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统“绝对不会”调用该方法。
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(this.getClass().getSimpleName(), "onServiceDisconnected");
}
};
bindService()方法的第三个参数是一个int值,还叫flag(这flag立的),它是用来做什么的呢?它是一个指示绑定选项的标志,通常应该是 BIND_AUTO_CREATE,以便创建尚未激活的服务。 其他可能的值为 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示无)。
4.2 服务端的配置
IBinder接口是什么呢?它是一个在整个Android系统中都非常重要的东西,是为高性能而设计的轻量级远程调用机制的核心部分。当然,它不仅仅可以用于远程调用,也可以用于进程内调用——事实上,我们现在所说的service这里的IBinder既有可能出现远程调用的场景,比如用它来进行IPC,也有可能出现进程内调用的场景,比如用它来进行同进程内客户端与服务器的交互。IBinder的具体的工作原理在这里就不详述了,以后我应该会就这一块的内容单独写一系列的博客,在这里只需要知道它是客户端用来和服务器进行交互的接口,并且知道可以怎样通过IBinder来实现它们的交互就可以了。
一般来讲,我们有三种方式可以获得IBinder的对象:继承Binder类,使用Messenger类,使用AIDL。接下来我将就这三种方式展开来讲。
4.2.1 继承Binder类
通过实现Binder类,我们的客户端可以直接通过这个类调用服务端的公有方法。另外,虽然从IPC的角度来讲,Binder是Android中的一种跨进程通信方式,但是其实一般service里面的Binder是不会涉及进程间通信的,所以其在这种情况下显得较为简单。
下面我们来看下通过继承Binder类实现客户端与服务端通信应该怎样做:
- 在service类中,创建一个满足以下任一要求的Binder实例:
- 包含客户端可调用的公共方法
- 返回当前Service实例,其中包含客户端可调用的公共方法
- 返回由当前service承载的其他类的实例,其中包含客户端可调用的公共方法
- 在onBind()方法中返回这个Binder实例
- 在客户端中通过onServiceDisconnected()方法接收传过去的Binder实例,并通过它提供的方法进行后续操作
4.2.2 使用Messenger
大家看到Messenger可能会很轻易的联想到Message,然后很自然的进一步联想到Handler——没错,Messenger的核心其实就是Message以及Handler来进行线程间的通信。下面讲一下通过这种方式实现IPC的步骤:
- 服务端实现一个Handler,由其接受来自客户端的每个调用的回调
- 使用实现的Handler创建Messenger对象
- 通过Messenger得到一个IBinder对象,并将其通过onBind()返回给客户端
- 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
- 服务端在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message
4.2.3 通过AIDL
IDL,即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。在我看来,它其实就是一个模板。为什么这样说呢?在我们的使用中,实际上起作用的并不是我们写的AIDL代码,而是系统根据它生成的一个IInterface实例的代码。而如果大家多生成几个这样的实例,然后把它们拿来比较,你会发现它们都是有套路的——都是一样的流程,一样的结构,只是根据具体的AIDL文件的不同有细微的变动。所以其实AIDL就是为了避免我们一遍遍的写一些千篇一律的代码而出现的一个模板。
那么如何使用AIDL来通过bindService()进行线程间通信呢?基本上有下面这些步骤:
- 服务端创建一个AIDL文件,将暴露给客户端的接口在里面声明
- 在service中实现这些接口
- 客户端绑定服务端,并将onServiceConnected()得到的IBinder转为AIDL生成的IInterface实例
- 通过得到的实例调用其暴露的方法
上面的描述其实比较抽象,基本上是那种看了也不知道怎么做的类型——这个如果要展开讲的话就又是长篇大论的了。基于这种考虑,这里只是简单的介绍一下AIDL这个东西,它的具体的语法,到底怎么来实现IPC?
最常见的aidl的使用就是Service的跨进程通信了,那么我们就写一个Activity和Service的跨进程通信吧。
首先,我们就在AS里面新建一个aidl文件(ps:现在AS建aidl不要求和java包名相同了):
package aidl;
interface IMyInterface {
String getInfor(String s);
}
public class MyService extends Service {
public final static String TAG = "MyService";
private IBinder binder = new IMyInterface.Stub() {
@Override
public String getInfor(String s) throws RemoteException {
Log.i(TAG, s);
return "我是 Service 返回的字符串";
}
};
@Overrid
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreat");
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}