Service 的 "使用方法"

1. 核心源码( Android 9.0 )

关键类 路径
Service.java frameworks/base/core/java/android/app/Service.java

2. Service

2.1 服务是什么?

服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还需要长期进行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

2.2 创建服务

定义一个服务

首先,我们定义一个 MyService.java 类,当然作为一个服务类,必须要继承 Service(android.app.Service):

1
2
3
4
5
6
7
8
9
10
// 源码路径:frameworks/base/core/java/android/app/Service.java

public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
private static final String TAG = "Service";
... ...

@Nullable
public abstract IBinder onBind(Intent intent);
... ...
}

Service 源码中定义了一个抽象方法 onBind,所以,子类继承它必须复写 onBind 方法。我们常见一个项目,添加一个 Service:

ex01gS.png

ex5a5T.png

服务既然已经定义好了,自然应该在服务中去处理一些事情,那处理事情的逻辑应该写在哪里?我们需要在服务里重写 Service 中的另一些常用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

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");
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
}
}

onCreate()onStartCommand()onDestroy() 这三个方法是每个服务中最常用到的方法。其中 onCreate() 方法会在服务创建的时候调用,onStartCommand() 方法会在每次服务启动的时候调用,onDestroy() 方法会在服务销毁的时候调用。

通常情况下,我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在 onStartCommand() 里,而当服务销毁时,我们应该在 onDestroy() 方法中去回收那些不再使用的资源。

AndroidManifest.xml

定义好了服务,就要让服务生效,每一个服务都需要在 AndroidManifest.xml 文件中进行注册(Android Studio会自动帮您完成 Service 的注册工作),这也是四大组件的共有特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

至此,一个服务就完全定义好了。

2.3 启动和停止服务

服务定义好了,接下来就应该考虑如何去启动以及停止这个服务了。

(1)先添加两个Button(activity_main.xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service"/>

<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service"/>

</LinearLayout>

(2)接下来,修改主函数 MainActivity 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service); // 获取按钮实例
Button stopService = (Button) findViewById(R.id.stop_service); // 获取按钮实例
startService.setOnClickListener(this); // 注册点击事件
stopService.setOnClickListener(this); // 注册点击事件
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
default:
break;
}
}
}

上面的代码很简单,主要作了以下工作:

(1)取得 startServicestopService 两个按钮实例,并且注册了点击事件;

(2)通过 Intent 对象,调用 Activity 的 startService()stopService() 方法来启动和停止服务。

这里活动的启动和停止完全是由活动本身控制的,如果我们 start 了服务,但是没有点击 stop,那么服务会一直处于运行状态,此时服务如何让自己停止下来?只需要在 MyService 的任何一个位置调用 stopSelf() 这个方法就能让服务停下来!

2.4 Log 测试

添加 Log,查看 Service 是如何运作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

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");
}

@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
Log.d("MyService", "onDestroy executed");
super.onDestroy();
}
}

添加了 3 行Log,目的就是看在我们点击两个按钮的时候,整个 Service 什么时候创建,什么时候启动,什么时候销毁!

我们来看一下执行结果,运行程序,查看Logcat中的打印日志:

(1)第一次点击 StartService按钮 后,MyService中的 onCreate()onStartCommand() 方法都执行了!

ex5qdP.png

(2)然后我们点击 stopService按钮 后,MyService中的 onDestroy() 方法被执行!

ex5jJS.png


3. 生命周期

上面介绍完 Service 的使用方法,接下来看看 Service 的 生命周期

跟 Activity 相比,Service 的生命周期很简单:onCreate() -> onStart() -> onDestroy(),我们以如下的方式展开这章节的讨论工作!

【主题】Activity 与 Service之间的 Communication

【问题】由上贴我们知道,当我们点击 START SERVICE 按钮后,服务的 onCreate() 和 onStartCommand() 方法会得到执行,此后 Service 是一直存在于后台运行的,Activity 无法控制 Service 中具体的逻辑运行,那么这样 Activity 只相当于起到一个通知的作用,除了告诉 Service 你可以开始工作了。那么这样显然会分离两者之间的关联性,这也不是我们需要的结果!

【后果】如果出现以上的问题,那么在我们平时的项目开发过程中,一直存在的 Service 很有可能会引起功耗的问题,可能影响手机的运行效率!

【要求】我们能否将 Activity 与 Service 建立一种联系,当 Activity 终结之时,Service 也销毁,也就是有没有办法让 Activity 和 Service 能够“不求同生,但求共死”

答案是肯定的!这就涉及到 Service 的另一个重要知识点:绑定解绑

还是以代码为例:我们在 MyService 里面添加一个下载功能,然后在活动中可以决定何时开始下载,以及随时可以查看下载进度,我们修改代码。

MyService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

private DownloadBinder mBinder = new DownloadBinder(); // 定义一个 DownloadBinder 类

// 让 DownloadBinder 成为 Binder 的子类
class DownloadBinder extends Binder {

public void startDownload() { // 定义开始下载的方法
Log.d("MyService", "startDownload executed");
}

public int getProgess() { // 定义一个查看下载进度的方法
Log.d("MyService", "getProgress executed");
return 0;
}
}

@Override
public IBinder onBind(Intent intent) { // onBind()方法,这个方法将在绑定后调用
return mBinder; // 返回 IBinder 的实例 --> DownloadBinder 类的实例
}

@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
Log.d("MyService", "onDestroy executed");
super.onDestroy();
}
}

BIND SERVICE / UNBIND SERVICE

我们在Layout中添加两个按钮 BIND SERVICEUNBIND SERVICE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service"/>

<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service"/>

<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BIND SERVICE"/>

<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="UNBIND SERVICE"/>
</LinearLayout>

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private MyService.DownloadBinder downloadBinder;

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.startDownload();
downloadBinder.getProgess();
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service); // 获取按钮实例
Button stopService = (Button) findViewById(R.id.stop_service); // 获取按钮实例
Button bindService = (Button) findViewById(R.id.bind_service); // 获取按钮实例
Button unbindService = (Button) findViewById(R.id.unbind_service); // 获取按钮实例
startService.setOnClickListener(this); // 注册点击事件
stopService.setOnClickListener(this); // 注册点击事件
bindService.setOnClickListener(this); // 注册点击事件
unbindService.setOnClickListener(this); // 注册点击事件
}

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务
break;
case R.id.unbind_service:
unbindService(connection); // 解绑服务
break;
default:
break;
}
}
}

可以发现,这里我们首先创建了一个 ServiceConnection 的匿名类,在里面重写了 onServiceConnected() 方法和 onServiceDisconnected() 方法,这两个方法会在活动与服务成功绑定以及解绑的时候被调用。

重点看下 bindService(bindIntent, connection, BIND_AUTO_CREATE) 这个方法:

bindService 接收了 3 个参数:

bindIntent:这个参数传入的就是我们的 intent,目的就是调用 MyService 这个服务。

connection:这个参数传入的就是创建好的 ServiceConnection 的实例,这个参数代表着我们的 Activity 是要和 Service 绑定在一起的。

BIND_AUTO_CREATE:这是一个 FLAG,表示在活动和服务进行绑定后自动创建服务。注意!是自动创建服务,也就是说 MyService 会执行 onCreate() 方法,但是不会执行 onStartCommand() 方法!

下面通过排列组合,对按钮进行点击,Log 分 3 种情况:

START SERVICE + STOP SERVICE

1、当我们先点击 START SERVICE :此时服务启动,调用 onCreat() 和 onStartCommand() 方法;

2、当我们后点击 STOP SERVICE :此时,服务被销毁,调用 onDestroy() 方法。

BIND SERVICE + UNBIND SERVICE

1、当我们先点击 BIND SERVICE :此时服务仅仅是创建,并未启动!所以调用的只是 onCreate() 方法。此时 Activity 与 Service 绑定,会同时调用 onBind() 方法,onServiceConnected() 方法会被执行,还记的 onBind() 方法的返回类型不?我们通过 Log 可以很明显发现,Activity 调用了服务内部的两个自定义方法。

exImQJ.png

2、然后点击 UNBIND SERVICE :由于服务还未启动,而 BIND SERVICE 只是将服务创建好并与活动进行绑定,那么解绑后,势必会销毁这个 Service,所以 onDestroy() 被执行!

exIQdx.png

START SERVICE + BIND SERVICE + UNBIND SERVICE + STOP SERVICE

1、我们先点击 START SERVICE :onCreat() 和 onStartCommand() 方法被执行,这个就不用多说了;

2、然后点击 BIND SERVICE :这个时候其实活动已经在后台运行了,我们此时将活动和服务绑定,那么 onCreate() 不会再执行,只会执行 onServiceConnected() 方法,Log 里面打出来看的很清楚。

exI3FK.png

3、如果此时你直接点击了 STOP SERVICE:你会发现毫无反应(无 Log 输出)!那这是为什么?因为你都没解绑,所以你是无法销毁服务的!

4、那我们先解绑,点击 UNBIND SERVICE :但是,LOG 日志仍然没有打印出 Destroy() 这个方法?我们前面不是说 bind 了 Service 之后,unbind 就会销毁这个服务吗?这跟我们之前分析的不符合。

其实原因很简单:我们先 start Service,那么此时服务已经在后台运行了,这个时候你 bind,让 Service 和 Activity 绑定,其实是没有什么意义的。但是既然绑定了,你如果不解绑,那么 Destroy() 毫无用武,所以,这种情况和(2)中分析的还是有区别的,此是解绑完后,服务还是舒舒服服的在后台运行,所以,要想干掉这个服务,你必须要 STOP SERVICE。

5、等待我们解绑之后,再执行 STOP SERVICE :这个时候 Service 就被销毁了!

exIdeI.png


4. 两个实用小技巧

4.1 Forground Service

服务几乎都是在后台运行的,一直一来它都是默默地做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不是由于系统内存不足的原因导致被回收掉,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务,有些项目由于特殊的需求会要求必须使用前台服务,比如说天气类软件,它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息。

【问题】:我们都知道服务是运行在后台的,如果系统出现内存不足的情况,那么此时,系统就可能回收后台的服务,那么我们如何保证服务可以一直运行?

【解决】:在服务中,有一个 前台服务 的概念,调用 startForground() 方法可以实现。

如何创建一个前台服务,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyService extends Service{

  ......

@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, "default")
.setContentTitle("This is a notification title")
.setContentText("This is a notification text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1, notification);
}
... ...
}

以上的代码是在 Service 的创建中添加了一个 Notification,调用 startForground() 就可以保证:只要服务一直存在,那么在前台就会一直显示这个 Notification。

如果我们在 onDestroy() 中调用 stopForground() 方法,会销毁这个 Notification,但是 Service 还是存活的,此时 Service 就会面临被 System 干掉的风险。如果直接 STOP SERVICE,那么 Notification 和 Service 都会销毁。

4.2 IntentService

【问题】:我们知道服务的代码逻辑是在主线程中执行的,如果我们在主线程中需要执行一些耗时的操作,那么很有可能出现 ANR。

这个时候可以采用 Android 的多线程编程的方式,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。所以,一个比较标准的服务就可以写成如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyService extends Service{

  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() { // 开启一个线程处理耗时操作
      @Override
      public void run() {
        // 处理具体的逻辑
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

现在,服务可以启动起来了。但是如果不调用 StopService()stopSelf() 方法,服务会一直运行,所以我们需要修改一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyService extends Service{

  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        // 处理具体的逻辑
        stopSelf(); // 让服务执行完逻辑后自行停止
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

上面的代码就是一个标准的 Service 的书写形式,主要包含两个知识点:Thread 子线程的创建stopSelf() 方法的调用。

虽说这种写法并不复杂,但是总会有人忘记开启线程,或者忘记调用 stopSelf(),那么有没有更好的办法能够实现上面两个需求呢?

【解决】:在 Android 中,专门提供了一个 IntentService 类(android.app.IntentService),这个类就能很好的满足我们的需求!

新建一个 MyIntentService 类继承自 IntentService,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.myapplication;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

public class MyIntentService extends IntentService {

public MyIntentService() {
super("MyIntentService"); // 调用父类的有参构造函数
}

@Override
protected void onHandleIntent(Intent intent) {
Log.d("MyIntentService", "Thread id is" + Thread.currentThread().getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}

以上代码做了几件事:

1、提供了一个无参的构造方法,并且调用了父类的有参构造函数;

2、子类实现父类的 onHandleIntent() 抽象方法,这个方法的精妙之处在于:它是一个已经运行在子线程中的方法。也就是说,服务调用了它,那么执行的逻辑就如同 Thread 子线程;

3、根据 IntentService 的特性,这个服务在运行结束后应该是会自动停止的,所以我们又重写了 onDestroy() 方法,在这里也打印一行日志,以证实服务是不是停止掉了。

我们在 xml 文件中,创建一个 MyIntentService 服务按钮:

1
2
3
4
5
<Button
android:id="@+id/start_intent_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="START INTENT SERVICE"/>

然后修改 MainActivity 中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

... ...

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
... ...
Button startIntentService = (Button) findViewById(R.id.start_intent_service);
... ...
startIntentService.setOnClickListener(this);

}

@Override
public void onClick(View view) {
switch (view.getId()) {
... ...

case R.id.start_intent_service:
// 打印主线程的 id
Log.d("MainActivity", "Thread id is" + Thread.currentThread().getId());
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);
break;
default:
break;
}
}
}

最后,在AndroidMainfest中注册服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".MyIntentService"
android:exported="false"></service>
... ...
</application>

</manifest>

【结果】

exIrY8.png

从打出的 LOG 可以看出:

1、MyIntentService 和 MainActivity 所在进程的 id是不一样的

2、onHandleIntent() 方法在执行完逻辑后确实销毁了服务,效果等同于 stopSelf()。

从上面的分析可以看出 onHandleIntent() 方法确实相当的好用!