简介

远程服务:Android 系统与Windows系统的通信原则基本一致,进程就是安全策略的边界,不同的APP属于不同进程 Process,一个进程不能直接访问其他进程的资源。需要实现多进程间的通信,就要使用IPC(Inter Process Commnication)进程间通信技术。Android 系统的 IPC 机制是基于AIDL(AndroidInterfaceDefinition Language)接口定义语言定制进程间的通讯规则的。系统会基于AIDL 规则把信息进行序列化处理,然后发送到另一个进程当中,Android 系统把这种基于跨进程通信的服务称作 Remote Service 。

AIDL:是一种IDL语言,定义Android中两个进程间通信的规则。因为在Android中每个应用程序都是一个单独的JVM,就像两个独立的小岛,过着自己的生活,进行自己的操作,互不相干,虽然都是在地球上,但无法进行联系,这时候AIDL就像一座桥连接着两个岛,桥制定规则,规定人怎么来往,哪些人可以来往。在进程间就规定数据怎么进行传输。

通信方式比较:其实进程间通信还可以使用BroadcastReceiver , Messenger 等,但是BroadcastReceiver占用资源较多,并且它优先级较低,如果在这个广播之前有系统级广播,那它就会延迟执行,对于那些及时性的通信要求高的应用显然不合适;而Messenger 通信虽然实现简单,但它是以队列的方式进行,显然对那些要求并发执行的应用也不合适,那AIDL优势也就出来了,可以传输复杂的数据量,也可以支持多进程并发情况的进程间通信。

远程服务应用:当今有很多企业都有多个独立的APP,如阿里巴巴旗下就天猫、淘宝、聚划算、支付宝等多个APP,这时候就有需要把Service服务放在一独立的后台进程当中,作为多个APP之间信息交换的桥梁。这样如用户信息,用户登录,身份验证等多个共用的模块都可以在Service服务中实现,以供不同的APP进行调用。


编写

那我们如何编写AIDL文件呢,其实AIDL语法和Java语法基本上差不多,只不过有点细微差别:

1.AIDL文件后缀名是.aidl,java文件是.java;

2.aidl默认支持的数据类型是:

* Java八种基本数据类型(byte/short/int/long/float/double/boolean/char)

* String 与CharSequence类型。

* List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的

* Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的,Map是不支持泛型的。

3.AIDL应用的目标文件即使与你正在编写的文件在同一个包下,也是需要导包的,但是java是不需要的;

4.AIDL中的定向 tag 表示了在跨进程通信中数据的流向,这个点放在下一篇(Android 远程服务 通过AIDL进行进程间复杂类型数据交换)详细叙述,

in 表示数据只能由客户端流向服务端,表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;

out 表示数据只能由服务端流向客户端,表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改,之后客户端将会同步变动;

inout 则表示数据可在服务端与客户端之间双向流通,表现为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

5.AIDL文件大概分为两类,一类是定义parcelable对象,给其它AIDL文件使用AIDL支持的默认数据类型外的数据;一类是用来定义接口的。看到没,AIDL都是在定义规则,而没有具体实现,所以它为什么叫接口定义语言了。

 

例子

我们先做个简单的例子来体会下AIDL是什么东西(我这demo把远程服务端与客户端写在了一个app里)

先新建一个AIDL文件,如图:



android远程文件夹 android 远程_Service


输入文件名,我这里是IMathInterface,然后就是这样

android远程文件夹 android 远程_AIDL_02


这是编译器自动帮我们新建的,可以发现AIDL文件所在包名跟我们主程序包名是一样的。

在这个文件里我定义了两个方法,代码如下:

package com.mangoer.remotemathservicedemo;

//远程服务接口的定义
interface IMathInterface {

    int add(int a,int b);
    void log(String tag);
}

然后需要生成与之对应的java接口文件,点击AS顶部工具栏的Build,然后选择MakeProject,然后在如图位置就会生成对应的java文件

android远程文件夹 android 远程_android远程文件夹_03


我们来看下这个文件


public interface IMathInterface extends android.os.IInterface {

    public int add(int a, int b) throws android.os.RemoteException;
    public void log(java.lang.String tag) throws android.os.RemoteException;

    /**本地IPC实现类Stub */
    public static abstract class Stub extends android.os.Binder implements com.mangoer.remotemathservicedemo.IMathInterface {

        private static final java.lang.String DESCRIPTOR = "com.mangoer.remotemathservicedemo.IMathInterface";

        /** Stub钩子方法,并绑定到接口 */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * asInterface(IBinder) 是Stub内部的远程服务接口,调用者可以通过该方法获得远程服务的实例
         */
        public static com.mangoer.remotemathservicedemo.IMathInterface asInterface(android.os.IBinder obj) {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //判断android.os.IInterface实例是否为本地服务 若是返回android.os.IInterface
            //若不是本地服务 构造Proxy对象并返回之
            if (((iin!=null)&&(iin instanceof com.mangoer.remotemathservicedemo.IMathInterface))) {
                return ((com.mangoer.remotemathservicedemo.IMathInterface)iin);
            }
            return new com.mangoer.remotemathservicedemo.IMathInterface.Stub.Proxy(obj);
        }
        //实现了android.os.IInterface接口定义的asBinder()方法
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        /*
        * Parcel是Android系统应用程序间传递数据的容器,能够在两个进程中完成打包和拆包的工作 但Parcel不同于通用意义上的序列化
        * Parcel的设计目的是用于高性能IPC传输 不能将其保存在持久存储设备上
        * 接收Parcel对象,并从中逐一读取每个参数,然后调用Service内部制定的方法,将结果写进另一个Parcel对象,
        * 准备将这个Parcel对象返回给远程的调用者
         */
        @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_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_log: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    this.log(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        //用来实现远程服务调用
        private static class Proxy implements com.mangoer.remotemathservicedemo.IMathInterface {

            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;
            }

            //以一定顺序将所有参数写入Parcel对象,以供Stub内部的onTransact()方法获取参数
            @Override
            public int add(int a, int b) throws android.os.RemoteException {

                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void log(java.lang.String tag) 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(tag);
                    mRemote.transact(Stub.TRANSACTION_log, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_log = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
}



方法有什么用,我都加了注释,这个文件是自动生成的,不要手动修改逻辑。现在要实现远程服务了,新建MathService类继承Service


public class MathService extends Service {

    private String TAG = "MathService";

    /*
    * 建立IMathInterface.Stub实例,并实现IMathInterface这个AIDL文件定义的几个远程服务接口
    *在onBind方法中将mBind返回给远程调用者
    * */
    private IMathInterface.Stub mBind = new IMathInterface.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            Log.e(TAG,"add");
            return a+b;
        }
        @Override
        public void log(String tag) throws RemoteException {
            log_e(tag);
        }
    };

    private void log_e(String tag) {

        while (true) {
            Log.e(tag,"math");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate process id = " + Process.myPid());
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return mBind;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind");
        return false;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy");
    }

}



然后需要注册该服务


<service android:name=".MathService"
         android:process=":remote">
    <intent-filter>
        <action android:name="com.mangoer.remotemathservicedemo.MathService" />
    </intent-filter>
</service>



可以看到我给服务加了一个属性android:process:如果客户端与服务端在同个App中,被设置的进程名是以一个冒号(:)开头的,则这个新的进程对于这个应用来说是私有的,代表Service在单独的进程中运行,进程名称为:App-packageName:remote。

如果这个进程的名字是以小写字符开头的(比如remote),则这个服务将运行在一个以这个名字命名的全局的进程中,

当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享这个进程,从而减少资源的占用。

我还加了一个intent-filter:这是为了隐式启动远程服务所用。

讲到intent-filter就得讲还有一个属性android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。手动设置false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。

 

现在要开始远程调用了,在布局里加几个按钮



android远程文件夹 android 远程_android远程文件夹_04


然后在MainActivity里开始写,在绑定按钮里开始启动服务

@OnClick(R.id.bind)
public void bind() {
    Log.e(TAG,"bind");
    //隐式启动服务,android5.0后要设置包名
    Intent serviceIntent = new Intent();
    serviceIntent.setAction("com.mangoer.remotemathservicedemo.MathService");
    serviceIntent.setPackage("com.mangoer.remotemathservicedemo");
    bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
}

因为不处于一个进程,需要隐式启动,而且在android5.0以前,我们只要设置Action就行了,但是之后需要把包名也一起设置进来,从源码可以看出如果启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常。

启动之前需要构建一个ServiceConnection



private IMathInterface mathInterface;

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mathInterface = IMathInterface.Stub.asInterface(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        mathInterface = null;
    }
};


在onServiceConnected里获取远程服务的实例,这时候我们看下日志

android远程文件夹 android 远程_android远程文件夹_05

可以看出Mainactivity打印出来的进程id是6821,进程名称是包名,再看下远程服务的日志

android远程文件夹 android 远程_Service_06


进程id是7151,进程名是 包名:remote,这也就证明了应用程序与远程服务不处于同一个进程。可以看出远程服务回调了onCreate,onBind两个方法。


然后点击计算按钮


@OnClick(R.id.math)
public void math() {
    if (mathInterface == null) {
        Toast.makeText(this,"远程服务未绑定",Toast.LENGTH_LONG).show();
        return;
    }
    try {
        int result = mathInterface.add(3,2);
        Log.e(TAG,"result="+result);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

通过在onServiceConnected方法里拿到的远程服务实例,调用add方法,

在日志了看到主进程里打印了

11-09 14:52:48.5686821-6821/com.mangoer.remotemathservicedemo E/MainActivity: result=5

在远程服务进程打印了,也就是调用了add方法

11-09 14:52:48.567 7151-7171/com.mangoer.remotemathservicedemo:remoteE/MathService: add

 

这个操作是在客户端(MainActivity)里调用服务端(MathService)的add方法,并把两个int型数据传递给服务端,服务端计算完,把结果再返回给客户端(MainActivity),两个进程间就通过IMathInterface这个接口类进行数据交换。这就基本完成了两个进程间的简单通信。

 

这里传递的都是简单的数据类型,打包解包都是自动进行的,对于一些自定义的数据类型,需要实现Parcelable接口,将其转换成Parcel对象,使其穿越进程边界,这里我们下一篇继续(Android 远程服务 通过AIDL进行进程间复杂类型数据交换)。