Android中的IPC方式

194 阅读5分钟

一、使用Intent

  1. Activity、Service,Receiver都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,可以在不同的进程间进行传输。
  2. 在一个进程中启动了另外一个进程的Activity、Service或Receiver,可以在Bundle中重复加要传递的数据通过Intent发送出去。


二、使用文件共享

  1. 可以通过序列化来实现文件共享,即一个进程序列化一个对象到文件系统,另外一个进程反序列化恢复该对象(但这两个对象不一样,只是内容一样);
  2. SharedPreferences是个特例,系统对他的读/写有一定的缓存策略,即内存会有一份SharedPreferences文件的缓存,系统对他的读/写就变得不靠谱,在高并发的读写访问中,SharedPreferences有很大的几率丢失数据,因而IPC不建议采用SharedPreferences。


三、使用Messenger

Messenger是一种轻量级的IPC方案,它底层实现是AIDL,可以在不同进程中传递Message对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情况。

  • 服务端进程:服务端创建一个Service来处理客户端请求,同时通过一个Handler对象来实例化一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
public class MessengerService extends Service {

    private static final String TAG = MessengerService.class.getSimpleName();

    private class MessengerHandler extends Handler {

        /**
         * @param msg
         */
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case Constants.MSG_FROM_CLIENT:
                    Log.d(TAG, "receive msg from client: msg = [" + 
                                  msg.getData().getString(Constants.MSG_KEY) + "]");
                    Toast.makeText(MessengerService.this, "receive msg from client: msg = [" + 
                                  msg.getData().getString(Constants.MSG_KEY) + "]", 
                                        Toast.LENGTH_SHORT).show();
                    Messenger client = msg.replyTo;
                    Message replyMsg = Message.obtain(null, Constants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");
                    replyMsg.setData(bundle);
                    try {
                        client.send(replyMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

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


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
  • 客户端进程:首先绑定服务器Service,绑定成功后用服务器的IBinder对象创建一个Messenger,通过这个Messenger可以向服务器发送消息,消息类型是Message。如果需要服务器响应,则需要创建一个Handler并通过它来创建一个Messenger(和服务器一样),并通过Message的replyTo参数传递给服务器。服务器通过Message的replyTo参数可以回应客户端。
public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
    private Messenger mService;

    private class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constants.MSG_FROM_SERVICE:
                    Log.d(TAG, "received msg form service: msg = [" + 
                                msg.getData().getString(Constants.MSG_KEY) + "]");
                    Toast.makeText(MainActivity.this, "received msg form service: msg = [" + 
                                msg.getData().getString(Constants.MSG_KEY) + "]", 
                                       Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }



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

    }

    public void bindService(View v) {
        Intent mIntent = new Intent(this, MessengerService.class);
        bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void sendMessage(View v) {
        Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
        Bundle data = new Bundle();
        data.putString(Constants.MSG_KEY, "Hello! This is client.");
        msg.setData(data);
        msg.replyTo = mGetReplyMessenger;
        try {
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

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

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        /**
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString(Constants.MSG_KEY, "Hello! This is client.");
            msg.setData(data);
            //
            msg.replyTo = mGetReplyMessenger;
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        /**
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {


        }
    };
}

注意:

客户端和服务端是通过拿到对方的Messenger来发送Message的。只不过客户端通过bindService,onServiceConnected,而服务端通过message.replayTo来获取对方的Messenger。Messenger中有一个Handler一串行的方式处理队列的消息,不存在并发执行,因而我们不用考虑线程同步的问题。


四、使用AIDL

Messenger是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端不可能一个一个进行处理,因为效率低,所以大量并发请求不适合用Messenger,而且Messenger只适合传递消息,不能跨进程调用服务端的方法。AIDL可以解决并发跨进程调用方法的问题。要知道Messenger本质上也是AIDL,只不过系统做了封装让上层的调用。


AIDL文件支持的数据类型:

  • 基本数据类型
  • String和CharSequence
  • ArrayList,里面的元素必须能够被AIDL支持
  • HashMap,里面的元素必须能够被AIDL支持
  • Parcelable,实现Parcelable接口的对象。注意:如果AIDL文件中用到了自定义的Parcelable对象,必须创建一个和它同名的AIDL文件
  • AIDL,AIDL接口本身也可以在AIDL文件中使用。


服务端

服务端创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。


客户端

绑定服务端的Service,绑定成功后,将服务器返回的Binder对象转成AIDL接口所属的类型,然后就可以调用AIDL中的方法。客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较好使,就会导致客户端线程长时间阻塞,导致ANR,客户端的onServiceConntected和onServiceDisconnected方法都在UI线程中


五、使用ContentProvider

用于在不同应用间数据共享,和Messenger底层实现同样是Binder和AIDL,系统做了封装,使用功能简单。

系统预置了许多ContentProvider,如通讯录、日程表,需要跨进程访问。使用方法:继承ContentProvider类实现6个抽象方法,这六个方法均运行在ContentProvider进程中,除onCreate运行在主进程中其余五个方法均由外界回调运行在Binder线程池中。

ContentProvider的底层数据,可以是SQLite数据库,也可以使文件,也可以是内存中的数据。


六、使用Socket

网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

Socket本身可以传输任意字节流。