如果说Activity通常都会提供一个用户界面UI的话,那么服务则不会提供任何用户界面,尽管如此,服务的作用仍然非常重要,它为我们提供了一种类似守护线程的手段来维持一些希望在退出以后仍然能持续运行的程序。
既然服务的作用如此重要,本篇主要讲解如何使用服务和声明应用程序服务,下一节讲解怎么高效率的运用服务。
服务是一个应用程序组件,它在后台执行运行时间比较长的操作,不提供用户界面。它可以被其他应用程序组件启动或停止,并且当用户切换到另一个应用程序时,它仍然在后台持续的运行。另外,组件可以绑定到服务来与之交互甚至执行IPC(进程间通信)。例如,服务可以处理网络事务,播放音乐,执行文件I/O,或者与内容提供者交互,所有这些都在后台完成。
一般情况下,服务主要有两种形式:被启动和被绑定。在这两种方式下,服务呈现不同的特性,下面简要介绍这两种形式的服务。
Ⅰ被启动方式
当应用程序组件(例如Activity)通过startService()方法启动时,服务是被启动的。一旦启动,服务可以在后台无限期运行,甚至在那个启动它的组件被销毁时。通常,一个启动的服务执行一个单一操作并且不返回结果。例如,可以使用服务在后台播放一段音乐,当播放完成时,服务会自己停止。在没有特别编制代码通知启动服务组件的情况下,该组件无法知道服务的处理进度。
Ⅱ被绑定方式
当一个应用程序组件通过调用bindService()方法绑定服务时,服务是被绑定的。一个绑定的服务会提供一个服务接口,允许组件与服务交互,发送请求,获得结果以及通过带有IPC的方式完成一些任务。只有在其他应用程序组件绑定到这个服务时,这个绑定服务才会运行。多个组件可以马上绑定到这个服务,但是当所有组件都解绑的时候,这个服务就会被销毁。
无论应用程序是否被启动,被绑定或者是两者均有,应用程序组件均可以使用Activity的相同方式——使用Intent启动它,使用这个服务(可以来自另一个程序)。然而,我们还可以定义一个私有的服务,阻止其他应用程序的访问。
注意:服务运行在其宿主进程的主线程中——这个服务不会创建它自己的线程,也不会在另外的进程中运行(除非特别指定)。这意味着,如果服务要做任何CPU密集的工作或者阻止操作(例如MP3回放或者上网),我们都应该在服务中创建一个新线程去做这些事情。通过使用另外一个线程减少ANR的风险,并且这个应用程序的主线程依然可以专门为用户和Activity交互服务。
前面我们介绍了服务的一些基础知识以及服务的一些基础特性,自此读者或许对服务有了一些概念上的理解。接下来,我们深入其中教大家如何创建一个自己的服务,以及如何将这个服务应用到我们的应用程序中,使其为我们服务。
要创建一个服务,就必须创建一个Service的子类。在实现中,需要重写一些回调方法,这些回调方法处理服务生命周期以及为绑定到这个服务的组件提供一种机制。此外还应该实现如下所示的回调方法。
ⅠonStartCommand():该方法在其他组件(比如Activity)通过startService()方法调用请求服务启动时候由系统调用。一旦执行这个方法,这个服务就将启动并且在后台永久性地运行。如果我们实现了它,应该通过调用stopSelf()或者stopService()的方式停止这个服务(如果只想支持绑定,就没必要实现这个方法了)。
ⅡonBind():该方法在另一个组件通过调用bindService()方法绑定到这个服务(例如执行RPC远程过程调用协议)的时候由系统调用。在这个方法中,通过返回IBinder对象来提供客户端与该服务沟通的端口。一般情况睛,我们必须实现这个方法。如果不允许绑定,该方法应该返回null。
ⅢonCreate():该方法在服务第一次被常见时由系统调用(在调用onStartCommand()或onBinder()之前)。在该方法中,你可以对你的服务做一些初始化的操作。如果服务已经运行,这个方法不会被调用。这里不建议做过长时间的操作,比如读一个文件。
ⅣonDestroy():该方法在服务不再被使用并且正在销毁的时候由系统调用。服务应该实现这个方法清理所有资源(例如线程,被注册的侦听器和接收器等),它是服务接收的最后一个调用。
如果一个组件通过调用startService()启动服务(调用到onStartCommand()中的结果),这个服务依然运行,直到通过stopSelf()或者另一个组件通过调用stopService()来停止它。
如果一个组件调用bindService()去创建一个服务(onStartCommand()没有被调用),这个服务只有在组件绑定到它的时候才运行。一旦服务解除绑定,系统就会销毁它。
在低内存的时候,Android系统将强制停止服务并且它必须为那个拥有用户焦点的Activity回收系统资源。如果这个服务被绑定到一个拥有用户焦点的Activity,则它不太可能被销毁,如果服务被声明为前台运行,那么它将永远不会被销毁。否则,如果服务被启动并长时间地运行,那么系统会随着时间的迁移而降低它在后台任务列表的位置,并且这个服务会有比较高的风险被销毁。如果我们的服务被启动,就必须设计它很好的处理系统的重启。如果系统销毁服务,则该服务在资源再次变成有效的时候是否马上重启取决于从onStartCommand()的返回值。
像Activity以及其他的组件一样,我们必须在应用程序的manifest文件中声明所有的服务。
为了声明服务,要添加一个
在
就像Activity一样,服务可以定义Intent筛选器,这些筛选器允许其他组件使用隐含的Intent调用服务。通过声明Intent筛选器,如果应用程序可通过startService()方法的Intent参数符合Intent筛选器定义的条件,则来自安装在用户设备中任何一个应用程序的组件就可以潜在地启动服务。
如果我们想只在本地使用服务(其他应用程序不能使用它),就不需要也不应该提供任何Intent筛选器。没有了Intent筛选器,就必须使用一个有明确的服务类类名的Intent来启动这个服务。
另外,如果包含android:exported属性并将它设置为false,则可以确保服务对于我们的应用程序是私有的。即使服务提供了筛选器,这也是非常有效的。
当一个服务被启动时,它有一个独立于启动它的组件的生命周期,并且这个服务可以在后台永久运行,甚至在启动它的组件被销毁时。例如,这个服务应该在它的工作完成以后通过调用stopSelf()方法停掉,或者其他组件可以通过调用stopService()方法停止它。
应用程序的组件可以通过调用startService()方法启动这个服务,并将一个Intent通过服务的onStartCommand()方法传送给该服务。
注意:在默认情况下,服务在同一个进程中运行,因此,如果服务在与来自同一个应用程序的Activity交到时,服务执行密集或者阻塞操作,这个服务会降低Activity的性能。为了避免这种情况,应该在这个服务中启动一个新线程。
Android框架实现了两个服务类去创建服务。
ⅠService。这是所有服务的基基类。当继承这个类的时候,重要的是创建一个新线程去完成服务的工作,因为服务默认情况下使用的是应用程序的主线程,这会降低应用程序的什么一个Activity的性能。
ⅡIntentService。这是Service的一个子类,它使用worker线程去处理所有的启动请求。该服务需要做的所有事情在onHandleIntent()方法中实现。
因为大多数启动服务不需要同时处理多个请求(事实上,同时处理多个请求可以成为一个危险的多线程情景),如果使用IntentService来实现服务的话,可以使这个服务按一定顺序处理这些请求,这样可以提高代码的可靠性。
IntentService类的工作流程如下。
Ⅰ创建一个默认的worker线程,这个线程独立执行发送到服务的onStartCommand()方法中的intent,而这个intent对象是应用程序通过startService()方法发送到服务中的。
Ⅱ创建一个工作队列顺序,保存发送到这个服务的请求。
Ⅲ在所有启动请求都被处理以后停止这个服务的请求。
Ⅳ提供onBind()方法的默认实现,该方法只需要返回null就可以了。
Ⅴ提供onStartCommand()方法的默认实现,它用于发送intent到工作队列并且发送到onHandleIntent实现上。
下面来一下继承IntentService类的例子:
public class HelloIntentService extends IntentService{
public HelloIntentService(){
super("HelloIntentService ");
}
@Override
protected void onHandleIntent(Intent intent){
long endTime=System.currentTimeMillis()+5*1000;
while(System.currentTimeMillis()
synchronized(this){
try{
wait(endTime-System.currentTimeMillis());
}catch(Exception){
}
}
}
}
}
正如前面所述的那样,IntentService使得启动服务的实现变得非常简单。然而,如果需要服务执行多线程(而不是通过一个工作队列处理启动请求),那么可以实现Service类去处理每一个intent。
下面的代码是HelloService类的实现,该类执行的工作与上述例子一样,即对每一个启动请求,它使用一个worker线程执行任务和一次只执行一个请求的进程:
public class HelloService extends Service{
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extend Handler{
public ServiceHandler(Looper looper){
super(looper)
}
@Override
public void handleMessage(Message msg){
long engTime=System.currentTimeMillis()+5*1000;
while(System.currentTimeMillis()
synchronized(this){
try{
wait(endTime-System.currentTimeMillis());
}catch(Exception e){
}
}
}
}
}
@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
publicc IBinder onBind(Intent intent){
return null;
}
@Override
public void onDestroy(){
Toast.makeText(this,"service done",Toast.LENGTH_SHORT).show();
}
}
正如以上代码所见,使用继承Service的类比使用继承InrtentService的类多很多的事情。
我们可以从Activity或者其他应用程序组件入手,通过startService()方法并输入一个intent对象来启动服务。Android系统将会调用该服务的onStartCommand()方法并且将这个intent作为此方法的输入参数之一。
例如,在startService()方法中使用一个明确的intent,Activity可以启动前面章节所述的服务(HelloService):
Intent intent=new Intent(this,HelloService.class)';
startService(intent);
这里的startService()方法会立即返回,并且Android系统会调用服务的onStartCommand()方法。如果这个服务没有正在运行,则系统首先调用onCreate(),而后调用onStartCommand()。
一个已启动的服务必须管理它自己的生命周期,也就是说,系统不全停止或者销毁这个服务,除非它必须回收系统内存,并且该服务在onStartCommand()方法之后会继续运行。这样服务必须通过stopSelf()停止自己,或者其他组件通过调用stopService()停止该服务。
一旦用stopSelf()或者stopService()方法停止请求的时候,系统要尽可能快地销毁服务。
我们就用一个完整的实例来说明如何使用Service类实现服务。在服务中,通过一个按钮启动一个服务,还显示一个Toast通知。在界面中,我们用一个按钮来停止这个服务。完成这个需求需要完成的步骤如下所示。
Ⅰ使用Android Studio向导生成Android应用程序项目,应用程序名字为“LYJService”,包名为"liyuanjing.example.com.lyjservice"。
Ⅱ创建LYJService类继承自Service。并输入如下代码:
public class LYJService extends Service { private static final String TAG="LYJService"; public LYJService() { } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { Log.e(TAG,"Service is created"); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e(TAG,"Service is Started"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { Log.e(TAG,"Service is Destroied"); super.onDestroy(); } }
Ⅲ在工程的AndroidManifest.xml文件中声明服务。需要注意的是,服务和广播不一样,要想正常使用它,就必须在AndroidManifest.xml文件中声明它。这里为了方便使用,我们为它注册一个筛选器,该筛选器的action为”liyuanjing.example.com.lyjService“,声明服务的代码如下所示:
android:name=".LYJService"
android:enabled="true"
android:exported="false" >
android:name="liyuanjing.example.com.lyjService"/>
设置android:exported属性设置为false,意味着将此服务设置为私有。
Ⅳ修改默认生成的activity_main.xml布局,在中间增加两个按钮,名称分别叫”Start Service“和”Stop Service“,修改后的代码如下所示:
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
Ⅴ修改默认生成的”MainActivity.java“文件。这里我们需要在onCreate()回调方法中加载布局并实现两个按钮的点击事件以启动或者停下服务。修改后的代码如下所示:
public class MainActivity extends Activity { private final static String SERVICE_ACTION="liyuanjing.example.com.lyjService"; private Button start; private Button stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Intent intent=new Intent(SERVICE_ACTION); this.stop=(Button)findViewById(R.id.stop); this.start=(Button)findViewById(R.id.start); this.start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startService(intent);//启动服务 } }); this.stop.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { stopService(intent);//停止服务 } }); } }
Ⅵ运行应用程序。可以看到,并不是应用程序运行的时候服务就会被创建起来。服务会在第一次启动的时候创建,并且只创建一次。停止服务之后,服务便被销毁了。点击start,然后点击stop,得到如下日志:
被绑定的服务是允许应用程序组件通过调用bindService()绑定的服务,目的在于创建一个长期的连接。
当我们想要这个服务与其他应用程序中的Activity交互时,创建一个被绑定的服务将是一个不错的选择。
想要创建一个被绑定的服务,则必须实现onBind()方法去返回与服务通信的接口对象(IBinder)。其他应用程序组件可以调用bindService()去检索服务提供的接口并且开始在服务中调用这些方法。服务只为绑定它的应用程序组件服务而活,因此,当没有组件绑定到这个服务的时候,系统会销毁它(在系统通过onStartCommand()来启动服务时,我们不需要停止一个绑定服务)
为创建一个被绑定的服务,必须要做的第一件事情是定义接口,该接口指定客户如何与该服务通信。服务和客户端之间的接口必须是一个IBinder的实现,它是服务从onBind()回调方法中返回的。一旦这个客户端收到IBinder,它能通过该接口与服务交互。
多个客户端能立刻绑定这个服务。当客户端完成与服务交互的时候,它调用unBindService()去解绑。一旦没有客户绑定到这个服务上,系统就会销毁该服务。
实现一个被绑定的服务有很多方法,它比实现被启动的服务更为复杂。
被绑定的服务是Service类的一个实现,该类允许其他应用程序绑定到服务上并与之交互,为给服务提供绑定,我们必须实现onBind()回调方法。该方法返回一个IBinder对象,该对象定义了客户端可以用来与服务交互的编程接口。
客户端可以通过调用bindService()方法绑定到这个服务。当这样做的时候它必须提供ServiceConnection实现,该实现将监视与服务的连接。没有值的bindService()方法会立即返回,但当Android系统创建客户端与服务之间的连接时,我们会调用ServiceConnection中的onServiceConnected()方法来传递IBinder,而客户端可用IBinder来与服务通信。
多个客户端可立即连接到服务上,但是只有首个客户端被绑定的时候,系统才会调用服务的onBind()方法去检索IBinder。之后,系统交付相同的IBinder到绑定的任何额外客户端上,不用再次调用onBind()方法。
当最后的客户端从这个服务中解除绑定的时候,系统会销毁服务(除非该服务也是通过startService()方法启动的)。
当我们实现被绑定服务的时候,最重要的部分是定义onBind()回调方法返回的接口。定义服务的IBinder接口有几种方法,接下来将会介绍这些方法。
前面我们讲述了被绑定服务的特性以及生命周期,现在来看一个完整的实例,通过这个实例,我们将深入理解如何定义AIDL并使用它来实现服务,具体步骤如下所示。
Ⅰ使用上面的LYJService项目,在”liyuanjing.example.com.lyjService“下添加IRemoteService.aidl文件以定义远程接口,相关代码如下所示:
package liyuanjing.example.com.lyjservice; interface IRemoteService { int getPid(); }
当完成这个步骤时,我们的项目工程将会发生变化,Android工具将为我们编译出iRemoteService.java库。
Ⅱ创建一个名叫“LYJBoundService”的服务类,它继承自Service类。
这里我们需要完成以下两件事。
㈠实现Ⅰ中定义的接口。
㈡实现必要的服务生命周期回调函数,最重要的是需要实现onBind()接口
修改后的服务代码如下所示:
public class LYJBoundService extends Service { public LYJBoundService() { } //实现IRmeoteService中的getPid()接口 IRemoteService.Stub mRemoteBinder=new IRemoteService.Stub(){ @Override public int getPid() throws RemoteException { return android.os.Process.myPid(); } }; @Override public IBinder onBind(Intent intent) { //将mRemoteBinder通过此接口返回 return mRemoteBinder; } }
Ⅲ将LYJBoundService配置到AndroidManifest.xml文件中,并增加一个
android:name=".LYJBoundService"
android:enabled="true"
android:exported="true" >
android:name="liyuanjing.example.com.boundservice.remote"/>
Ⅳ修改默认的Activity使存在3个按钮,它们分别是“Bind Service”,"UnBind Service"和“Kill Service”,其中,只有当成功绑定到服务的时候,“UnBindService”按钮才变为用。修改后的代码如下所示:
布局文件:
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bind Service" />
android:id="@+id/unBind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UnBind Service" />
android:id="@+id/kill"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kill Service" />
MainActivity:
public class MainActivity extends Activity implements View.OnClickListener{ private final static String SERVICE_ACTION="liyuanjing.example.com.boundservice.remote"; private static final String TAG="MainActivity"; private Button bind; private Button unBind; private Button kill; private IRemoteService mIRemoteService; private ServiceConnection mServiceConnection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIRemoteService=IRemoteService.Stub.asInterface(service); unBind.setEnabled(true); kill.setEnabled(true); } @Override public void onServiceDisconnected(ComponentName name) { mIRemoteService=null; unBind.setEnabled(false); kill.setEnabled(false); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Intent intent=new Intent(SERVICE_ACTION); this.bind=(Button)findViewById(R.id.bind); this.unBind=(Button)findViewById(R.id.unBind); this.kill=(Button)findViewById(R.id.kill); this.bind.setOnClickListener(this); this.unBind.setOnClickListener(this); this.kill.setOnClickListener(this); } @Override public void onClick(View v) { int id=v.getId(); switch (id){ case R.id.bind: Intent intent=new Intent(SERVICE_ACTION); bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE); break; case R.id.unBind: if(mIRemoteService!=null){ try{ int pid=mIRemoteService.getPid(); Log.e(TAG,"Service_Pid = "+pid); unbindService(mServiceConnection); }catch(RemoteException e){ e.printStackTrace(); } } break; case R.id.kill: if(mIRemoteService!=null){ try{ int pid=mIRemoteService.getPid(); Log.e(TAG,"Service_Pid = "+pid); }catch(RemoteException e){ e.printStackTrace(); } } break; } } }
Ⅴ运行应用程序,先点击Bind Service,在点击unBind Service,最后点击Kill Service得到如下日志:
项目目录结构如下图: