深入理解Service(二)——绑定服务

在上一篇博客深入理解Service(一)——服务生命周期中了解了Service会有启动和绑定两种状态,对应着这两种状态会有不同的生命周期:开启服务与停止服务的方式均有所不同。这篇博客主要介绍绑定服务相关的内容,以及如果服务与调用者,比如Activity处于不同进程时该如何操作,即通常意义上所说的IPC。

绑定服务的实现

什么时候需要使用绑定服务呢?我的理解是调用者在启动完服务后,还需要以后与服务打交道,还要控制服务,而不是像启动服务一样任服务自由自在不受控制。常见的一个场景,Service播放音乐,界面上有上一曲,下一曲、暂停、播放等按钮,通过这些按钮还可以控制Service操作,这种情景就是使用的绑定,本篇博客的例子也将实现一个简单的demo。

1.定义服务

绑定服务,需要重写onBind()方法,并且返回一个IBinder对象,该IBinder客户端再绑定服务成功后可以获取一个该对象,因此客户端可以通过IBinder来控制服务。IBinder是一个接口,一般我们不会直接实现该接口,而是会继承Binder类,Binder类实现了IBinder类。
下面是该播放服务的示例:

**
 * 播放音乐服务
 */
public class PlayMusicService extends Service {
    public PlayMusicService() {
    }

    public class PlayMusicBinder extends Binder {

        /**
         * 播放音乐
         */
        public void play() {

            playMusic();

        }

        /**
         * 暂停播放
         */
        public void pause() {
            pauseMusic();
        }

        /**
         * 暂停播放
         */
        public void stop() {

            stopMusic();

        }

    }

    private PlayMusicBinder mBinder = new PlayMusicBinder();

    private MediaPlayer mediaPlayer;

    @Override
    public void onCreate() {
        super.onCreate();
        mediaPlayer = MediaPlayer.create(this, R.raw.diamonds);
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null)
            mediaPlayer.release();
    }

    private void playMusic() {
        mediaPlayer.start();
    }

    private void pauseMusic() {
        mediaPlayer.pause();
    }

    private void stopMusic() {
        mediaPlayer.stop();
    }
}

其中PlayMusicBinder继承自Binder类,并且提供了三个接口,分别是播放音乐、暂停音乐以及停止播放;在onBind()方法中返回这样一个实例。播放音乐用到了MediaPlayer,音乐文件直接直接放在了res/raw/目录下。至此,服务端就完成了,主要工具就是编写一个Binder类提供给客户端操作的接口。
从上面可以看到,定义服务端主要包括两步:
1. 扩展Binder类,实现接口
2. 在onBind()方法返回一个IBinder实例

2.定义调用者(Activity)

在定义完服务端后,再来定义客户端,bindService方法中第二个参数是一个ServiceConnection接口,当服务连接成功以及失去连接时会分别回调两个方法。下面是Activity的定义:

public class PlayMusicActivity extends AppCompatActivity {

    private PlayMusicService.PlayMusicBinder musicBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //在服务连接成功后保存PlayeMusicBinder对象,控制服务端
            musicBinder = (PlayMusicService.PlayMusicBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            musicBinder = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_play_music);
    }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this, PlayMusicService.class), connection, Service.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }

    public void playMusic(View view) {
        if (musicBinder != null)
            musicBinder.play();
    }

    public void pauseMusic(View view) {
        if (musicBinder != null)
            musicBinder.pause();
    }

    public void stopMusic(View view) {
        if (musicBinder != null)
            musicBinder.stop();
    }
}

从上面的代码可以看出,在onStart中绑定服务,一旦服务绑定成功,则保存PlayMusicBinder对象。界面上有三个按钮,分别是播放、暂停和停止,接下来就可以控制后台服务播放音乐了。
从上面可以看到,定义客户端主要有三步:
1. 定义ServiceConnection对象,在onServiceConnect回调中保存IBinder对象
2. 绑定服务
3. 使用onServiceConnect回调中保存的IBinder对象操作Service

在服务中扩展Binder这种方式适用于服务端和客户端在同一个进程中,那么如果客户端和服务端在不同进程中,那么该如何实现了?也就是通常意义上的IPC,IPC的方式有很多,比如管道、共享内存、Socket等等,但是在Android中有一种特殊的机制:Binder机制。下面首先介绍Binder机制的两种表现形式,Messenger和定义AIDL接口。下面分别就这两种方法就行介绍。

Messenger

Messenger对Binder进行了封装,适用于服务端单线程的情况,因为服务端会定义一个Handler用于顺序处理客户端发送过来的Message,在服务的onBind中返回内部的Binder。
Messenger用于发送消息,Handler用于处理消息,一般我们使用服务端作为处理消息,所以服务端需要Messenger和Handler,并且在onBind方法中返回Messenger内部的Binder,这样客户端就可以根据绑定服务后得到Messenger,可看做是服务端的Messenger,然后就可以通过它来发送消息了;那么如果服务端会在处理完客户端的消息后回复客户端,并且客户端也需要处理这个消息,那么该如何实现呢?
根据上面的分析,既然服务端需要回复客户端,也就得拿到客户端的Messenger,然后客户端需要处理消息,也需要一个Handler,所以说客户端也需要一个Messenger和一个Handler。下面我们以一个例子来详细说明,客户端向服务端发送两个整形数,服务端将和返回给客户端。

1.服务端的定义

/**
 * 使用Messenger,适合应用与服务在不同进程中
 */
public class MessengerService extends Service {

    //Handler用于处理客户端发送来的消息,不存在线程安全问题
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case 0:
                    int a = msg.arg1;
                    int b = msg.arg2;
                    int sum = a + b;
                    //得到客户端的Messenger,回复消息
                    Messenger replyTo = msg.replyTo;
                    Message message = Message.obtain();
                    message.what = 1;
                    message.arg1 = sum;
                    try {
                        replyTo.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }

        }
    };

    private Messenger messenger = new Messenger(mHandler);

    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回Messenger内部的Binder
        return messenger.getBinder();
    }
}

从上面的代码可以看到,服务端主要包括三步:
1. 定义Handler用于处理消息
2. 根据Handler声明Messenger对象
3. 在onBind方法中返回Messenger的Binder

如何将一个服务开启在另一个进程呢?
这个只需要在清单文件中注册服务时,添加一个process属性即可,如下:

<service
            android:name=".MessengerService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote" />

process属性可以为服务指定任意进程,remote前面的“:”的含义是使完整的进程名使用包名+remote。

2.客户端的定义

在定义完服务端之后,我们再来看下客户端的定义,客户端主要完成绑定服务,发送数据以及接受数据的功能。如下:

public class MainActivity extends AppCompatActivity {

    private TextView resultShowTv;
    private Messenger messenger;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {


            //得到服务端的Messenger,发送消息
            messenger = new Messenger(service);
            Message msg = Message.obtain(handler, 0, 5, 10);
            msg.replyTo = replyTo;//将客户端的Messenger对象赋给replyTo字段
           try {
                messenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resultShowTv = (TextView) findViewById(R.id.result_show_tv);

    }

    //客户端中接受消息的Messenger
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    int sum = msg.arg1;
                    resultShowTv.setText(String.valueOf(sum));
                    break;
            }
        }
    };

    private Messenger replyTo = new Messenger(handler);

    public void messenger(View view) {
        Intent bindService = new Intent(this, MessengerService.class);
        bindService(bindService, connection, BIND_AUTO_CREATE);
    }

}

从上面的代码可以看到,客户端主要有三步:
1. 编写ServiceConnection类,在onServiceConnect回调中得到服务端的Messenger,一旦得到了该对象,就发送Message对象,并且将客户端这边的Messenger带了过去
2. 编写客户端处理消息的Handler以及Messenger
3. 绑定服务

下图是效果:
Messenger
并且可以在Android Studio上看到两个进程名,如下:
进程服务
从上图可以看到,一个进程名为com.xks.binderdemo,一个进程名为com.xks.binderdemo.remote,这就是清单文件中“:”的效果。

使用AIDL

AIDL是Android Interface Define Language(Android接口定义语言)的简称。所谓接口定义,就是在通信双方定义一个规则,你可以这样发数据,我就可以接受数据。
使用AIDL时有如下三步:
1. 定义.aidl文件
在.aidl文件中编写接口
2. 实现接口
Android SDK工具会根据.aidl文件生成一个Java类,而开发者需要做的是继承其内部的一个Stub抽象类并实现.aidl中的接口
3. 向客户端公开接口
在onBind方法中返回Binder

1.定义.aidl文件

.aidl文件语法类似于Java,只能位于src/目录下,每个.aidl文件都必须定义单个接口,并且只需包含接口声明和方法签名。
默认情况下,AIDL支持以下类型:
- Java的基本类型,如int、float、double、boolean等
- String类型和Charsequence
- List
List包含元素的类型必须是AIDL支持的,并且另一端实际接收到的ArrayList类型,但生成的方法使用的是List接口
- Map
Map中的所有元素的类型必须是AIDL支持的,另一端实际接收到的是HashMap类型,但生成的方法使用的Map接口
- Parcelable类型

另外,如果需要使用非以上类型,都必须使用import语句,即使这些类型与AIDL位于同一个包中。
定义服务接口时,请注意:
- 方法可带零个或多个参数,返回值或空值
- 所有非基本类型的参数都㤇指示数据走向的方向标记。可以是in、out或inout。基本类型默认为in,不能是其他方向

注意:应该将方向限定为真正需要的方向

  • .aidl文件中包括的所有代码注释都包含在生成的IBinder接口中(import和package语句之前的注释除外)
  • 只支持方法;不能公开AIDL中的静态字段。

下面为了演示例子,首先定义一个Book类,主要有两个字段,isbn和name,实现了Parcelable接口,如下:

public class Book implements Parcelable {

    private int isbn;
    private String name;


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.isbn);
        dest.writeString(this.name);
    }

    public Book() {
    }

    protected Book(Parcel in) {
        this.isbn = in.readInt();
        this.name = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public String toString() {
        return "Book{" +
                "isbn=" + isbn +
                ", name='" + name + '\'' +
                '}';
    }
}

实现Parcelable接口的类需要在其内部提供一个CREATOR的常量,然后在src/目录下定义Book的aidl文件,如下:

// Book.aidl
package com.xks.binderdemo;

parcelable Book;

接下来,定义BookManager.aidl文件,如下:

// IMyAidlInterface.aidl
package com.xks.binderdemo;

// Declare any non-default types here with import statements

import com.xks.binderdemo.Book;

interface BookManager {

    /**
     * 打印书籍
     */
    String printBook(in Book book);

    /**
     * 添加书籍
     */
    void addBook(in Book book);

    /**
     *得到书籍数量
     */
    int getBookSize();

    /**
     * 根据索引取书
     */
    Book get(int index);

}

主要有四个接口,分别是打印书籍,添加书籍,得到书籍数量以及根据索引取书,在编写完.aidl文件后,同步一下,可以得到一个BookManager的接口,如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: E:\\Workspace\\Android\\BlogDemo\\servicedemo\\src\\main\\aidl\\com\\xks\\binderdemo\\BookManager.aidl
 */
package com.xks.binderdemo;

public interface BookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.xks.binderdemo.BookManager {
        private static final java.lang.String DESCRIPTOR = "com.xks.binderdemo.BookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.xks.binderdemo.BookManager interface,
         * generating a proxy if needed.
         */
        public static com.xks.binderdemo.BookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.xks.binderdemo.BookManager))) {
                return ((com.xks.binderdemo.BookManager) iin);
            }
            return new com.xks.binderdemo.BookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_printBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.xks.binderdemo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.xks.binderdemo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    java.lang.String _result = this.printBook(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.xks.binderdemo.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.xks.binderdemo.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getBookSize: {
                    data.enforceInterface(DESCRIPTOR);
                    int _result = this.getBookSize();
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_get: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    com.xks.binderdemo.Book _result = this.get(_arg0);
                    reply.writeNoException();
                    if ((_result != null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.xks.binderdemo.BookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * 打印书籍
             */
            @Override
            public java.lang.String printBook(com.xks.binderdemo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_printBook, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            /**
             * 添加书籍
             */
            @Override
            public void addBook(com.xks.binderdemo.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public int getBookSize() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookSize, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            /**
             * 根据索引取书
             */
            @Override
            public com.xks.binderdemo.Book get(int index) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.xks.binderdemo.Book _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(index);
                    mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = com.xks.binderdemo.Book.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_printBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getBookSize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_get = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    }

    /**
     * 打印书籍
     */
    public java.lang.String printBook(com.xks.binderdemo.Book book) throws android.os.RemoteException;

    /**
     * 添加书籍
     */
    public void addBook(com.xks.binderdemo.Book book) throws android.os.RemoteException;

    public int getBookSize() throws android.os.RemoteException;

    /**
     * 根据索引取书
     */
    public com.xks.binderdemo.Book get(int index) throws android.os.RemoteException;
}

从上面可以看到BookManager内部有一个叫做Stub的抽象类实现了BookManager接口,并且将接口方法都变成了abstract,子类必须实现这些方法,至于其他Binde通信的细节,Android都已经帮我们实现了。
Stub中有个adInterface的方法,该方法主要用于客户端从IBinder对象得到BookManager接口,这样就可以通过该接口与服务端通信。

实现接口

在定义完接口后,在BookService中实现BookManager.Stub类,实现那几个抽象方法,如下:

public class BookService extends Service {

    private List<Book> bookList = new ArrayList<>();

    public class BookBinder extends BookManager.Stub {
        @Override
        public String printBook(Book book) throws RemoteException {
            return book.toString();
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            bookList.add(book);
        }

        @Override
        public int getBookSize() throws RemoteException {
            return bookList.size();
        }

        @Override
        public Book get(int index) throws RemoteException {
            return bookList.get(index);
        }
    }

    private BookBinder bookBinder = new BookBinder();


    public BookService() {
    }

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

可以看到BookService的实现很简单,与单纯使用Binder类似,只不过这里继承的是BookManager.Stub,而不是Binder。最后在onBind方法中返回声明的对象即可。

3.向客户端公开接口

客户端这里使用的是Activity,界面上有四个按钮,分别对应于四个方法,最下面有个TextView用于显示信息,Activity的实现如下:

public class BookActivity extends AppCompatActivity {

    private TextView infoShowTv;

    private BookManager bookManager;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = BookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
        }
    };

    private Book[] books = {
            new Book(1, "Java编程思想"),
            new Book(2, "第一行代码"),
            new Book(3, "Anroid开发艺术探索")
    };

    private int index = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book);

        infoShowTv = (TextView) findViewById(R.id.info_show_tv);

    }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this, BookService.class), connection, Service.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }

    /**
     * 添加书籍
     *
     * @param view
     */
    public void addBook(View view) {

        if (bookManager != null)
            try {
                bookManager.addBook(books[index++ % books.length]);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

    }

    public void printBook(View view) {
        if (bookManager != null)
            try {
                infoShowTv.setText(bookManager.printBook(books[index % books.length]));
            } catch (RemoteException e) {
                e.printStackTrace();
            }

    }

    public void getSize(View view) {
        if (bookManager != null)
            try {
                infoShowTv.setText(String.valueOf(bookManager.getBookSize()));
            } catch (RemoteException e) {
                e.printStackTrace();
            }

    }

    public void getFirst(View view) {
        if (bookManager != null)
            try {
                infoShowTv.setText(bookManager.printBook(bookManager.get(0)));
            } catch (RemoteException e) {
                e.printStackTrace();
            }

    }
}

下图是效果:
AIDL效果图
上图中,首先获取List的尺寸为0,然后加入一本书后,尺寸就变成了1,然后打印,继续加书获取尺寸。
使用AIDL的时候,需要注意的是,调用服务端的方法都是同步调用的,所以如果说如果操作很耗时的话,那么客户端调用需要放到线程中操作;另外,服务端需要注意线程安全问题,因为多个客户端调用时会有并发问题,而Messenger内部就不需要了,因为Handler确保了消息的同步性。

总结

从上面可以看到,绑定服务时,如果服务和客户端在同一个进程中,那么直接在服务端扩展Binder即可;如果服务和客户端不在同一个进程中,那么可以使用Messenger或AIDL。Messenger是进行IPC的最简单方式,并且Android用Handler帮我们实现了一个队列,不需要考虑线程安全问题;而使用AIDL的方式则更加通用,需要开发者自己考虑线程安全问题。
对于进程间通信或客户端和服务端在同一个进程中,底层使用的都是Binder。可以认为只有当客户端得到服务端Binder的一个句柄后,才可以通过该句柄来控制服务端,与服务端进行通信。对上述三种情况分别为:
- 客户端和服务在同一个进程中,客户端在ServiceConnection的onServiceConnected中得到服务端的Binder对象
- 使用Messenger时,客户端同样在ServiceConnection的onServiceConnect回调中得到服务端的Messenger,然后使用Messenger发送消息
- 使用AIDL时,与客户端和服务在同一个进程中一样

本文的代码可以查看我的Github地址

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值