IPC机制分析和AIDL的使用

什么是IPC

inter-process Communication;翻译过来就是进程间通信

首先要理解什么是进程

进程是pc端或者移动端上的一个程序或者应用。一个进程可以包含多个线程,这是一个包含和被包含的关系。

什么时候需要使用多进程:

举个简单的例子,我们平时经常用的获取联系人,获取手机里的照片,这都是进程间的通信,只是这个通信的两个进程是我们的应用和系统的应用。再比如说,音乐播放器的后台播放,当你把显示播放信息的Activity全部杀死之后,还是能够继续播放,这时候后台实际上是还有另外一个进程在播放音乐,两个进程的生命是互补干扰的。这样就可以实现后台播放了。

还有一点就是当我们的应用需要实现的功能很复杂,需要大内存的时候,android系统为每个进程分配的内存是有限的,这时候就需要使用多进程来获得更多的内存使用

为什么会存在进程间通信?

线程间通信实现起来比进程间通信简单得多,最简单的使用一个handler就可以实现,为什么google不把handler机制使用在进程间通信上呢。这个问题我们需要从android的运行机制上分析了。android为每一个进程分配了一个独立的JVM,不同的JVM有不同的内存地址,这样会导致不同的进程访问同一个类的对象会产生新的副本,既然他们不能通过简单的内存共享来传递数据,那么必然涉及到进程间通信。

进程间通信的方式:

很多人第一答案就是AIDL,显然,AIDL是进程间通信的一种方式。但是android系统为我们提供了各种进程间通信的方式来适应不同的需要。

下面就来总结一下IPC实现的方式:

1.bundle,通过intent传递一些信息就可以实现进程间通信----这种方式适用于四大组件间的通信

2.文件共享

3.contentprovider,比如获取联系人

4.AIDL、messenger

5.socket,socket大多用于网络间的通信,功能强大,但是实现起来较复杂,需要注意的细节也很多。


说到进程间通信就必须说到非常重要的一个知识点,那就是序列化。java提供的序列化方式是serializable,android中提供的序列化方式是parcelable。这里就不进行分析了,只是需要说一下两者之间的区别。parcelable的效率是远高于serializable的,而且serializable的开销非常大,所以android上才开发了parcelable序列化方式。

还有一个就是binder,但是binder这个东西不是一时半会能讲清楚的,我只是也是一知半解。以后彻底透彻了之后再写吧


下面就说重点:AIDL的使用。

传递简单数据就不写出来了,直接写传递对象(我们自定义的model类)

服务端写法:

第一步新建aidl文件夹和新建一个包,名称一定要和java代码下放要传递的对象的类的包名一致

第二步:序列化要传递的对象类

第三步:在第一步建好的包下面新建aidl接口。注意:这里是两个aidl文件;一个aidl文件和要传递的对象类名一致,还有一个定义我们传递的接口


建好之后工程目录如下:



User.aidl文件里面只需要写一句话,下面是User.aidl的内容

parcelable User;



然后是传递接口aidl,里面内容如下:

package com.ciotea.server.model;
import com.ciotea.server.model.User;
// Declare any non-default types here with import statements

interface TransformUser {
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

    boolean addUser(in User  user);
     List<User> getUser(in String name);
}


注意:其中import  com.ciotea.server.model.User;是需要我们手写上去的,AS不会自动导包

   addUser方法传递的参数是User,前面需要加上in,否则编译会报错。这里的in可以设置为in,out,intout;具体含义如下

in:参数由客户端设置,或者理解成客户端传入参数值。

out:参数由服务端设置,或者理解成由服务端返回值。

inout:客户端输入端都可以设置,或者理解成可以双向通信。

序列化部分就不贴出来了。自行度娘吧

 

接下来需要我们make project一下。AS会自动在我们的generated / source / aidl / debug / 目录下面自动生成一段Java代码

目录结构如下:


如果AS已经自动生成了这些代码。说明前面的aidl文件定义是成功的。

 

客户端

接下来要做的就是把aidl文件和需要传递的对象的类拷贝到客户端中,就不一一解释了,贴上结构:



只有一个需要注意的:包名要和服务端代码的包名一致,同样的make project一下。也会生成和服务器端一样的代码

 

接下来我们来看看AS帮我们自动生成的这个类

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\AidlDemo\\client\\src\\main\\aidl\\com\\ciotea\\server\\model\\TransformUser.aidl
 */
package com.ciotea.server.model;
// Declare any non-default types here with import statements

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

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

        /**
         * Cast an IBinder object into an com.ciotea.server.model.TransformUser interface,
         * generating a proxy if needed.
         */
        public static com.ciotea.server.model.TransformUser asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.ciotea.server.model.TransformUser))) {
                return ((com.ciotea.server.model.TransformUser) iin);
            }
            return new com.ciotea.server.model.TransformUser.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_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addUser: {
                    data.enforceInterface(DESCRIPTOR);
                    User _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = User.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    boolean _result = this.addUser(_arg0);
                    reply.writeNoException();
                    reply.writeInt(((_result) ? (1) : (0)));
                    return true;
                }
                case TRANSACTION_getUser: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.util.List<User> _result = this.getUser(_arg0);
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.ciotea.server.model.TransformUser {
            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 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public boolean addUser(User user) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                boolean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((user != null)) {
                        _data.writeInt(1);
                        user.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
                    _reply.readException();
                    _result = (0 != _reply.readInt());
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public java.util.List<User> getUser(java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<User> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(User.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }

    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public boolean addUser(User user) throws android.os.RemoteException;

    public java.util.List<User> getUser(java.lang.String name) throws android.os.RemoteException;
}

貌似一看,好多代码,看得脑袋都大,但是我们把Stub这个内部类收起来之后再来看:


一下就变得清晰了:一个内部类Stub,三个方法:basicTypes();addUser();getUser();

是不是有点熟悉:没错,这就是我们刚才在aidl中定义的三个方法;

 

我们再来一步一步看这个Stub内部类,

public static abstract class Stub extends android.os.Binder implements com.ciotea.server.TransformUser 

首先,它继承自Binder类,所以它是binder子类。

它里面还有一个内部类Proxy,Proxy作为一个代理类,当位于不同的进程时就会调用proxy来执行请求

再来解释stub里面的方法:

DESCRIPTOR:binder的标示,一般用当前binder的包名+类名

asInterface(android.os.IBinder obj):传入的是一个binder对象,这个方法的作用是将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,但是它里面是做了判断的,如果是同一个进程,那么返回的就是stub对象本身,如果是不同的进程就返回Stub的代理proxy对象

asBinder():返回当前binder对象


public booleanonTransact(intcode, android.os.Parcel data, android.os.Parcel reply,intflags):

这个方法运行在服务端中,当客户端发起请求的时候,服务端会调用这个方法去做处理,

ontransact方法的第一个参数是客户端传递过来的需要调用的方法的code,服务端根据这个code判断客户端需要调用的方法

ontransact方法第二个参数是客户端传递过来的参数。

ontransact方法第三个参数是服务端回复给客户端的结果,如果是有返回值的方法,会先调用本地的方法进行查询。如果有结果,就会向reply里面序列化写入结果_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);



接下来就是服务端service的编写

这里我声明了一个list集合,模仿数据库操作

service里面主要做一件事就可以了,创建一个Stub对象,在onbind方法里面返回给客户端就好,客户端拿到这个stub对象就可以进行访问了

别忘了服务要在mainfest里面注册,还需要声明action才能被其他进程访问。

<service
    android:name="com.ciotea.MyService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.bgxt.Service.CUSTOM_TYPE_SERVICE"/>
    </intent-filter>
</service>
代码如下:

public class MyService extends Service {

    static List<User> users = new ArrayList<>();
    static {
        users.add(new User("xiong",23,"北京大学"));
        users.add(new User("zhou",23,"人名大学"));
        users.add(new User("xiong",23,"中国民航大学"));
        users.add(new User("xiong",23,"哈佛大学"));
    }

    private Binder stub = new TransformUser.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
        @Override
        public boolean addUser(User user) throws RemoteException {
            boolean b = users.add(user);
            return b;
        }

        @Override
        public List<User> getUser(String name) throws RemoteException {
            List<User> reply = new ArrayList<>();
            for (User user : users) {
                if (user.name.equals(name)){
                    reply.add(user);
                }
            }
            return reply;
        }
    };

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("MyService","onBind");
        return stub;
    }
}

再后面就是客户端绑定服务和调用服务的方法就好了

首先需要创建一个ServiceConnection对象,通过onServiceConnected 方法里面的binder对象通过asInterface就能拿到

服务端我们声明的aidl接口的对象,就能调用服务端的方法进行操作了

private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            transformUser = TransformUser.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

//绑定服务
        Intent intent = new Intent();
        intent.setAction("cn.bgxt.Service.CUSTOM_TYPE_SERVICE");
        boolean b = bindService(intent, connection, BIND_AUTO_CREATE);
        Log.i("====",b+"");

接下来只需要调用aidl中声明的方法:
@Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.add_user:
                try {
                    boolean b = transformUser.addUser(new User("周鱼儿", 23, "中山大学"));
                    Log.i("server adduser",b+"");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.select_user:
                try {
                    List<User> users = transformUser.getUser("xiong");
                    for (User ser : users) {
                        Log.i("client getuser",ser.name+ser.age+ser.shcool);
                    }

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
查询结果和添加结果


到这里一个aidl过程就完成了。其实aidl并不难,只是要细心,很多地方一个小问题就导致自动生成代码不通过。

这里就把需要注意的地方和问题总结一下:

1.包名一定要一致,aidl的包名和要传递的对象的包名要一致,客户端和服务端的aidl文件包名要一致,客户端要传递对象的包名和服务端要传递对象的包名要一致

2.当不是基本类型的传递时,需要添加一个新的aidl类,类名和传递对象一致,里面只写一句话: pareclable +传递对象的类名

3.refactor AIDL包名之后,aidl文件里面不会自动更新包名,需要收到更改。

4.aidl文件的不会自动import,需要手动import

public static abstract class Stub extends android.os.Binder implements com.ciotea.server.TransformUser 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值