1.service(服务) Service是一种可以在后台执行长时间运行操作而没有用户界面的应用组件。 服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件已销毁也不受影响。 此外,组件可以绑定到服务,与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
Service基本上分为两种形式: ①启动状态 当应用组件(如activity)通过调用startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。 ②绑定状态 当应用组件通过调用bindService()绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
无论哪种Service启动类型,都需要在AndroidManifest.xml中声明,其格式如下: ?<service android:enabled=[“true” | “false”] android:exported=[“true” | “false”] android:isolatedProcess=[“true” | “false”] android:name=“string” android:permission=“string” android:process=“string” > . . . < /service>
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的情况下服务才会被激活,否则不会激活。
2.Service启动服务 ?要创建服务,必须创建 Service 的子类,或使用它的一个现有子类,如IntentService。 ? public class SimpleService extends Service { // 绑定服务时才会调用(必须要实现的方法 ) @Override public IBinder onBind(Intent intent) { return null; }
//只执行一次 @Override public void onCreate() { System.out.println(“onCreate invoke”); super.onCreate(); }
//每次通过startService()方法启动Service时都会被回调 @Override public int onStartCommand(Intent intent, int flags, int startId) { System.out.println(“onStartCommand invoke”); return super.onStartCommand(intent, flags, startId); }
// 服务销毁时的回调 @Override public void onDestroy() { System.out.println(“onDestroy invoke”); super.onDestroy(); } } SimpleService继承了Service类,并重写了onBind方法,该方法是必须重写的,但是由于此时是启动状态的服务,则该方法无须实现,返回null即可,只有在绑定状态的情况下才需要实现该方法并返回一个IBinder的实现类。接着重写了onCreate、onStartCommand、onDestroy三个主要的生命周期方法,关于这几个方法说明如下: ①onBind() ?当另一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。 ②onCreate() 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用onStartCommand或onBind之前)。 如果服务已在运行,则不会调用此方法,该方法只调用一次。 ③onStartCommand() 当组件通过调用startService()请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法。) ④onDestroy() 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。
我们通过Demo测试一下Service启动状态方法的调用顺序,MainActivity代码如下: public class MainActivity extends Activity implements View.OnClickListener {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
@Override public void onClick(View v) { Intent it=new Intent(this, SimpleService.class); switch (v.getId()){ case R.id.startService: startService(it); break; case R.id.stopService: stopService(it); break; } } } 最后,千万记得在清单配置文件中声明Service。
从代码看出,启动服务使用startService(Intent intent)方法,仅需要传递一个Intent对象即可,在Intent对象中指定需要启动的服务。而使用startService()方法启动的服务,在服务的外部必须使用stopService()方法停止,在服务的内部可以调用stopSelf()方法停止当前服务。 注意:对于启动服务,一旦启动将与访问它的组件无任何关联,即使访问它的组件被销毁了,这个服务也一直运行下去,直到手动调用停止,服务才被销毁。至于onBind方法,只有在绑定服务时才会起作用,在启动状态下,无需关注此方法。 我们运行程序并多次调用startService方法,最后调用stopService方法。Log截图如下: 从Log可以看出,第一次调用startService方法时,onCreate()、onStartCommand()将依次被调用,而多次调用startService时,只有onStartCommand()被调用。最后我们调用stopService方法停止服务时,onDestory方法被回调(多次startService,只需一次stopService即可退出服务),这就是启动状态下Service的执行周期。
接下来分析一下onStartCommand(Intent intent, int flags, int startId),这个方法有3个传入参数,它们的含义如下: ①intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service。 ②flags:启动请求时是否有额外数据,可选值有 0、START_FLAG_REDELIVERY、START_FLAG_RETRY。 0:代表没有。 START_FLAG_REDELIVERY:代表onStartCommand方法的返回值为 START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,会重建服务,并通过传递给服务的最后一个Intent调用 onStartCommand(),此时Intent是有值的。 START_FLAG_RETRY:代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。 ③startId :指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。
实际上onStartCommand()的返回值int类型才是最最值得注意的,它有三种可选值,分别是START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,具体含义如下: ①START_STICKY 当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(),但其中的Intent将是null,除非有挂起的Intent,如pendingintent。这个状态下比较适用于不执行命令,但无限期运行并等待作业的媒体播放器或类似服务。 ②START_NOT_STICKY 当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service,除非程序中再次调用startService启动此Service。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 ③START_REDELIVER_INTENT 当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
总结一下:由于每次启动服务(调用startService)时,onStartCommand()都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand()中处理事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制。
3.Service绑定服务 ?绑定服务是Service的另一种变形,当Service处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件绑定到服务时,组件可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即IPC)。 与启动服务不同的是,绑定服务的生命周期通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如Activity)解除绑定后,绑定服务就会被销毁。 要实现绑定服务,我们必须提供一个IBinder接口的实现类,该类用来提供客户端与服务进行交互的编程接口,IBinder接口可以通过三种方法定义: ①扩展Binder类 如果服务是提供给自有应用专用的,并且Service与客户端在相同的进程中运行(常见情况),则应通过扩展Binder类并从onBind() 返回它的一个实例来创建接口。客户端收到Binder后,可利用它直接访问Binder实现中以及Service 中可用的公共方法。 如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。 不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。 ②使用 Messenger ?Messenger可以翻译为信使,通过它可以在不同的进程中传递Message对象,在Message中可以存放我们需要传递的数据,然后在进程间传递。 ?如果需要让接口跨不同的进程工作,则可使用Messenger为服务创建接口,客户端就可利用 Message对象向服务发送命令。同时客户端也可定义自有Messenger,以便服务回传消息。这是执行进程间通信 的最简单方法。因为Messenger会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。 ③使用 AIDL ?由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger的跨进程方式其底层实现就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。 ?使用AIDL必须创建一个定义编程接口的.aidl文件。Android SDK工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。
以上3种实现方式,我们可以根据需求自由的选择,但需要注意的是大多数应用不会使用AIDL来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。 这里重点讲同一进程间的service,因此只涉及扩展Binder,至于Messager和AIDL会专门讲解。
绑定服务使用步骤如下: ①创建Service服务端,继承自Service,并在类中创建一个实现IBinder接口的实例对象并提供公共方法给客户端调用。 ②从onBind() 回调方法返回此Binder实例。 ③在客户端中,从onServiceConnected() 回调方法接收Binder,并使用提供的方法调用绑定服务。 注意:此方式只有在客户端和服务位于同一应用和进程内才有效,如对于需要将Activity绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。另一点之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其API。服务和客户端还必须在同一进程内,因为此方式不执行任何跨进程编码。 ?? 以下是一个扩展Binder类的实例,先看看Service端的实现BindService.java: public class LocalService extends Service{ 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类返回给客户端 @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(); }
//service中的公共方法 public int getCount(){ return count; }
//解除绑定时调用 @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的实现: public class BindActivity extends Activity { private ServiceConnection conn; private LocalService mService;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bind); //创建绑定对象 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) 系统会调用该方法以传递service中onBind() 方法返回的IBinder。其中第二个参数service便是服务端返回的IBinder实现类对象,通过该对象我们便可以调用方法获取LocalService实例对象,进而调用服务端的公共方法。而ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,如包名,组件描述等信息,较少使用该参数。 ②onServiceDisconnected(ComponentName name) Android系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。 注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
在onServiceConnected()被回调前,我们还需先把当前Activity绑定到服务LocalService上,绑定服务是通过通过bindService()方法,解绑服务则使用unbindService()方法,这两个方法解析如下: ①bindService(Intent service, ServiceConnection conn, int flags) 该方法执行绑定服务操作,其中Intent是我们要绑定的服务的意图;而ServiceConnection代表与服务的连接;flags则是指定绑定时是否自动创建Service,0代表不自动创建,BIND_AUTO_CREATE则代表自动创建。 ②unbindService(ServiceConnection conn) 该方法执行解除绑定的操作,其中ServiceConnection代表与服务的连接。
Activity通过bindService()绑定到LocalService后,ServiceConnection#onServiceConnected()便会被回调,并可以获取到LocalService实例对象mService,之后我们就可以调用LocalService服务端的公共方法了,最后别忘了在清单文件中声明该Service。
我们运行程序,点击绑定服务并多次点击绑定服务,接着多次调用LocalService中的getCount()获取数据,最后调用解除绑定的方法移除服务,其结果如下: 当我们第一次点击绑定服务时,LocalService服务端的onCreate()、onBind()会依次被调用,此时客户端的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。
关于绑定服务的注意点: ①多个客户端可同时绑定到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索IBinder。系统随后无需再次调用onBind(),便可将同一IBinder传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService() 也启动了该服务)。 ②通常情况下我们应该在客户端生命周期的引入 和退出时设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况: a.如果只需要在Activity可见时与服务交互,则应在onStart()期间绑定,在onStop()期间取消绑定。 b.如果希望Activity在后台停止运行状态下仍可接收响应,则可在onCreate()期间绑定,在onDestroy()期间取消绑定。需要注意的是,这意味着Activity在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当提高该进程的权重时,系统很可能会终止该进程。 ③通常情况下,切勿在 Activity 的onResume()和onPause()期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。此外,如果应用内的多个 Activity 绑定到同一服务,并且其中两个Activity之间发生了转换,则如果当前Activity在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在Activity的onResume()和onPause()中。 ④我们应该始终捕获DeadObjectException异常,该异常是在连接中断时引发的,表示Service对象已销毁,这是远程方法引发的唯一异常,DeadObjectException继承自RemoteException,因此我们也可以捕获RemoteException异常。 ⑤应用组件(客户端)可通过调用 bindService() 绑定到服务,Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder,而该绑定是异步执行的。
4.关于启动服务与绑定服务间的转换问题 ?虽然服务的状态有启动和绑定两种,但实际上一个服务可以同时是这两种状态,也就是说,它既可以是启动服务(以无限期运行),也可以是绑定服务。 ?需要注意的是Android系统仅会为一个Service创建一个实例对象,所以不管是启动服务还是绑定服务,操作的是同一个Service实例,而且由于绑定服务或者启动服务执行顺序问题将会出现以下两种情况: ①先绑定服务后启动服务 ?如果Service实例先以绑定状态运行,然后再以启动状态运行,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,直到收到调用停止服务或者内存不足时才会销毁该服务。 ②先启动服务后绑定服务 ?如果当前Service实例先以启动状态运行,然后再以绑定状态运行,当前启动服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有Context调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。
以上两种情况显示出启动服务的优先级确实比绑定服务高一些。不过无论Service是处于启动状态还是绑定状态,或处于启动并且绑定状态,我们都可以通过调用Intent来使用服务(即使此服务来自另一应用)。 当然,我们也可以通过清单文件将服务声明为私有服务,阻止其他应用访问。最后这里有点需要特殊说明一下的,由于服务在其托管进程的主线程中运行(UI线程),它既不创建自己的线程,也不在单独的进程中运行(除非另行指定), 这意味着,如果服务将执行任何耗时事件或阻止性操作(例如 MP3 播放或联网)时,则应在服务内创建新线程来完成这项工作,简而言之,耗时操作应该另起线程执行。只有通过使用单独的线程,才可以降低发生“应用无响应”(ANR) 错误的风险,这样应用的主线程才能专注于用户与Activity之间的交互,以达到更好的用户体验。
5.IntentService IntentService本质是一种特殊的Service,继承自Service,并且本身就是一个抽象类。 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止。 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务。 它内部通过HandlerThread和Handler实现异步操作。 创建IntentService时,只需实现onHandlerIntent和构造方法,onHandlerIntent为异步方法,可以执行耗时操作。 IntentService的具体使用方法和源码分析会在下一篇专门讲解。
6.服务Service与线程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异步下载。在Android官方看来也是如此,所以官网提供了一个Thread与Service的结合来方便我们执行后台耗时任务,它就是IntentService,当然 IntentService并不适用于所有的场景,但它的优点是使用方便、代码简洁,不需要我们创建Service实例并同时也创建线程,某些场景下还是非常赞的!由于IntentService是单个worker thread,所以任务需要排队,因此不适合大多数的多任务情况。
综上,Service和Thread没有一点关系。
7.管理服务生命周期 ?关于Service生命周期方法的执行顺序,前面我们已分析得差不多了,这里重新给出一张执行的流程图(出自Android官网) 服务的整个生命周期从调用onCreate() 开始,到 onDestroy() 返回时结束。服务在onCreate()中完成初始设置,并在onDestroy() 中释放所有剩余资源。无论服务是通过startService() 还是 bindService() 创建,都会为服务调用onCreate() 和onDestroy() 方法。 服务的有效生命周期从调用onStartCommand() 或onBind()方法开始。对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。
我们还必须考虑另外一种情况,也就是启动服务与绑定服务的结合体,例如,可以通过使用 Intent(标识要播放的音乐)调用startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity可以通过调用bindService()绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则stopService() 或stopSelf()不会真正停止服务。
8.Android 5.0以上的隐式启动问题 显示启动: Intent intent = new Intent(this,MyService.class); startService(intent);
隐式启动: 需要设置一个Action,我们可以把Action的名字设置成Service的全路径名字,android:exported默认为true。 final Intent serviceIntent=new Intent(); serviceIntent.setAction(“com.test.MyService”); startService(serviceIntent);
如果在同一个应用中,两者都可以用。在不同应用时,只能用隐式启动。
Android 5.0之后google出于安全的角度禁止了隐式声明Intent来启动Service。如果使用隐式启动Service,会报没有指明Intent的错误,如下: 主要原因我们可以从源码中找到,这里看看Android 4.4的ContextImpl源码中的validateServiceIntent(Intent service),可知如果启动service的intent的component和package都为空并且版本大于KITKAT的时候只是报出一个警报,告诉开发者隐式声明intent去启动Service是不安全的。 而在android5.0之后呢?比如android6.0的源码如下: 从源码可以看出如果启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常,该异常与之前隐式启动所报的异常时一致的。那么该如何解决呢? 解决方式: ①设置Action和packageName: final Intent serviceIntent=new Intent(); serviceIntent.setAction(“com.test.MYService”); serviceIntent.setPackage(getPackageName());//设置应用的包名 startService(serviceIntent); ②将隐式启动转换为显示启动: public static Intent getExplicitIntent(Context context, Intent implicitIntent) { //检索所有满足implicitIntent的service PackageManager pm = context.getPackageManager(); List< ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); //确保只有一个符合要求 if (resolveInfo == null || resolveInfo.size() !=1) { return null; } //得到组件信息,并创建组件 ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); Intent explicitIntent = new Intent(implicitIntent); explicitIntent.setComponent(component); return explicitIntent; } 调用方式如下: Intent mIntent=new Intent();//辅助Intent mIntent.setAction(“com.test.MyService”); final Intent serviceIntent=new Intent(getExplicitIntent(this,mIntent)); startService(serviceIntent);
9.如何保证服务不被杀死 实际上这种做法并不推荐,但是既然谈到了,我们这里就给出一些实现思路吧。主要分以下3种情况: ①因内存资源不足而杀死Service 这种情况比较容易处理,可将onStartCommand() 的返回值设为 START_STICKY或START_REDELIVER_INTENT ,该值表示服务在内存资源紧张时被杀死后,在内存资源足够时再恢复。也可将Service设置为前台服务,这样就有比较高的优先级,在内存资源紧张时也不会被杀掉。 简单代码如下: //返回 START_STICKY或START_REDELIVER_INTENT @Override public int onStartCommand(Intent intent, int flags, int startId) { // return super.onStartCommand(intent, flags, startId); return START_STICKY; } ②用户通过 settings -> Apps -> Running -> Stop 方式杀死Service 这种情况是用户手动干预的,不过幸运的是这个过程会执行Service的生命周期,也就是onDestory()会被调用,这时便可以在 onDestory()中发送广播重新启动。这样杀死服务后会立即启动。这种方案是行得通的,但为程序更健全,我们可开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。 这里给出第一种方式的代码实现如下: public class ServiceKilledByAppStop extends Service{ private BroadcastReceiver mReceiver; private IntentFilter mIF;
@Override public IBinder onBind(Intent intent) { return null; }
@Override public void onCreate() { super.onCreate(); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Intent a = new Intent(ServiceKilledByAppStop.this, ServiceKilledByAppStop.class); startService(a); } }; mIF = new IntentFilter(); mIF.addAction(“com.restart.service”); //注册广播接者 registerReceiver(mReceiver, mIF); }
@Override public void onDestroy() { super.onDestroy(); Intent intent = new Intent(); intent.setAction(“com.restart.service”); //发送广播 sendBroadcast(intent);
unregisterReceiver(mReceiver); } } ③用户通过 settings -> Apps -> Downloaded -> Force Stop 方式强制性杀死Service 这种方式就比较悲剧了,因为是直接kill运行程序的,不会走生命周期的过程,前面两种情况只要是执行Force Stop ,也就废了。也就是说这种情况下无法让服务重启,或者只能去设置让Force Stop 无法操作了。
|