Android 有四大组件,这是每一个 Android 开发者一开始就知道的,也是 Android 基础中的重中之重,是构成 Android 的基石,但相信并不是每一个人都对服务非常的了解,今天我们一起来探究 Android 四大组件之一 Service(服务),对服务进行详细的剖析,来看一看这个运行在后台的默默无闻的工作者
一、服务的概念
服务(Service)是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件,服务可以由其他应用组件启动,而且即使用户切换到其他应用,服务仍然能够在后台继续运行,此外组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信(IPC),它非常适合去执行那些不需要和用户进行交互,长期运行在后台的一些任务,服务的运行不依赖与任何用户界面,即使用户切换到另外其他的程序,或者切换到后台,服务仍然能够正常运行,例如服务可以处理网络事务、播放音乐、执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行
- 注意:服务本身并不是运行在一个独立的进程当中,依赖于创建服务时所在的应用程序进程,当服务程序被杀掉时,所依赖进程的服务也会相应的停止运行
- 服务不是线程,他不是一种来完成主线程之外工作的手段(以避免应用程序没有响应错误)
二、服务的基本用法
我们来新建一个 ServiceTest 项目,进行服务相关的演示
1. 首先我们来创建一个 MyService
/**
* 定义一个 Service
*/
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
我们可以看到,MyService 是继承自 Service 类的,说明它是一个服务,我们看到这个 MyService 里面基本什么也没有,有一个空的构造方法,还有一个 onBind() 方法,打开 Service 的源码我们可以看到 onBind() 是一个必须在子类实现的抽象方法:
@Nullable
public abstract IBinder onBind(Intent intent);
接下来我们要进行逻辑的编写,就必须调用 Service 类中的其他方法,这里我们先重写父类的 onCreate()、onStartCommand、onDestroy() 方法如下:
/**
* 定义一个 Service
*/
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 在服务第一次创建的时候调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
/**
* 每次服务启动的时候调用
*
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用
* 在这里可以回收不在使用的资源
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
这 3 个方法是服务中最常用的方法,其中 onCreate() 方法在服务创建的时候调用,onStartCommand() 方法服务每次启动的时候都会调用,服务销毁的时候调用 onDestroy() 方法,通常情况下,我们如果一旦服务启动就需要去执行某些操作,逻辑就可以写在 onStartCommand() 方法中,同时我们还要注意在 onDestroy() 方法中释放不需要的资源,另外还需要注意,服务时需要在 AndroidManifest.xml 文件中注册的,只是我们的 Android Studio 太智能了,已经自动帮我们注册好了如下,但我们还是要知道:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.qiudengjiao.servicetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
</application>
</manifest>
到这里,我们的服务就算定义完了
2. 启动和停止服务
和 Activity 一样,服务的启动和停止也是借助 Intent 来实现的,接下来我们来开始演绎
我们先在 activity_main.xml 中来定义两个按钮,一个来控制启动,一个来控制停止如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.qiudengjiao.servicetest.MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="开启服务" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="stopService"
android:text="停止服务" />
</LinearLayout>
接着我们来看 MainActivity 中的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 开启服务
*/
public void startService(View view) {
Intent startIntent = new Intent(this, MyService.class);
//启动服务
startService(startIntent);
}
/**
* 停止服务
*/
public void stopService(View view) {
Intent stopService = new Intent(this, MyService.class);
//停止服务
stopService(stopService);
}
}
这里看到,我们通过构建 Intent 对象,并调用 startService() 和 stopService() 来启动和停止服务,因为 startService() 和 stopService()方法都是定义在 Context 类中的,所以在 Activity 中我们可以直接调用,需要注意的是,这里完全是由 Activity 来决定服务何时停止的,如果不点击停止服务按钮,服务就会一直处于运行状态,,如果需要服务自己停下来,需要在 Myservice 中调用 stopSelf() 方法,说了这么多,我们来点击开启和停止服务分别来看看日志的打印情况,看是否和我们描述的一样:
点击开启服务日志打印情况:
点击停止服务日志打印情况:
从日志的打印情况和我们的描述是一模一样的,也验证了我们的想法
3. 活动和服务进行通信
在上面 1 和 2 中我们介绍了 Service 的基本用法,当启动 Service 后,就可以在 onCreate() 或者 onStartCommand() 方法里去执行具体的逻辑操作,那么我们如何才能把 Activity 和 Service 关联起来,进行通信,这样我们就能在 Activity 中来控制 Service,这里就需要用到我们一开始创建 Service 时就必须强制实现父类的 onBind() 方法,其实这个方法就是用于和 Acitivity 进行关联的
例如我们希望在 MyService 里面提供一个下载功能,然后活动中可以决定何时开始下载,以及随时查看下载进度,实现这个功能的思路是可以创建一个专门的 Binder 对象对下载功能进行管理,接下来我们修改 MyService 中的代码代码如下所示:
/**
* 定义一个 Service
*/
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
//模仿方法
public void startDownload() {
// 执行具体的下载任务
Log.d(TAG, "startDownload");
}
//模拟加载进度
public int getProgress() {
Log.d(TAG, "getProgress");
return 0;
}
}
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* 在服务第一次创建的时候调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
/**
* 每次服务启动的时候调用
*
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁服务时调用
* 在这里可以回收不在使用的资源
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
可以看到我们在 MyService 里新建了一个 DownloadBinder 类,并让它继承自 Binder,然后在它的内部提供了开始下载以及查看进度的方法,当然我们这里只是提供模拟的方法,并没有真正的去实现它对应的功能,接着在 MyService 中创建了 DownloadBinder 的实例,然后再 onBind() 方法里返回了这个实例,到这里 MyService 中的工作就全部完成
接下来我们在布局文件添加两个按钮去调用服务里的方法,我们来修改 activity_main.xml 中的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.qiudengjiao.servicetest.MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="开启服务" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="stopService"
android:text="停止服务" />
<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bindService"
android:text="绑定服务" />
<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unBindService"
android:text="解绑服务"
/>
</LinearLayout>
这两个按钮分别是用来绑定和解绑服务的,那么到底需要谁去和服务绑定呢,当然是活动,当一个服务和活动绑定之后,就可以调用该服务里 Binder 提供的方法,我们修改 MainActivity 中的代码:
public class MainActivity extends AppCompatActivity {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 开启服务
*/
public void startService(View view) {
Intent startIntent = new Intent(this, MyService.class);
//启动服务
startService(startIntent);
}
/**
* 停止服务
*/
public void stopService(View view) {
Intent stopService = new Intent(this, MyService.class);
//停止服务
stopService(stopService);
}
/**
* 绑定服务
*/
public void bindService(View view) {
Intent bindIntent = new Intent(this, MyService.class);
//绑定服务
bindService(bindIntent, connection, BIND_AUTO_CREATE);
}
/**
* 解绑服务
*/
public void unBindService(View view) {
//解绑服务
unbindService(connection);
}
}
这里我们首先创建了创建了一个 ServiceConnection 的匿名类,并重写了 onServiceConnected() 和 onServiceDisconnected() 方法
- onServiceConnected():活动与服务绑定时调用
- onServiceDisconnected():活动与服务解除绑定时调用
在 onServiceConnected() 方法中我们通过向下转型得到 DownloadBinder 的实例,这个实例保证了活动与服务之间的紧密连接,这样我们就能在活动中根据具体的场景来调用 DownloadBinder 中的任何 public 方法了
当然,现在活动还没有和服务进行绑定,这个功能是在绑定服务的按钮的点击事件里完成的,可以看到这里我们任然构建了一个 Intent 对象,然后调用 bindService() 方法进行绑定,bindService() 方法接受 3 个参数,第一个参数就是刚刚构建的 Intent 对象,第二个参数是前面创建出的 ServiceConnection 的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE 表示在活动和服务绑定之后自动创建服务,这会使得 MyService 中的 onCreate() 方法得到执行,但是 onStartCommand() 方法不会执行,点击解绑服务按钮,调用 unbindService() 方法,就可以解除活动和服务之间的绑定
现在我们来运行下程序,点击绑定服务按钮,看我们的日志打印情况:
可以看到,首先是 MyService 的 onCreate() 方法得到了执行,然后 startDownload() 方法和 getProgress() 方法得到执行,说明我们成功的在活动里调用了服务里提供的方法
另外需要注意:任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,还可以和任何一个其他的活动进行绑定,而且绑定完成后都可以获取到相同的 DownloadBinder 实例
4.服务的生命周期
1)管理服务的生命周期
服务的生命周期比 Activity 的生命周期要简单得多,但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台
服务的生命周期(从创建到销毁)可以遵循两条不同的路径:
- 启动服务
该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过 stopSelf() 来自行停止运行,此外,其他组件也可以通过调用 stopService 来停止服务,服务停止后,系统会将其销毁
- 绑定服务
该服务在另一个组件(客户端)调用 bindService() 时创建,然后,客户端通过 IBinder 接口与服务进行通信,客户端可以调用 unbindService() 关闭连接,多个客户端可以绑定到相同的服务,而且当所有绑定全部取消后,系统即会销毁该服务,(服务不必自行停止运行)
这两条路并非完全独立,也就是说,你可以绑定到已经使用 startService() 启动的服务,例如,可以通过使用 Intent (标识要播放的音乐),调用 startService() 来启动后台音乐服务,随后,可能在用户需要稍加控制播放器获取有关当前播放歌曲的信息是,Activity 可以调用 bindService() 绑定到服务,在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会实际停止服务
2)实现生命周期回调
与 Activity 类似,服务也拥有生命周期的回调方法,你可以实现这些方法来监控服务状态的变化并适时执行相应的操作,以下框架服务展示了每种生命周期方法,来自 Google 官网:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
接下来看看服务的生命周期图,来自 Google 官网:
注意:与 Activity 生命周期回调方法不同,你不需要调用这些回调方法的超类实现
上图,左边显示了使用 startService() 所创建的服务的生命周期,右图显示了使用 bindService() 所创建的服务的生命周期,通过实现这些方法,你可以监控服务生命周期的两个嵌套循环:
- 服务的整个生命周期从调用 onCreate() 开始,到 onDestroy() 返回结束,与 Activity 类似,服务也在 onCreate() 方法中完成初始化设置,并在 onDestroy() 方法中释放所剩余资源,例如音乐播放器播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 方法中停止该线程,无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate() 和 onDestroy() 方法
- 服务的有效生命周期从调用 onStartCommand() 或 onBind() 方法开始每种方法均有 Intent 对象,该对象分别传递到 startService() 或 bindService(),对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务任然处于活动状态)对于绑定服务,有效生命周期在 onUnbind() 返回时结束
注意:尽管启动服务是通过调用 stopSelf() 或 stopService() 来停止,但该服务并无相应的回调(没有 onStop() 回调)因此,除非服务绑定到客户端,否则在服务停止时,系统会通过 onDestroy() 将其销毁
5. 如何销毁 Service
上面我们分析了 Service 两种情况的生命周期,我们总结一下销毁 Service 的各种情况
1)点击开启服务按钮,再点击停止服务按钮,这样 Service 就被销毁了,我们来看日志情况:
点击开启服务:
点击停止服务:
2)如果我们点击的是绑定服务按钮,那么就需要点击解绑服务按钮来销毁服务,来看看是具体什么执行情况:
点击绑定服务按钮:
点击解绑服务按钮:
3)以上两种销毁方式比较好理解,接下来如果我们先点击开启服务按钮,再点击绑定服务按钮,然后点击停止服务按钮,这样会怎么样呢?这时我们发现,不管我们是单独点击停止服务按钮,还是单独点击解绑服务按钮,Service 都不会销毁,必须两个按钮都点击一下,Service 才会被销毁,这种现象说明,点击停止服务按钮只能让 Service 停止,点击解绑服务按钮只会让 Service 和 Activity 解除关联,要想让 Service 销毁,必须既没有和任何 Activity 关联,又处于停止状态的时候,接下来我们来看看日志的打印情况,以验证我们上面的说法:
点击开启服务按钮:
再点击绑定服务按钮:
点击停止服务按钮:
这里看到点击停止服务按钮,服务并没有停止,我们接下来再点击解绑服务按钮:
这时候我们看到服务才停止了
今天就先写到这里,如有错误请指出,大家周末愉快
参考:Google 官网,郭神第一行代码