IPC之AIDL简析

IPC之AIDL简析


一.什么是AIDL

Android Interface Definition Language意为Android接口描述语言,常用于进程间通讯。首先要说明的是,如果你很牛逼,你完全不用AIDL文件即可实现Binder,Android之所以给开发者提供AIDL是为了方便系统为我们自动生成实现Binder的代码。

二.实现简单的跨进程服务

使用前先理解这是一个典型的C/S结构,提供服务的是服务端,需要跨进程访问的是客户端。

1.创建一个Android项目作为service端(这里使用AS作为示例)命名为:Demo_aidl_service,在新建一个AIDL文件,方便的是AS已经为我们考虑到了,右键工程->new->AIDL->AIDL FILE,创建一个名为ILoadAidlInterface.aidl的文件。结构如下(注意所在包):

这里写图片描述

2.将暴露给客户端的接口在这个AIDL文件中声明(通俗说就是把将来客户端要用的方法写出来)

// ILoadAidlInterface.aidl
package com.example.aidl_service;

interface ILoadAidlInterface {

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

    void load(String username,String pwd);
}

第一个basicTypes是系统默认的不用管,我这里只写了个load方法用于模拟登入功能,记住写完后一定要make一下工程让系统去生成相应的ILoadAidlInterface.java类

3.服务端service的实现,还是在原工程下创建一个LoadService继承Service(注意包名)

package com.example.aidl_service.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import com.example.aidl_service.ILoadAidlInterface;

/**
 * Created by Xio on 2016/7/11.
 */
public class LoadService extends Service {
    @Override

    public IBinder onBind(Intent intent) {
        Log.d("service", "service onBind");
        return mBinder;
    }

    @Override
    public void onCreate() {
        Log.d("service", "service onCreate");
        super.onCreate();
    }

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

        }

        @Override
        public void load(String username, String pwd) throws RemoteException {
            Log.d("service", "load: 用户名:" + username + ",密码:" + pwd);
        }
    };
}

在服务中我们创建了一个Binder对象并在onBind中返回,这个对象继承自ILoadAidlInterface.Stub并实现了它内部的俩个方法,在load方法中打印了个log。最后别忘记注册服务activity并且在activity下启动这个服务,如果不启动何来进程间通信?

        <service android:name=".service.LoadService">
            <intent-filter >
                <action android:name="com.example.aidl_service.service.LoadService"/>
            </intent-filter>
        </service>

注意要给service设定一个action以便客户端找到该服务。

4.接下来是新建客户端工程:名为Demo_aidl_client,之后把服务端工程下的aidl连文件夹一起拷过来,这样就能确保俩个aidl文件的包名一样。别忘了再make下工程。最后在mainactivity下绑定远程服务就行了。

public class MainActivity extends Activity {

    private Button mBtn;
    private ServiceConnection connection;
    private ILoadAidlInterface mLoadAIDL = null;

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

        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mLoadAIDL = ILoadAidlInterface.Stub.asInterface(service);
                Log.d("client", "onServiceConnected: ok!");
                doLoad();
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mLoadAIDL = null;
                Log.d("client", "unbind");
            }
        };

        mBtn = (Button) findViewById(R.id.btn);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("TvT", "onClick: ");
                if(mLoadAIDL==null){
                    bindService();
                    Log.i("TvT", "onClick: 1111111111111111111");
                }else{
                    doLoad();
                }
            }


        });
    }

    private void doLoad() {
        try {
            mLoadAIDL.load("xiongzihao","123456");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setAction("com.example.aidl_service.service.LoadService");
        intent.setPackage("com.example.aidl_service");
        bindService(intent,connection,BIND_AUTO_CREATE);
    }

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

ILoadAidlInterface mLoadAIDL = ILoadAidlInterface.Stub.asInterface(service);
这句式关键,绑定成功后我们将服务端返回的Binder对象转化为了AIDL接口,通过这个接口就可以调用服务端的远程方法了。mLoadAIDL.load();

这里需要注意:Android5.0官方规定service必须使用显示调用,如果隐式调用的话会产生异常导致程序崩溃,所以我们在setAction的同时还要setPackge使其显式调用

方法是执行在服务端,最后结果打印的log是在sevice端下的,而不是在客户端,这说明跨进程成功了!

三.分析AIDL实现跨进程原理

我这里参考了很多资料和博文,基本上都是从服务端开始讲起接口回调,这本来就是种逆向思维,不太好理解,这里我们从客户端讲起,一步步顺着流程走下去。

Demo_aidl_client要跨进程访问Demo_aidl_service,首先通过bindservice方法绑定一个服务,这个服务就是服务端所提供的服务LoadService,建立连接之后客户端通过asInterface方法拿到一个IBinder对象,这个方法等会分析,这时客户端发起远程请求,同时当前线程会挂起直至服务端返回数据,所以一个远程方法比较耗时的话,不能在主线程去发起此请求
先看下系统根据ILoadAidlInterface.aidl生成的ILoadAidlInterface.java文件,其格式是固定的。

package com.example.aidl_service;
// Declare any non-default types here with import statements

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

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

        /**
         * Cast an IBinder object into an com.example.aidl_service.ILoadAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.example.aidl_service.ILoadAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.aidl_service.ILoadAidlInterface))) {
                return ((com.example.aidl_service.ILoadAidlInterface) iin);
            }
            return new com.example.aidl_service.ILoadAidlInterface.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_load: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    this.load(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.example.aidl_service.ILoadAidlInterface {
            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;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @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 void load(java.lang.String username, java.lang.String pwd) 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.writeString(username);
                    _data.writeString(pwd);
                    mRemote.transact(Stub.TRANSACTION_load, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

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

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public void load(java.lang.String username, java.lang.String pwd) throws android.os.RemoteException;
}
  • 可以看到这个类继承了IInterface接口,它自己也是个接口,改接口类声明了俩个方法,可以看出就是我们在aidl文件中写的两个方法(一个是系统默认的)basicTypes()和load()。
  • 同时还声明了两个静态id来标识这两个方法,
  • 接着声明了个内部类stub继承Binder(实现ILoadAidlInterface接口),这个stub不用想的太复杂,他就是个Binder。
  • 在这个stub内部有个静态的标识DESCRIPTOR,作为Binder的唯一标识,这里是:”com.example.aidl_service.ILoadAidlInterface”;
  • stub下有个方法asInterface(IBinder obj)这个方法是不是很熟悉?没错,这个就是客户端绑定服务之后调用的方法ILoadAidlInterface.Stub.asInterface(service);
  • 那么他是干什么的呢?我们观察这个方法里面做了个判断,它判断客户端和服务器是否位于同一进程,如果是就返回服务端的stub对象本身。但是这里我们不是在同一进程,所以它返回的是封装后的stub.proxy对象。代码:return new com.example.aidl_service.ILoadAidlInterface.Stub.Proxy(obj);
  • 进入到proxy:我们在client里请求的是load方法,所以这里调用proxy里的load方法,这个方法运行在客户端,首先他把username和pwd这两个参数传递到了_data对象里,再通过transact方法来发起RPC(远程过程调用),同时线程挂起,然后服务端的onTransact方法执行。
  • onTransact执行在服务端的Binder线程池中,方法别调用后通过刚刚定义的标识来判断执行那个方法,这里执行load方法,把username和pwd从_data中取出调用load方法,如果有返回值的话就向_reply中写入返回值。如果此方法返回false,那么客户端请求失败,可以在这里做权限验证
  • 那么proxy的transact的RPC过程返回后,线程继续,从_reply中取出数据(如果有的话)。自此,整个流程就完成了。

最后在简单的阐述下过程(因为很多人肯定晕了):首先在服务端的LoadService下我创建了一个Binder对象mBinder,继承自ILoadAidlInterface实现了里面load方法(其实就是打印了一个log),那么客户端bindservice的时候会拿到这个mBinder,通过Stub.asInterface(mBinder)把binder传了进去,由于这里是跨进程,那么执行asinterface方法就是把mBinder在传给本地的proxy(代理)下的mRemote,就是通过mRemote执行远程函数load的调用,执行在服务端的Binder线程池中的onTransact就会调用load方法并返回结果,跨进程就成功了。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值