likes
comments
collection
share

Android 进程间通信

作者站长头像
站长
· 阅读数 19

前言

IPC(Inter-Process Communication)为进程间通信或跨进程通信,是指两个进程之间进行间通信的过程。在Android 中,为每个进程都分配了一个独立的虚拟机,不同虚拟机在内存分配上都有不同的地址空间,互相访问数据需要借助其他手段。下面介绍在 Android 中实现 IPC 的方式。

1 使用 Bundle 的方式

我们知道在 Android 中三大组件(Activity,Service,Receiver)都支持在 Intent 中传递 Bundle 数据,由于 Bundle 实现了 Parceable 接口,所以它可以很方便的在不同的进程之间进行传输。当我们在一个进程中启动另外一个进程的 Activity,Service,Receiver 时,我们就可以在 Bundle 中附加我们所需要传输给远程的进程的信息,并且通过 Intent 发送出去。这里注意:我们传输的数据必须基本数据类型或者能够被序列化。

  • 基本数据类型(int, long, char, boolean, double等)
  • String 和 CharSequence
  • List:只支持 ArrayList,并且里面的元素都能被 AIDL 支持
  • Map:只支持 HashMap,里面的每个元素都能被 AIDL 支持
  • Parcelable:所有实现 Parcelable 接口的对象

下面是利用 Bundle 进行进程间通信的示例:

Intent intent = new Intent(MainActivity.this, TwoActivity.class);
Bundle bundle = new Bundle();
bundle.putString("data", "test");
intent.putExtras(bundle);
startActivity(intent);

利用 Bundle 进行进程间通信只能是单方向的简单数据传输,它使用时有一定的局限性。

2 使用文件共享的方式

共享文件也是以后不错的进程间通信的方式,两个进程通过读/写同一个文件来交换数据,比如进程A把数据写入到文件 File 中,然后进程B就可以通过读取这个文件来获取这个数据。通过这种方式,除了可以交换简单的文本信息之外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。 举个例子: 在A进程中创建一个线程进行写数据:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //Custom Java Bean
                User user = new User("ZhangSan", 15, false);
                File cachedFile = new File(CACHE_FILE_PATH);
                try {
                    FileOutputStream fileOutputStream = new FileOutputStream(cachedFile);
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
                    objectOutputStream.writeObject(user);

                    objectOutputStream.close();
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

在B进程中创建一个线程进行读取数据:

        new Thread(new Runnable() {
            @Override
            public void run() {
                User user = null;
                File cachedFile = new File(CACHE_FILE_PATH);
                if (cachedFile.exists()) {
                    try {
                        FileInputStream fileInputStream = new FileInputStream(cachedFile);
                        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
                        user = (User) objectInputStream.readObject();

                        objectInputStream.close();
                        fileInputStream.close();
                    } catch (IOException | ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

通过文件共享的这种方式来共享数据对文件的格式是有具体要求的,比如可以是文本文件,也可以是 XML 文件,只要读写双方约定数据格式即可。这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,比如读取的数据不完整或者读取的数据不是最新的。因此通过文件共享的方式适合在数据同步要求不高的进程间通信,并且要妥善处理并发读/写问题。

3 使用 Messenger 的方式

我们也可以通过 Messenger 来进行进程间通信,在 Messenger 中放入我们需要传递的数据,就可以轻松的实现进程之间数据传递了。Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL,关于 AIDL 在下面会介绍到。 实现一个 Messenger 有以下几步,分为服务器端和客户端: 服务器进程:在A进程创建一个 Service 来处理其他进程的连接请求,同时创建一个 Handler 并通过它来创建一个 Messenger 对象,然后在 Service 的 onBind() 中返回这个 Messenger 对象底层的 Binder 即可。

public class MessengerService extends Service{
    private Handler messengerHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //取出客户端的消息内容
            Bundle bundle = msg.getData();
            String clientMsg = bundle.getString("client");
            Log.i(TAG, "来自客户端的消息:" + clientMsg);
            //新建一个Message对象,作为回复客户端的对象
            Message message = Message.obtain();
            Bundle bundle1 = new Bundle();
            bundle1.putString("service", "服务端收到");
            message.setData(bundle1);
            try {
                msg.replyTo.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };
    
    //创建服务端Messenger
    private final Messenger mMessenger = new Messenger(messengerHandler);
        
    @Override
    public IBinder onBind(Intent intent) {
        //向客户端返回IBinder对象,客户端利用该对象访问服务端
        return mMessenger.getBinder();
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
    }
}

客户端进程:在进程B中首先绑定远程进程 Service,绑定成功后,根据 Service 返回的 IBinder 对象创建 Messenger 对象,并使用此对象发送消息,为了能收到 Service 端返回的消息,客户端也创建了一个自己的 Messenger 发送给 Service 端,Service 端就可以通过客户端的 Messenger 向客户端发送消息了,具体的实现代码如下:

public class MessengerActivity extends Activity{  
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //获取服务端关联的Messenger对象
            Messenger mService = new Messenger(service);
            //创建Message对象
            Message message = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("client", "服务端在吗,听到请回答");
            message.setData(bundle);
            //在message中添加一个回复mReplyMessenger对象
            message.replyTo = mReplyMessenger;
            try {
                mService.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    //为了收到Service的回复,客户端需要创建一个接收消息的Messenger和Handler  
    private Handler messengerHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //消息处理
            Bundle bundle = msg.getData();
            String serviceMsg = bundle.getString("service");
            Log.i(TAG, "来自服务端的回复:" + serviceMsg);
        }
    };

    private Messenger mReplyMessenger = new Messenger(messengerHandler);

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

    private void init() {
        Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

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

Messenger 内部消息处理使用 Handler 实现的,所以它是以串行的方式处理客服端发送过来的消息的,如果有大量的消息发送给服务器端,服务器端只能一个一个处理,如果并发量大的话用 Messenger 就不合适了,而且 Messenger 的主要作用就是为了传递消息,很多时候我们需要跨进程调用服务器端的方法,这种需求 Messenger 就无法做到了。

4 使用 AIDL 的方式

5 使用 ContentProvider 的方式

ContentProvider(内容提供者)是 Android 中的四大组件之一,为了在应用程序之间进行数据交换,Android 提供了 ContentProvider,ContentProvider 是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider 暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作。ContentProvider 分为系统的和自定义的,系统的(例如:联系人,图片等数据)。 开发一个 ContentProvider 的步骤如下:

  1. 定义自己的 ContentProvider 类,该类继承 ContentProvider 基类;
  2. 在 AndroidMainfest.xml 中注册这个 ContentProvider,类似于 Activity 注册,注册时要给 ContentProvider 绑定一个域名;
  3. 当我们注册好这个 ContentProvider 后,其他应用就可以访问 ContentProvider 暴露出来的数据了。

ContentProvider 只是暴露出来可供其他应用操作的数据,其他应用则需要通过 ContentProvider 来操作ContentProvider 所暴露出来的数据。Content提供了 getContentResolver() 方法来获取 ContentProvider 对象,获取之后皆可以对暴露出来的数据进行增、删、改、查操作了。 使用 ContentResolver 操作数据的步骤如下:

  1. 调用 Activity 的 getContentResolver() 获取 ContentResolver 对象;
  2. 根据调用的 ContentResolver 的 insert()、delete()、update() 和 query() 方法操作数据库即可。

6 使用 BroadcastReceiver 的方式

广播是一种被动跨进程通信方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就像电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。 BroadcastReceiver 本质上是一个系统级的监听器,它专门监听各个程序发出的 Broadcast,因此它拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcastReceivert 总会被激发。我们知道,只要注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为是将自己感兴趣的 IntentFilter 注册到 Android 系统的AMS(ActivityManagerService)中,里面保存了一个 IntentFilter 列表。广播发送者将 IntentFilter 的 action 行为发送到 AMS 中,然后遍历 AMS 中的 IntentFilter 列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应的 IntentFilter 或者 Service 中,也就是说:会调用抽象方法 onReceive() 方法。其中 AMS 起到了中间桥梁的作用。 程序启动 BroadcastReceiver 只需要两步:

  1. 创建需要启动的 BroadcastReceivert 的 intent;
  2. 调用 Context 的 sendBroadcast() 或者 sendOrderBroadcast() 方法来启动指定的 BroadcastReceivert。

每当 Broadcast 事件发生后,系统会创建对应的 BroadcastReceiver 实例,并自动触发 onReceiver() 方法,onReceiver() 方法执行完后,BroadcastReceiver 实例就会被销毁。

注意:onReceiver() 方法中尽量不要做耗时操作,如果 onReceiver() 方法不能再10秒之内完成事件的处理,Android 会认为该进程无响应,也就弹出我们熟悉的 ANR 对话框。如果我们需要在接收到广播消息后进行耗时的操作,我们可以考虑通过 Intent 启动一个 Server来完成操作,不应该启动一个新线程来完成操作,因为BroadcastReceiver 生命周期很短,可能新建线程还没有执行完,BroadcastReceivert 已经销毁了,而如果 BroadcastReceivert 结束了,它所在的进程中虽然还有启动的新线程执行任务,可是由于该进程中已经没有任何组件,因此系统会在内存紧张的情况下回收该进程,这就导致 BroadcastReceivert 启动的子线程不能执行完成。

7 使用 Socket 的方式

Socket 也是实现进程间通信的一种方式,Socket 也称为“套接字”,网络通信中的概念,通过 Socket 我们可以很方便的进行网络通信,都可以实现网络通信录,那么也能实现跨进程通信,但是 Socket 主要还是应用在网络通信中。