Android Service

1.服务Service
Service是Android四大组件之一,它可以在后台执行长时间运行操作而没有用户界面。
Service可由其他应用组件(如Activity)启动,服务一旦被启动将在后台一直运行,即使启动服务的组件已销毁也不受影响。 组件也可以绑定到服务与之进行交互,甚至是执行进程间通信 。

Service在后台运行,如果需要实现和用户的交互,可通常通过通知栏或发送广播,UI去接收显示。

注意:
①服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
②服务在后台运行,并不是说服务会自动开启新线程,所有的代码都是默认运行在主线程中的,也就是说我们需要在服务内部手动创建子线程并执行具体的任务,否则就有可能出现主线程被阻塞的情况。
总之,服务是运行于宿主进程的主线程中的,既不创建自己的线程,也不运行在单独的进程中。

Service两种启动形式:
①启动状态
其他组件调用startService()启动服务时,服务处于启动状态。一旦启动,服务可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务。已启动的服务通常是执行单一操作,且不会将结果返回给调用方。
②绑定状态
其他组件调用bindService()绑定服务时,服务处于绑定状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信跨进程执行这些操作。多个组件可以同时绑定到一个服务,当全部取消绑定后,该服务即会被销毁。

无论哪种Service启动类型,都需要在AndroidManifest.xml中声明:

< /service>
exported:该服务能否被其他应用隐式调用,默认值是由service中有无intent-filter决定的,如果有intent-filter默认值为true,否则为false。为false的情况下,即使有intent-filter匹配也无法打开,即无法被其他应用隐式调用。
permission:权限声明
process:是否需要在单独的进程中运行。当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为remote,而后者的进程名称为App-packageName:remote。
isolatedProcess :设置true意味着服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
enabled:是否可以被系统实例化,默认为true,因为父标签也有enable属性,所以必须两个都为true的情况下服务才会被激活,否则不会激活。

2.启动服务
自定义一个服务继承Service类,也可以直接使用它的一个现有子类IntentService。
public class SimpleService extends Service {
// 绑定服务时才会调用(必须要实现的方法 )
@Override
public IBinder onBind(Intent intent) {
return null;
}

//只执行一次
@Override
public void onCreate() {
super.onCreate();
}

//每次调用startService()时都会被回调
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

//服务销毁时的回调
@Override
public void onDestroy() {
super.onDestroy();
}
}
SimpleService继承了Service类,并重写onBind方法,该方法是必须重写的,但如果是启动状态的服务,该方法无须实现,返回null即可,只有在绑定状态的情况下才需要实现该方法并返回一个IBinder的实现类。
关于几个方法说明:
①onBind()
当一个组件通过调用bindService()与服务绑定时系统将调用此方法。在onBind()方法的实现中必须返回一个IBinder接口的实现类,供客户端与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下可以直接返回null。
②onCreate()
首次创建服务时系统将调用onCreate()执行一次性设置程序(在调用onStartCommand或onBind之前)。如果服务已在运行则不会调用onCreate方法,该方法只调用一次。
③onStartCommand()
当组件调用startService()启动服务时系统将调用此方法。一旦执行此方法服务即会启动并可在后台无限期运行。如果实现此方法则需要在Service工作完成后,通过调用stopSelf()或stopService()来停止服务。(在绑定状态下,无需实现此方法。)
④onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

注意:自定义Service完成后,不要忘记在清单配置文件中声明。

Service启动状态的生命周期:
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;
}
}
}
使用startService(Intent intent)启动服务,仅传递一个Intent对象,在Intent对象中指定需要启动的服务。
使用startService()方法启动的服务,在服务的外部必须使用stopService()方法停止,或者在服务的内部调用stopSelf()方法停止当前服务。

注意:对于启动服务,一旦启动,它将与访问它的组件无任何关联,即使访问它的组件被销毁了,这个服务也一直运行下去,直到手动调用停止,服务才被销毁。

运行程序并多次调用startService()方法,最后调用stopService方法。Log打印如下:
onCreate
onStartCommand
onStartCommand
onStartCommand
onDestroy
从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:表示该方法的返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后会重建服务,并通过传递给服务的最后一个Intent调用onStartCommand()方法,此时Intent是有值的。
START_FLAG_RETRY:表示当该方法调用后一直没有返回值时会重新调用onStartCommand()方法。
③startId:表示当前服务的唯一ID,与stopSelf(int startId)配合使用,stopSelf(int)可以更安全地根据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处于绑定状态时,它就代表客户端-服务器接口中的服务器。

与启动服务不同的是,处于绑定状态的服务的生命周期通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行,也就是说宿主解除绑定后,绑定服务就会被销毁。

要实现绑定服务,必须提供一个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就派上用场了。
实际上Messenger的跨进程方式的底层实现就是AIDL,只不过Android系统把它封装成透明的Messenger了。
因此,如果想让服务同时处理多个请求应该使用AIDL。在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。

以上3种实现方式,可以根据需求自由选择。
这里重点讲同一进程间的service,因此只涉及扩展Binder,至于Messager和AIDL会专门讲解。
  
自定义Service:
public class LocalService extends Service{
private int count;
private boolean quit;
private Thread thread;
private LocalBinder binder = new LocalBinder();
//自定义扩展Binder类
public class LocalBinder extends Binder {
// 声明一个方法,提供给客户端调用
LocalService getService() {
// 返回当前对象LocalService,这样就可在客户端调用Service的公共方法了
return LocalService.this;
}
}

//把Binder类返回给客户端
@Override
public IBinder onBind(Intent intent) {
return binder;
}

@Override
public void onCreate() {
super.onCreate();
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();
}
}
自定义LocalService类继承Service,在该类中创建内部类LocalBinder继承自Binder类,LocalBinder中声明getService()方法,客户端访问该方法可获取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) {
bindService(intent, conn, Service.BIND_AUTO_CREATE);
}
});

// 解除绑定
btnUnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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实现类对象,通过该对象便可以调用binder内自定义的方法获取LocalService实例对象,进而调用服务端的公共方法。
②onServiceDisconnected(ComponentName name)
Android系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。
注意:当客户端取消绑定时,系统“绝对不会”调用该方法。

在onServiceConnected()被回调前,需要先把Activity绑定到服务LocalService上,绑定服务使用bindService(),解绑服务使用unbindService()方法:
①bindService(Intent service, ServiceConnection conn, int flags)
绑定服务,其中Intent是要绑定的服务的意图;而ServiceConnection代表与服务的连接;flags则是指定绑定时是否自动创建Service,0代表不自动创建,BIND_AUTO_CREATE则代表自动创建。传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
②unbindService(ServiceConnection conn)
执行解除绑定的操作,其中ServiceConnection代表与服务的连接。

客户端调用bindService()绑定到LocalService后,系统就会回调ServiceConnection的onServiceConnected()方法,在该方法里就可以获取到LocalService实例对象mService,之后就可以调用LocalService的公共方法了。
最后别忘了在清单文件中声明该Service。

运行程序,点击绑定服务并多次点击绑定服务,接着多次调用LocalService中的getCount()获取数据,最后调用解除绑定的方法移除服务:
在这里插入图片描述
当第一次点击绑定服务时,LocalService服务端的onCreate()、onBind()依次被调用,此时客户端的onServiceConnected()被调用并返回LocalBinder对象,接着调用LocalBinder的getService()返回LocalService实例对象,此时客户端便持有了LocalService的实例对象,就可以任意调用LocalService类中声明的公共方法了。

多次调用bindService()绑定服务,onBind()方法只在第一次bindService()时被回调了一次,即只有第一次调用bindService时才会回调onBind()方法。接着点击获取服务端的数据,从Log中看出点击了3次,通过getCount()获取了服务端的3个不同数据。最后点击解除绑定,此时LocalService的onUnBind、onDestroy方法依次被回调,并且多次绑定只需一次解绑即可。

说明绑定状态下的Service生命周期方法的调用依次为onCreate()、onBind()、onUnBind()、onDestroy()。

总结绑定服务使用步骤:
①自定义服务类继承Service,在类中创建一个实现了IBinder接口的实例对象,并提供公共方法给客户端调用。
②从onBind()方法返回此Binder实例。
③客户端中,在onServiceConnected()回调方法中接收服务端的Binder,并使用提供的方法调用绑定服务。
注意:此方式只有在客户端和服务位于同一应用和进程内才有效,如对于需要将Activity绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。另一点之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其API。服务和客户端还必须在同一进程内,因为此方式不执行任何跨进程编码。

关于绑定服务的注意点:
①多个客户端可同时绑定到一个服务。不过只有在第一个客户端绑定时,系统才会调用服务的 onBind()方法来检索IBinder。系统随后无需再次调用onBind(),便可将同一IBinder传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService()也启动了该服务)。
②通常应该在客户端生命周期的引入和退出时设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况:
1)如果只需要在Activity可见时与服务交互,则应在onStart()绑定,在onStop()取消绑定。
2)如果希望Activity在后台停止运行状态下仍可接收响应,则可在onCreate()绑定,在onDestroy()取消绑定。注意,这意味着Activity在整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当提高该进程的权重时,系统很可能会终止该进程。
③切勿在Activity的onResume()和onPause()期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑不合理。此外,如果应用内的多个Activity绑定到同一服务,并且其中两个Activity之间发生了转换,则如果当前Activity在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在Activity的onResume()和onPause()中。
④应该始终捕获DeadObjectException异常,该异常是在连接中断时引发的,表示Service对象已销毁,这是远程方法引发的唯一异常,
⑤应用组件可通过调用bindService()绑定到服务,Android系统随后调用服务的onBind()方法,该方法返回用于与服务交互的IBinder,而该绑定是异步执行的。

4.启动服务与绑定服务间的转换问题
虽然服务的状态有启动和绑定两种,但实际上一个服务可以同时是这两种状态,也就是说它既可以是启动服务(以无限期运行),也可以是绑定服务。
需要注意的是Android系统仅会为一个Service创建一个实例对象,所以不管是启动服务还是绑定服务,操作的都是同一个Service实例,而且由于绑定服务和启动服务执行顺序问题会出现以下两种情况:
①先绑定服务后启动服务
绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,直到收到调用停止服务或内存不足时才会销毁该服务。
②先启动服务后绑定服务
启动服务并不会转为绑定服务,但是依然会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有Context调用了stopService()或服务本身调用了stopSelf()方法或内存不足时才会销毁服务。

以上两种情况显示出启动服务的优先级确实比绑定服务高一些。不过无论Service处于启动状态还是绑定状态或处于启动且绑定状态,都可以通过调用Intent来使用服务(即使此服务来自另一应用)。 当然,也可以通过清单文件将服务声明为私有服务来阻止其他应用访问。

如果既使用了startService(),又使用了bindService(),应该怎样销毁服务呢?这时会发现,不管是单独使用stopService()还是unbindService(),Service都不会被销毁,必要两个方法都使用一下Service才会被销毁。也就是说stopService只会让Service停止,unbindService只会让Service和Activity解除关联,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。

注意:由于服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定), 这意味着如果在服务中执行任何耗时事件或阻止性操作(例如 MP3 播放或联网)时,应该在服务内创建新线程来完成这项工作,即服务内的耗时操作应该另起线程执行。只有通过使用单独的线程才可以降低发生ANR错误的风险,这样应用的主线程才能专注于用户与Activity之间的交互,以达到更好的用户体验。

5.IntentService
IntentService本质是一种特殊的Service,它继承自Service,并且本身是一个抽象类。
IntentService的优势:
①用于在后台执行耗时的异步任务,当任务完成后会自动停止。
②拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务。
③内部通过HandlerThread和Handler实现异步操作。
④创建IntentService时只需实现构造方法和onHandlerIntent()方法,onHandlerIntent()为异步方法,可以执行耗时操作。
IntentService的具体使用方法和源码分析会在下一篇专门讲解。

6.前台服务
一般Service都是在后台运行的,因此它的系统优先级比较低,当系统出现内存不足时就有可能会回收掉正在后台运行的Service。如果希望Service可以一直保持运行状态而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。

前台服务可以给用户提供界面上的操作,每个前台服务都必须要在通知栏显示一个通知,用户可以感知到app的前台服务在运行。这个通知默认是不能移除的。服务停止后,通知会被系统移除。当用户不需要直接操作app,app需要给用户一个状态显示的时候,可以使用前台服务。

使用前台服务:
①添加前台权限
< uses-permission android:name=“android.permission.FOREGROUND_SERVICE”/>
②在Service中开启通知
public class MyService extends Service {
public void onCreate() {
initNotification();
……
}
public void onDestory() {
stopForeground(true);
……
}
……
private void initNotification() {
//点击通知要跳转的界面
Intent intent = new Intent(this, XxxActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this , 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//8.0开始,需要注册通知通道
String channelName = “……”;
String channelId = “……”;
NotificationManager notificationManager = (NotificationManager)getSystemService( Context.NOTIFICATION_SERVICE);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notifixationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificatio nChannel(notificationChannel);
}
Notification notification = new Notification.Builder(this, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(“title”)
.setContentText(“content”)
.setContentIntent(pendingIntent)
.setAutoCancel(false)
.setOngoing(true)
.build();
startForeground(1, notification);
}
}
③启动前台服务
startForegroundService(new Intent(this, MyService.class);
首先创建一个PendingIntent,它会会被分配给Notification作为点击通知后的跳转动作。
然后使用NotificationManager创建了一个NotificationChannel,用Notification.Builder配置并创建一个Notification,配置标题、内容文字、图标等。
最后使用startForeground(1, notification);启动前台服务。
注意:在Service中使用stopForeground(true)方法可以停止前台服务,但是不能退出整个服务。参数boolean表示是否取消掉前台服务的通知,false表示保留通知。

注意:
①8.0适配:通知需要加上NotificationChannel,开启前台服务的方式startForegroundService()。
②9.0适配:manifest.xml文件中需要增加权限:FOREGROUND_SERVICE。

总结:
①前台服务适用于用户知晓和需要与用户交互的任务,系统对其优先级更高,通常用于长时间运行的任务。
②后台服务适用于不需要用户交互且无需用户感知的任务,系统对其优先级较低,可能会在资源紧张时终止其运行。
注意,从 Android 8.0(API 级别 26)开始引入了后台服务限制,即后台服务在后台执行任务时受到了一些限制。在后台执行任务时,应使用适当的后台执行方式,如JobScheduler、WorkManager或前台服务来确保任务的执行和系统资源的合理利用。

7.服务的生命周期
在这里插入图片描述
Service的整个生命周期从onCreate()开始,到onDestroy()返回时结束。服务在onCreate()中完成初始设置,并在onDestroy()中释放所有资源。无论服务是通过startService()还是bindService()创建,都会为服务调用onCreate()和onDestroy()方法。
服务的有效生命周期从调用onStartCommand() 或onBind()方法开始。对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

8.Android 5.0以上的隐式启动问题
显示启动:
Intent intent = new Intent( this,MyService.class);
startService(intent);

隐式启动:
需要设置一个Action,可以把Action的名字设置成Service的全路径名字,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之后:
在这里插入图片描述
可以看出如果启动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();
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 无法操作了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值