IPC(中)-进程间通讯方式详解

本文深入解析Android中的多种IPC通信方式,包括使用Bundle、文件共享、Messenger和AIDL。详细介绍了每种方式的工作原理、优缺点及使用场景,并提供了具体的示例代码。特别地,对AIDL的创建、服务端与客户端实现进行了详细阐述,包括参数类型限制、数据流向以及并发处理。此外,还探讨了Messenger与AIDL的关系以及在多线程和权限验证中的注意事项。
摘要由CSDN通过智能技术生成

IPC(中)

1 Android中IPC方式

在第一篇IPC(上)中我们已经介绍了IPC的基础知识:序列化和Binder,本篇将详细介绍各种跨进程通讯方式.具体有如下几种:

  • Intent中extras传递

  • 共享文件

  • Binder

  • ContentProvider

  • Socket

1.1 Bundle

四大组件中的三大组件(Activity,Service,Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以他可以方便在不同进程间传输,所以在我们开启另一个进程的Activity,Service,Receiver时候,就可以使用Bundle的方式来,但是有一点需要注意,我们在Bundle中的数据必须可以被序列化,比如基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象等等,具体支持类型如下

如果是Bundle不支持的类型我们无法通过它在进程间通讯.但有的时候可以适当改变下实现方式来解决问题,比如A进程进行计算,得到结果后给到B进程,但是结果的数据类型Bundle不支持传递,那么这个时候我们可以将计算过程放在B进程的后台服务中,然后当需要计算的时候A进程通过Intent告知B进程的Service开始计算了,由于Service在B进程所以可以很方便的拿到数据,这样就成功避免了进程间通讯的问题.

1.2 使用文件共享

共享文件也是一种进程间通讯的方式,两个进程通过读/写同一个文件来交换数据,交换信息除了文本信息外,还可以序列化对象到文件在从另一个进程中读取这个对象,但是有一点需要注意,Android基于Linux,并发读/写文件没有限制,当两个线程同时写文件的时候可能会出现问题,这里尤其需要注意.下面是序列化对象到文件共享数据的栗子

这次我们在MainActivity中,序列化一个User对象到文件,在另一个进程运行的SecondActivity中反序列化,看对象的属性值是否相同.

// MainActivity
public void serialize(View v) {
        User user = new User("zhuliyuan", 22);
        try {

            File file = new File(getCacheDir(), "user.txt");
            FileOutputStream fos = new FileOutputStream(file);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(user);

            fos.close();
            oos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

//SecondActivity
public void deserialize(View v) {
        File file = new File(getCacheDir(), "user.txt");
        if (file.exists()) {
            try {
                FileInputStream fis = new FileInputStream(file);
                ObjectInputStream ois = new ObjectInputStream(fis);
                User user = (User) ois.readObject();

                fis.close();
                ois.close();

                Log.i("yyjun", user.toString());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

下面看下日志

可以发现SecondActivity成功恢复了User数据,这里虽然数据相同但是和之前MainActivity的User对象并不是同一个.

通过文本共享这种方式共享数据对文本格式没有要求,只要双方按约定好格式即可.但是也有局限性当并发读/写的时候读取的文件可能不是最新的,如果并发写就更加的严重了,要尽量避免这种情况或者使用线程同步来限制并发.通过上面分析我们可以知道,文件共享方式适合在对数据同步要求不高的进程间进行通信,并且需要妥善处理并发问题.

当然SharedPreferences是个特例,它通过键值对方式存储数据,在底层上采用xml文件来存储键值对,一般情况每个应用的SharedPreferences文件目录位于/data/data/package name/shared_prefs目录下.从本质上来说SharedPreferences也是属于文件的一种,但是由于系统对它的读写有一定的缓存策略,所以内存中会有一份SharedPreferences缓存,而在多进程模式下,系统对他读写变得不可靠,当高并发时候有很大几率丢失数据,因为,在多进程通讯的时候最好不要使用SharedPreferences.

api文档中也明确指出了这一点

1.3 使用Messenger

Messenger可以翻译为信使,通过它可以在不同进程中传递Message对象,在Message中放 入我们想传递的数据,就可以实现数据的进程间传递了.Messenger底层实现是AIDL,这个可以通过构造方法初见端倪

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

是不是可以明显看出AIDL的痕迹,既然这么像那我们就通过源码来分析下,车总是要发的不过且慢,我觉得咱们先看下Messenger的栗子热热身,在来分析更好

实现步骤如下

  1. 服务端进程

    首先在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handle并通过它来创建一个Messenger对象,然后在Service中的onBind中返回这个Messenger对象底层的Binder即可

  2. 客户端进程

    客户端进程中,首先需要绑定服务端service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger向服务端发送消息,发送的消息类型为Message对象,如果需要服务端能够回应客户端,就必须和服务端一样,创建一个Handle并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端就可以通过replyTo参数回应客户端.

下面先来个简单的栗子,此栗中服务端无法回应客户端

在service中创建一个handle,然后new一个Messenger将Handle作为参数传入,再在onBind方法中返回Messenger底层的Binder

public class MessengerService extends Service {
   

    private static final String TAG = "MessengerService";

    private static class MessengerHandler extends Handler {
   
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constants.TYPE_MSG_FROM_CLIENT:
                    Log.i(TAG, "receiver client msg " + msg.getData().getString("msg"));
                    break;
            }
        }
    }

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

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

在注册service让其在单独的进程

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

接下来客户端实现,先绑定MessengerService服务,在根据服务端返回的Binder对象创建Messenger并使用此对象向服务端发送消息.

public class MainActivity extends AppCompatActivity {
   

    private static final String TAG = "MainActivity";

    private Messenger mMessenger;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMessenger = new Messenger(service);
            Message msg = Message.obtain(null, Constants.TYPE_MSG_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "this is client msg");
            msg.setData(bundle);
            try {
                mMessenger.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);

        bindService(new Intent(this, MessengerService.class), mConnection, BIND_AUTO_CREATE);
    }

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

最后运行,看一下日志,很显然服务端收到了客户端的消息

01-18 09:49:01.823 31013-31013/? I/MessengerService: receiver client msg this is client msg

通过上面栗子可以看出,Messenger中进行数据传输必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输.简单来说Message中所支持的数据类型就是Messenger支持的数据类型,而Message中能作为载体传送数据的只有what,arg1,arg2,obj,replyTo,而obj在同一进程中是很实用的,但是进程间通讯的时候,在Android2.2以前obj不支持跨进程传递,2.2以后仅仅支持系统实现的Parcelable接口的对象才能通过它来传递,也就等于我们自定义的类即使实现了parcelable也无法通过obj传递,但是不要方,我们还有Bundle可以支持大量的数据传递.

具体在Message的obj字段的注释可以窥探一二.

可以看到在跨进程传递的时候,obj只支持非空的系统实现Parcelable接口的数据,要想传递其他数据使用setData,也就是Bundle方式,Bundle中可以支持大量的数据类型.

上面只能客户端向服务端发送信息,但有的时候我们还需要能够回应客户端,下面就介绍如何实现这种效果.还是上面的栗子只是稍微改下,当服务端接受到客户端消息后回复客户端接受成功.

首先我们修改下客户端,为了接受服务端发送消息,客户端也需要一个接受消息的Messenger和Handler

private Messenger clientMessenger = new Messenger(new ClientHandler());

private static class ClientHandler extends Handler {
   
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case Constants.TYPE_MSG_FROM_SERVICE:
                Log.i(TAG, msg.getData().getString("reply"));
                break;
        }
    }
}

还有一点就是客户端发送消息的时候,需要把接受服务端回复的messenger通过message的reply带到服务端

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值