Android中的IPC方式

内容和案例参考自《Android开发艺术探索》

Android中的IPC方式

  1. 通过在Intent中附加extras(Bundle)来传递信息
  2. 通过共享文件、SharedPreference的方式共享数据
  3. Messenger方式(AIDL、Binder)跨进程通信
  4. ContentProvider(Binder)实现跨进程访问
  5. Socket实现IPC

一、 Bundle

  1. 四大组件中的三大组件(Activity、Service、Receiver)均支持在Intent中传递Bundle数据
  2. Bundle实现了Parcelable接口,因此可以跨进程通信
  3. 传输的数据必须要能够被序列化:基本类型、实现了Parcelable接口的对象、实现了Serializable接口的对象及一些Android支持的特殊对象。

二、 文件共享

  1. 使用文件,通过ObjectInputStream、ObjectOutputStream输入输出数据;不适合并发场景。
  2. 使用SharedPreferences,SharedPreferences也属于文件的一种,但是系统对它的读写有一定的缓存策略,即内存中会有一份SharedPreferences文件的缓存。

三、 Binder

3.1 Messenger

  • Messenger的特点:以串行的方式处理客户端发来的消息。主要用于传递消息,不能做到跨进程调用服务端的方法。
  • 利用Messenger使得客户端与服务端能相互通信:
  • 定义或声明客户端与服务端的成员。
    1. 创建服务,在AndroidManifest.xml文件中注册服务。
    2. 客户端:声明mServiceMessenger
    3. 客户端:创建mClientHandlerhandleMessage()已重写)。
    4. 客户端:创建mClientMessenger(通过mClientHandler初始化)。
    5. 服务端:声明mClientMessenger
    6. 服务端:创建mServiceHandlerhandleMessage()已重写)。
    7. 服务端:创建mServiceMessenger(通过mClientHandler初始化)。
  • 进程通信时客户端方法的执行
    1. 客户端:利用onServiceConnected()中的参数Binder初始化mServiceMessenger
    2. 客户端:创建Message对象,并设置what、data、replyTo(设置为mClientMessenger)参数。
    3. 客户端:通过mServiceMessenger.send(Message)将Message发送给服务端。
  • 进程通信时服务端方法的执行。
    1. 服务端:在handleMessage()中获取并输出Message中data的数据;获取Message中replyTo的Messenger并初始化mClientMessenger
    2. 服务端:通过mClientMessenger.send(Message)回复Message给客户端。
// 客户端:MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    // 创建Messenger对象
    private Messenger mService;

    private Messenger mReplyMessenger = new Messenger(new MessengerHandler());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... 省略
    }

    private void sendMessage() {
        Message msg = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
        Bundle data = new Bundle();
        data.putString("msg", mEtMsg.getText().toString());
        msg.setData(data);
        // 将信使Messenger传递给Service
        msg.replyTo = mReplyMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void bind() {
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    private void unbind() {
        unbindService(mConnection);
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 利用onBind()返回的Binder创建Messenger
            mService = new Messenger(service);
            Log.d(TAG, "onServiceConnected: 服务已绑定");
        }

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

    @Override
    public void onClick(View v) {
        // ... 省略
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MessengerService.MSG_FROM_SERVICE:
                    Log.d(TAG, "receive message from service: " + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}
// 服务端:MessengerService.java
public class MessengerService extends Service {

    public static final String TAG = "MessengerService";

    public static final int MSG_FROM_CLIENT = 0;
    public static final int MSG_FROM_SERVICE = 1;


    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_FROM_CLIENT:
                    // 打印客户端消息
                    Log.d(TAG, "handleMessage: receive message from Client : " + msg.getData().getString("msg"));

                    // 回复消息给客户端
                    Messenger client = msg.replyTo;
                    Message replyMsg = Message.obtain(null, MSG_FROM_SERVICE);
                    Bundle data = new Bundle();
                    data.putString("reply", "消息已收到");
                    replyMsg.setData(data);
                    try {
                        client.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

3.2 AIDL

  1. 服务端:
    • 创建Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service实现这个AIDL。
  2. 客户端:
    • 绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接下来就可以调用AIDL中的方法。
  3. AIDL接口中支持的数据类型:
    • 基本数据类型;
    • StringCharSequence
    • List:只支持ArrayList,里面的每个元素必须能够被AIDL支持;
    • Map:只支持HashMap,里面的每个元素必须能够被AIDL支持,包括key和value;
    • Parcelable:所有实现了Parcelable接口的对象(需导入);
    • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用(需导入);
    • 注意:
      • 自定义的Parcelable对象和AIDL必须要显式import进来;
      • 如果AIDL中用到了自定义Parcelable对象,必须新建一个与其同名的AIDL,并在其中声明它为Parcelable对象;
      • AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,不能一概使用out或者inout,因为底层实现有开销;
      • AIDL的包结构在服务端和客户端要保持一致,哪怕客户端是另外一个应用;
  4. AIDL实现客户端与服务端通信
    • AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接会存在多线程同时访问的情况,所以需要在AIDL方法中处理线程同步;
    • 当服务端返回CopyOnWriteArrayListConcurrentHashMap时,Binder会按照List、Map的规范去访问数据并最终形成ArrayListHashMap返回给客户端;
    • onNewBookArrivied方法在客户端的Binder线程池中执行,为了便于UI操作,我们需要通过Handler切换到客户端的主线程中去执行;
    • Binder会把客户端传递过来的对象重新转化并生成一个新的对象;服务端存储客户端的监听对象时,不能用普通的List,应使用RemoteCallbackList
    • RemoteCallbackList是一个泛型,支持管理任意的AIDL接口;客户端进程终止后,它能够自动移除客户端所注册的listener,另外,其内部自动实现了线程同步的功能;
    • RemoteCallbackListbeginBroadcatfinihBroadcat必须要配对使用,哪怕仅仅是获取RemoteCallbackList中的元素个数。
    • 注意:
      • 客户端调用远程服务的方法,被调用的方法运行在客户端的Binder线程池中,同时客户端线程会被挂起,因此调用客户端调用服务端的耗时方法时,不能在客户端的UI线程;
      • 发起远程调用请求的方法需考虑ANR问题;
      • 服务端远程调用客户端的方法时,被调用的方法运行在客户端的Binder线程池中,因此被调用的方法不能操作UI线程,需要使用Handler将操作切换到UI线程;
      • 服务端的方法本身运行在服务端的Binder线程池中,服务端方法本身就可以执行大量耗时操作,此时无需在服务端方法中开线程进行异步任务,除非你明确知道自己在做什么。
  5. Binder意外死亡时重新连接服务:
    • 第一种方法是给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到;binderDied方法的回调,在binderDied方法中我们可以重新连接远程服务
    • 第二种方法是在onServiceDisconnected中重新连接远程服务;
    • 区别是onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调。
// Book.aidl
package com.slim.ipcaidlobserver;

parcelable Book;
// IOnNewBookArrivedListener.aidl
package com.slim.ipcaidlobserver;

import com.slim.ipcaidlobserver.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}
// IBookManager.aidl
package com.slim.ipcaidlobserver;

import com.slim.ipcaidlobserver.Book;
import com.slim.ipcaidlobserver.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
// Book.java
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    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];
        }
    };

    private Book(Parcel source) {
        bookId = source.readInt();
        bookName = source.readString();
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}
// BookManagerService.java
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
            Log.d(TAG, "书籍添加成功");
            onNewBookArrived(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    };

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

    public void onNewBookArrived(Book book) {
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
                if (listener != null) {
                    listener.onNewBookArrived(book);
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mListenerList.finishBroadcast();
    }
}
// MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = "MainActivity";

    private static final int MESSAGE_BOOK_ARRIVED = 1;

    private EditText mEtBookId;
    private EditText mEtBookName;
    private Button mBtBookAdd;
    private Button mBtBookList;
    private Button mBtBind;
    private Button mBtUnbind;
    private Button mBtRegister;
    private Button mBtUnregister;

    private IBookManager mBookManager;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_BOOK_ARRIVED:
                    Log.d(TAG, "receive new book: " + msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEtBookId = (EditText) findViewById(R.id.et_book_id);
        mEtBookName = (EditText) findViewById(R.id.et_book_name);
        mBtBookAdd = (Button) findViewById(R.id.bt_book_add);
        mBtBookList = (Button) findViewById(R.id.bt_book_list);
        mBtBind = (Button) findViewById(R.id.bt_bind);
        mBtUnbind = (Button) findViewById(R.id.bt_unbind);
        mBtRegister = (Button) findViewById(R.id.bt_register);
        mBtUnregister = (Button) findViewById(R.id.bt_unregister);

        mBtBookAdd.setOnClickListener(this);
        mBtBookList.setOnClickListener(this);
        mBtBind.setOnClickListener(this);
        mBtUnbind.setOnClickListener(this);
        mBtRegister.setOnClickListener(this);
        mBtUnregister.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_book_add:
                addBook();
                break;
            case R.id.bt_book_list:
                printBookList();
                break;
            case R.id.bt_bind:
                bind();
                break;
            case R.id.bt_unbind:
                unbind();
                break;
            case R.id.bt_register:
                register();
                break;
            case R.id.bt_unregister:
                unregister();
                break;
            default:
                break;
        }
    }

    public void addBook() {
        if (mBookManager == null) { return; }
        try {
            int bookId = Integer.parseInt(mEtBookId.getText().toString());
            String bookName = mEtBookName.getText().toString();
            mBookManager.addBook(new Book(bookId, bookName));
        } catch (RemoteException | NumberFormatException e) {
            e.printStackTrace();
        }
    }

    public void printBookList(){
        if (mBookManager == null) { return; }
        List<Book> bookList;
        try {
            bookList = mBookManager.getBookList();
            Log.d(TAG, "query book list: " + bookList.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: 服务已绑定");
            mBookManager = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBookManager = null;
            // 服务意外死亡时执行此方法,在此处设置重新连接至服务
        }
    };

    public void bind() {
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    public void unbind() {
        unbindService(mConnection);
    }

    public void register(){
        try {
            mBookManager.registerListener(mListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void unregister(){
        try {
            mBookManager.unregisterListener(mListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

IPC方式的优缺点和适用场景

名称优点缺点适用场景
Bundle简单易用只能传输Bundle支持的数据类型四大组件间的进程间通信
文件共享简单易用不适合高并发场景,并且无法做到进程间的即时通信无并发访问情形,交换简单的数据实时性不高的场景
AIDL功能强大,支持一对多并发通信,支持实时通信使用稍复杂,需要处理好线程同步一对多通信且有RPC需求
Messenger功能一般,支持一对多串行通信,支持实时通讯不能很好的处理高并发情况,不支持RPC,数据通过Messenger进行传输,因此只能传输Bundle支持的数据类型低并发的一对多即时通信,无RPC需求,或无需要返回结果的RPC需求
ContentProvider在数据访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作可以理解为受约束的AIDL,主要提供数据源的CRUD操作一对多的进程间的数据共享
Socket功能强大,可以通过网络传输字节流,支持一对多并发实时通讯实现细节稍微有点繁琐,不支持直接的RPC网络数据交换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值