Android Tech And Thoughts.

Service

Word count: 3.7kReading time: 15 min
2019/11/29 Share

IPC.jpg

本文仅列出一些需要注意的点.如果需要详细地了解 Service,请查阅官方文档:Android developer :Service

Concept

Service是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务有三种类型分别为 前台、后台、绑定 。

注意:服务在其托管进程的主线程中运行,它既创建自己的线程,也在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

Thread or Service?

选择的关键在于,你是否只在用户与您的应用交互时执行操作。例如如果你只想在Activity运行的同时播放一些音乐,则可在 onCreate 中创建线程,在 onStart 中启动线程运行,然后在 onStop 中停止线程,您还可以考虑 AsyncTask 或者 HandlerThread,而非传统的 Thread 类。但是如果你希望退出 Activity 之后仍然能够播放音乐,也就是说你不依赖与交互了,这时候 Service 是比较好的选择。

Basic API

  • onStartCommand() : 启动的服务
  • onBind():绑定的服务
  • onCreate():先于 onStartCOmmon 或者 onBind 执行,做一些初始化设置
  • onDestroy():资源清理

如果服务通过调用 startService() 启动服务(回调 onStartCommand() ),则服务会一直运行,直到使用 stopSelf() 自行停止运行,或由其它组件通过 stopService() 将其停止运行。

如果组件通过调用 bindService() 来创建服务,则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。

注意,一个服务可能既是启动的服务,也是绑定的服务,这时候它的生命周期就是综合起来看的zain

在设置清单文件地时候,为确保应用的安全性,在启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务会响应 Intent,而用户也无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),则系统会抛出异常。您可以使用 android:exported = false 以确保服务仅适用于您的引用

启动服务

如果多次使用 startService() 尝试启动同一个 Service,该 Service 的 onStartCommand 方法就会多次被u调用。但是该 Service 的 onCreate 方法只会被调用一次。

Service # onStartCommand

1
public int onStartCommand(Intent i,int flags,int startId){
2
    onStart(i , startId);
3
    return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY
4
}

默认情况,有两种返回值,但是其实有四种可返回值,我们可以通过重写来实现,我们来分别看下这四种状态:

  • START_STICKY:Service被kill掉会被重启,但不会重传Intent对象
  • START_NO_STICKY:Service被kill掉后,不会被重启
  • START_REDELIVER_STICKY:与START_STICKY不同的是会重传Intent对象
  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版,但是不保证Service被重启

startId 是用来标记启动任务的次数的,这样,以便于在所有任务都执行完毕的时候,知道所有任务都执行完毕了。

绑定服务

绑定服务(注意这不是一个动词,而是一个名词 ‘绑定的服务’)是 客户端-服务器 接口中的 服务器。借助绑定服务,组件(例如 Activity)可有绑定到服务、发送请求、接收响应,以及执行IPC。绑定服务通常只在为其它应用组件提供服务时处于活动状态,不会无限期在后台运行。

使用

绑定服务是 Service 的实现,如需要为服务提供绑定,您必须实现 onBind() 回调方法,改哦方法会返回 IBinder 对象,该对象定义的编程接口可供客户端用来与服务进行交互。

客户端通过调用 bindService() 绑定到服务。调用时,它必须提供 ServiceConnection 的实现,后者会监控与服务的链接。当创建客户端与服务之间的连接时,Android系统会调用 ServiceConnection 上的 onServiceConnected() , onServiceConnected() 方法包含 IBinder 参数,客户端随后会使用该参数与绑定服务进行通信。

您可以同时将多个客户端连接到服务。但是,系统会缓存 IBinder 服务通信通道。换言之,只有在第一个客户端绑定服务时,系统才会调用服务的 onBind() 方法来生成 IBinder。然后,系统会将同一 IBinder 传递至绑定到相同服务的所有其他客户端,无需再次调用 onBind()

当最后一个客户端取消与服务的绑定时,系统会销毁服务(除非它也是一个启动的服务)

针对您的绑定服务实现,最重要的环节是定义 onBind() 回调方法所返回的接口。

注意:绑定 (bindService)为异步操作(无需将 IBinder 返回至客户端即可立即返回) 。如要接收 IBinder,客户端必须创建一个 ServiceConnection 实例(包含一个回调方法),并将其传给 bindService (实现异步回调)

只有 Activity、服务 和 内容提供程序可以绑定到服务,您无法从广播接收器绑定到服务

三种方式提供 IBinder

扩展 Binder 类

其基本形式如下,新建Service类,实现 onBind 回调方法,在 onBind里面不会直接返回 Service实例,而是返回一个内部类型 IBinder 实例(实现了 IBinder 接口的内部类),这样,客户端间接地持有了mService 的实例(如果是 IPC ,则是虚拟实例),从而可以调用 Service 的方法。

Service.java

1
public class LocalService extends Service {
2
    private static final String TAG = "dan";
3
    private IBinder mBinder = new LocalBinder();
4
5
    public LocalService() {
6
    }
7
8
    @Override
9
    public IBinder onBind(Intent intent) {
10
        // TODO: Return the communication channel to the service.
11
        //throw new UnsupportedOperationException("Not yet implemented");
12
        return mBinder;
13
    }
14
15
    @Override
16
    public void onCreate() {
17
        super.onCreate();
18
        Log.d(TAG, "onServiceCreate: ");
19
    }
20
21
    public int getRandomNum() {
22
        return (int) (Math.random()*1000);
23
    }
24
25
    public class LocalBinder extends Binder {
26
        public LocalService getService() {
27
            return LocalService.this;
28
        }
29
    }
30
}

MainActivity.java

1
public class MainActivity extends AppCompatActivity {
2
3
    private static final String TAG = "dan";
4
    private LocalService localService;
5
    private ServiceConnection connection = new ServiceConnection() {
6
        @Override
7
        public void onServiceConnected(ComponentName name, IBinder service) {
8
            LocalBinder localBinder = (LocalBinder)service;
9
            localService = localBinder.getService();
10
            Log.d(TAG, "onServiceConnected: " );
11
        }
12
13
        @Override
14
        public void onServiceDisconnected(ComponentName name) {
15
            Log.d(TAG, "onServiceDisconnected: ");
16
        }
17
    };
18
19
    @Override
20
    protected void onCreate(Bundle savedInstanceState) {
21
        super.onCreate(savedInstanceState);
22
        setContentView(R.layout.activity_main);
23
24
        findViewById(R.id.get_btn).setOnClickListener(new View.OnClickListener() {
25
            @Override
26
            public void onClick(View v) {
27
28
                Log.d(TAG, "onClick: "+localService.getRandomNum());
29
            }
30
        });
31
    }
32
33
    @Override
34
    protected void onStart() {
35
        super.onStart();
36
        Intent serviceIntent = new Intent(this, LocalService.class);
37
        bindService(serviceIntent, connection, BIND_AUTO_CREATE);
38
    }
39
40
    @Override
41
    protected void onStop() {
42
        super.onStop();
43
        unbindService(connection);
44
    }
45
}

使用Messenger

如果需要让接口跨不同进程工作,您可以使用 Messenger 为服务创建接口。服务可借此方式定义 Handler,以响应不同类型的 Message 对象(携带不同的 what 参数)。此Handler是 Messenger 的基础(由它构造服务端的Messenger),该Messenger随后与客户端分享一个 IBinder (onBind回调返回 Messenger.getBinder() ),客户端只需要根据服务传过来的IBinder构造Messenger,然后用send 发送消息即可。此外,客户端还可以自定义 Messenger,以便回传给服务消息(双向通信)

messenger.png

MessengerService.java

1
public class MessengerService extends Service {
2
    private static final String TAG = "dan";
3
    static final int MSG_SAY_HELLO = 1;
4
5
    static class IncomingHandler extends Handler {
6
        private Context applicationContext;
7
8
        IncomingHandler(Context context){
9
            applicationContext = context.getApplicationContext();
10
        }
11
12
        @Override
13
        public void handleMessage(Message msg){
14
            switch (msg.what){
15
                case MSG_SAY_HELLO:
16
                    Toast.makeText(applicationContext,"hello!",Toast.LENGTH_SHORT)
17
                            .show();
18
                    break;
19
                    default:
20
                        super.handleMessage(msg);
21
            }
22
        }
23
    }
24
25
    Messenger mMessenger;
26
27
28
    @Override
29
    public IBinder onBind(Intent intent) {
30
        // TODO: Return the communication channel to the service.
31
        //throw new UnsupportedOperationException("Not yet implemented");
32
33
        Log.d(TAG, "onBind: ");
34
        mMessenger = new Messenger(new IncomingHandler(this));
35
        return mMessenger.getBinder();
36
    }
37
}

MainActivity.java

1
public class MainActivity extends AppCompatActivity {
2
3
    Messenger mService = null;
4
5
    /** Flag indicating whether we have called bind on the service. */
6
    boolean bound;
7
8
    /**
9
     * Class for interacting with the main interface of the service.
10
     */
11
    private ServiceConnection mConnection = new ServiceConnection() {
12
        public void onServiceConnected(ComponentName className, IBinder service) {
13
            // This is called when the connection with the service has been
14
            // established, giving us the object we can use to
15
            // interact with the service.  We are communicating with the
16
            // service using a Messenger, so here we get a client-side
17
            // representation of that from the raw IBinder object.
18
            mService = new Messenger(service);
19
            bound = true;
20
        }
21
22
        public void onServiceDisconnected(ComponentName className) {
23
            // This is called when the connection with the service has been
24
            // unexpectedly disconnected -- that is, its process crashed.
25
            mService = null;
26
            bound = false;
27
        }
28
    };
29
30
    //
31
    public void sayHello(View v) {
32
        if (!bound) return;
33
        // Create and send a message to the service, using a supported 'what' value
34
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
35
        try {
36
            // mService 是一个 Messenger(由服务端 Handler 构造)
37
            mService.send(msg);
38
        } catch (RemoteException e) {
39
            e.printStackTrace();
40
        }
41
    }
42
43
    @Override
44
    protected void onCreate(Bundle savedInstanceState) {
45
        super.onCreate(savedInstanceState);
46
        setContentView(R.layout.activity_main);
47
    }
48
49
    @Override
50
    protected void onStart() {
51
        super.onStart();
52
        // Bind to the service
53
        bindService(new Intent(this, MessengerService.class), mConnection,
54
                Context.BIND_AUTO_CREATE);
55
    }
56
57
    @Override
58
    protected void onStop() {
59
        super.onStop();
60
        // Unbind from the service
61
        if (bound) {
62
            unbindService(mConnection);
63
            bound = false;
64
        }
65
    }
66
}

为接口使用 Messenger 比使用 AIDL 简单,因为 Messenger 会将所有服务调用加入队列;纯AIDL接口会同时向服务发送多个请求,服务随后必须执行多线程处理。如果您的服务必须执行多线程处理,请使用 AIDL 来定义接口。

Messenger完全解析

使用AIDL

Android 接口定义语言[ AIDL]

IDL代表接口语言。您可以利用 AIDL 定义客户端与服务均认可的编程接口,以便二者使用IPC进行相互通信。在Android中,一个进程通常无法访问另一个进程的内存,因此,进程需要将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象,AIDL就是对这一系列操作的封装。

注意:只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,您才有必要使用 AIDL。 如果您无需跨应用执行并发 IPC,则应该使用扩展Binder来创建接口,或者如果你需要跨应用但不需要并发执行 IPC ,则应该使用 Messenger

Android Developer Guide :


在开始设计 AIDL 接口之前,请注意,AIDL 接口的调用是直接函数调用。您无需对发生调用的线程做任何假设。实际情况的差异取决于调用是来自本地进程中的线程,还是远程进程中的线程。具体而言:

  • 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是您的主界面线程,则其将继续在 AIDL 接口中执行。如果该线程是其他线程,则其便是在服务中执行代码的线程。因此,只有在本地线程访问服务时,您才能完全控制哪些线程在服务中执行(但若出现此情况,您根本无需使用 AIDL,而应通过实现 Binder 类来创建接口)。
  • 远程进程的调用分派自线程池,且平台会在您自己的进程内部维护该线程池。您必须为来自未知线程,且多次调用同时发生的传入调用做好准备。换言之,AIDL 接口的实现必须基于完全的线程安全。如果调用来自同一远程对象上的某个线程,则该调用将依次抵达接收器端。
  • oneway 关键字用于修改远程调用的行为。使用此关键字后,远程调用不会屏蔽,而只是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自 Binder 线程池的常规调用(普通的远程调用)。如果 oneway 用于本地调用,则不会有任何影响,且调用仍为同步调用。

上面的话读起来有点绕口,我来翻译一下:

  • Service端会开启新的子线程执行client端的调用(除非Service和Client在同一进程内,你才能保证Service的执行方法与调用线程保持一致,也就是说完全地控制Service端执行代码的进程,否则是不可控的)
  • 这里所指的进程指的服务端进程,对于远程客户端的调用,该调用分配自线程池(还是指的Service),所以对于 Service 端,必须要做好线程同步工作
  • 暂时没太理解 (TODO)
定义AIDL接口

您必须在 .aidl 文件中使用 Java 编程语言的语法定义 AIDL 接口,然后将其保存至应用的源代码(在 src/ 目录中)内,这类应用会托管服务或与服务进行绑定。

在构建每个包含 .aidl 文件的应用时,Android SDK 工具会生成基于该 .aidl 文件的 IBinder 接口,并将其保存到项目的 gen/ 目录中。服务必须视情况实现 IBinder 接口。然后,客户端应用便可绑定到该服务,并调用 IBinder 中的方法来执行 IPC。

如果要使用 AIDL创建绑定服务,请执行以下步骤:

  1. 创建 .aidl 文件

    此文件定义带有方法签名的编程接口

  2. 实现接口

    Android SDK 工具会基于您的 .aidl 文件,使用 Java 生成接口。此接口拥有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现这些方法

  3. 向客户端公开接口

    实现 Service 并重写 onBind(),从而返回 Stub 类的实现

注意,如果您后续对于 aidl 进行修改,必须保证它的向后兼容性,保留对于原始接口的支持

1.创建 .aidl 文件

  • AIDL有默认的支持的数据类型,除此之外,也可以是 AIDL生成的其他接口或 Parcelable 类型。
  • 即使您在与接口相同的包内定义上方未列出的附加类型,亦须为其各自加入一条 import 语句
  • 定义服务接口时,所有的非原语参数均需要指示数据走向的方向标记,这类标记可以是 in、out 或 inout。原语默认是 in
  • 您可以在 AIDL 接口中定义 String 和 int 常量
  • 方法调用由 transact() 代码分配。该代码通常基于接口中的方法索引

三种方式提供 IBinder 的比较

Refer

01.Android Developer

CATALOG
  1. 1. Concept
  2. 2. Thread or Service?
  3. 3. Basic API
  4. 4. 启动服务
  5. 5. 绑定服务
    1. 5.0.1. 使用
  6. 5.1. 三种方式提供 IBinder
    1. 5.1.0.1. 扩展 Binder 类
    2. 5.1.0.2. 使用Messenger
    3. 5.1.0.3. 使用AIDL
      1. 5.1.0.3.0.1. Android 接口定义语言[ AIDL]
      2. 5.1.0.3.0.2. 定义AIDL接口
  7. 5.1.0.4. 三种方式提供 IBinder 的比较