Service全部内容基本会在本篇涉及到,我们将围绕以下主要知识点进行分析:
Service简单概述
Service在清单文件中的声明
Service启动服务实现方式及其详解
Service绑定服务的三种实现方式
关于启动服务与绑定服务间的转换问题
前台服务以及通知发送
服务Service与线程Thread的区别
管理服务生命周期的要点
Android 5.0以上的隐式启动问题及其解决方案
保证服务不被杀死的实现思路
1.Service简单概述
Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行,Service基本上分为两种形式:
启动状态
当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。
绑定状态
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2.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 的情况下服务才会被激活,否则不会激活。
ok~,关于Service在清单文件的声明我们先了解这些就行,接下来分别针对Service启动服务和绑定服务进行详细分析
3.Service启动服务
首先要创建服务,必须创建 Service 的子类(或使用它的一个现有子类如IntentService)。在实现中,我们需要重写一些回调方法,以处理服务生命周期的某些关键过程,下面我们通过简单案例来分析需要重写的回调方法有哪些?
package com.zejian.ipctest.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;/**
* Created by zejian
* Time 2016/9/29.
* Description:service simple demo
*/
public class SimpleService extends Service { /**
* 绑定服务时才会调用
* 必须要实现的方法
* @param intent
* @return
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
} /**
* 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。
* 如果服务已在运行,则不会调用此方法。该方法只被调用一次
*/
@Override
public void onCreate() {
System.out.println("onCreate invoke");
super.onCreate();
} /**
* 每次通过startService()方法启动Service时都会被回调。
* @param intent
* @param flags
* @param startId
* @return
*/
@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() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。
onCreate()
首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或onBind() 之前)。如果服务已在运行,则不会调用此方法,该方法只调用一次
onStartCommand()
当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法。)
onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。
我们通过Demo测试一下Service启动状态方法的调用顺序,MainActivity代码如下:
package com.zejian.ipctest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;import com.zejian.ipctest.service.SimpleService;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button startBtn;
private Button stopBtn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startBtn= (Button) findViewById(R.id.startService);
stopBtn= (Button) findViewById(R.id.stopService);
startBtn.setOnClickListener(this);
assert stopBtn != null;
stopBtn.setOnClickListener(this);
} @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(声明方式跟Activity相似):
<manifest ... >
...
<application ... >
<service android:name=".service.SimpleService" />
...
</application>
</manifest>
从代码看出,启动服务使用startService(Intent intent)方法,仅需要传递一个Intent对象即可,在Intent对象中指定需要启动的服务。而使用startService()方法启动的服务,在服务的外部,必须使用stopService()方法停止,在服务的内部可以调用stopSelf()方法停止当前服务。如果使用startService()或者stopSelf()方法请求停止服务,系统会就会尽快销毁这个服务。值得注意的是对于启动服务,一旦启动将与访问它的组件无任何关联,即使访问它的组件被销毁了,这个服务也一直运行下去,直到手动调用停止服务才被销毁,至于onBind方法,只有在绑定服务时才会起作用,在启动状态下,无需关注此方法,ok~,我们运行程序并多次调用startService方法,最后调用stopService方法。Log截图如下:
从Log可以看出,第一次调用startService方法时,onCreate方法、onStartCommand方法将依次被调用,而多次调用startService时,只有onStartCommand方法被调用,最后我们调用stopService方法停止服务时onDestory方法被回调,这就是启动状态下Service的执行周期。接着我们重新回过头来进一步分析onStartCommand(Intent intent, int flags, int startId),这个方法有3个传入参数,它们的含义如下:
onStartCommand(Intent intent, int flags, int startId)
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
该flag代表当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返回值,以达到对程序更友好的控制。好~,以上便是Service在启动状态下的分析,接着我们在来看看绑定状态的Service又是如何处理的?
4.Service绑定服务
绑定服务是Service的另一种变形,当Service处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件(如 Activity)绑定到服务时(有时我们可能需要从Activity组建中去调用Service中的方法,此时Activity以绑定的方式挂靠到Service后,我们就可以轻松地方法到Service中的指定方法),组件(如Activity)可以向Service(也就是服务端)发送请求,或者调用Service(服务端)的方法,此时被绑定的Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即IPC,这个后面再单独分析)。与启动服务不同的是绑定服务的生命周期通常只在为其他应用组件(如Activity)服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如Activity)解除绑定后,绑定服务就会被销毁。那么在提供绑定的服务时,该如何实现呢?实际上我们必须提供一个 IBinder接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:
扩展 Binder 类
如果服务是提供给自有应用专用的,并且Service(服务端)与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中以及Service 中可用的公共方法。如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。 不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。
使用 Messenger
Messenger可以翻译为信使,通过它可以在不同的进程中共传递Message对象(Handler中的Messager,因此 Handler 是 Messenger 的基础),在Message中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说Messenger是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。
使用 AIDL
由于Messenger是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到Service(服务端),Service仍然只能一个个处理,这也就是Messenger跨进程通信的缺点了,因此如果有大量并发请求,Messenger就会显得力不从心了,这时AIDL(Android 接口定义语言)就派上用场了, 但实际上Messenger 的跨进程方式其底层实现 就是AIDL,只不过android系统帮我们封装成透明的Messenger罢了 。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用AIDL必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。
以上3种实现方式,我们可以根据需求自由的选择,但需要注意的是大多数应用“都不会”使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本篇中也不打算阐述如何使用AIDL(后面会另开一篇分析AIDL),接下来我们分别针对扩展 Binder 类和Messenger的使用进行分析。
4.1 扩展 Binder 类
前面描述过,如果我们的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。其使用开发步骤如下
1.创建BindService服务端,继承自Service并在类中,创建一个实现IBinder 接口的实例对象并提供公共方法给客户端调用
2.从 onBind() 回调方法返回此 Binder 实例。
3.在客户端中,从 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~,以上便是同一应用同一进程中客户端与服务端的绑定回调方式。