zoukankan      html  css  js  c++  java
  • Android AIDL应用间交互

    Android Service介绍中我们对长时间运行的服务、应用内交互的服务进行了相关介绍,本文主要介绍使用Service进行应用间的交互示例APK见:TrineaAndroidDemo.apk
     
    1、介绍
    Android使用AIDL来完成进程间通信(IPC),AIDL全程为Android Interface Definition Language。在服务需要接受不同应用多线程的请求时才需要使用AIDL,如果是同一个应用内的请求使用Binder实现即可,见应用内交互的服务;如果只是应用间通信而不是多线程处理的话使用Messenger,当然这两种情况也可以使用AIDL。本地进程和远程进程使用AIDL有所不同,本地进程内调用时会都在调用的线程内执行,远程进程使用是通过Service进程内一个由系统维护的线程池发出调用,所以可能是未知线程同时调用,需要注意线程安全问题
     
    2、示例
    在被调用应用内(后面称服务端)的Service内有个属性count,需要在调用者(后面称客户端)内对这个属性进行操作。下面将介绍服务器端和客户端的主要代码
    服务器端(代码见ServiceDemo):

    (1) 服务器端新建AIDL文件定义接口

    在服务器端的src目录下新建以.aidl为后缀的文件,在这个文件中定义一个接口,声明服务器端和客户端交互的api,语法跟普通java接口声明一样,可以添加中英文注释。不同的是

    a. 除了Java基本数据类型 (int, long, char, boolean等)、String、CharSequence、List、Map外,其他复杂类型都需要显式import(包括其他AIDL定义的接口),即便复杂类型在同一个包内定义。

    b. 支持泛型实例化的List,如List<String>;不支持泛型实例化的Map,如Map<String, String>。对于List为参数接收者接收到的始终是ArrayList;对于Map为参数接收者接收到的始终是HashMap。

    c. interface和函数都不能带访问权限修饰符。

    d. 接口内只允许定义方法,不允许定义静态属性。

    我们定义MyAIDLInterface.aidl文件如下

    package com.trinea.android.demo;
    
    interface MyAIDLInterface {
    
         int getCount();
    
         void setCount(int count);
    }

    声明两个接口在后面供客户端调用。保存文件,编译后,Sdk工具在gen目录下相同子目录中自动生成同名的以.java为后缀的文件,这里文件名为MyAIDLInterface.java。

    (2) 自动生成的同名java文件解析

    MyAIDLInterface.java为Sdk工具自动生成,内容如下

    根据AIDL文件自动生成的Java文件
    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: D:\\Eclipse\\AndroidDemo\\MapDemo\\src\\com\\trinea\\android\\demo\\MyAIDLInterface.aidl
     */
    package com.trinea.android.demo;
    
    public interface MyAIDLInterface extends android.os.IInterface
    {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.trinea.android.demo.MyAIDLInterface
    {
    private static final java.lang.String DESCRIPTOR = "com.trinea.android.demo.MyAIDLInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
    this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.trinea.android.demo.MyAIDLInterface interface,
     * generating a proxy if needed.
     */
    public static com.trinea.android.demo.MyAIDLInterface asInterface(android.os.IBinder obj)
    {
    if ((obj==null)) {
    return null;
    }
    android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.trinea.android.demo.MyAIDLInterface))) {
    return ((com.trinea.android.demo.MyAIDLInterface)iin);
    }
    return new com.trinea.android.demo.MyAIDLInterface.Stub.Proxy(obj);
    }
    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_getCount:
    {
    data.enforceInterface(DESCRIPTOR);
    int _result = this.getCount();
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    case TRANSACTION_setCount:
    {
    data.enforceInterface(DESCRIPTOR);
    int _arg0;
    _arg0 = data.readInt();
    this.setCount(_arg0);
    reply.writeNoException();
    return true;
    }
    }
    return super.onTransact(code, data, reply, flags);
    }
    private static class Proxy implements com.trinea.android.demo.MyAIDLInterface
    {
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
    mRemote = remote;
    }
    public android.os.IBinder asBinder()
    {
    return mRemote;
    }
    public java.lang.String getInterfaceDescriptor()
    {
    return DESCRIPTOR;
    }
    public int getCount() 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);
    mRemote.transact(Stub.TRANSACTION_getCount, _data, _reply, 0);
    _reply.readException();
    _result = _reply.readInt();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    return _result;
    }
    public void setCount(int count) 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(count);
    mRemote.transact(Stub.TRANSACTION_setCount, _data, _reply, 0);
    _reply.readException();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    }
    }
    static final int TRANSACTION_getCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_setCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public int getCount() throws android.os.RemoteException;
    public void setCount(int count) throws android.os.RemoteException;
    }

    其中有几个主要的部分:

    a. 抽象类Stub,继承Binder实现自定义接口,作用同进程内通信服务中自定义的Binder,客户端通过它对服务进行调用。

    b. 静态类Proxy,实现自定义接口,代理模式接收对Stub的调用。

    (3) 服务器端定义服务

    跟一般的服务定义类似,新建个上面自动生成的MyAIDLInterface.java文件中的Stub的对象,在onBind中返回,代码如下:
    服务器端Service代码
    package com.trinea.android.demo;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.widget.Toast;
    
    public class MyAIDLService extends Service {
    
        private int                  mCount;
        private MyAIDLInterface.Stub myBinder = new MyAIDLInterface.Stub() {
    
                                                  @Override
                                                  public void setCount(int count) throws RemoteException {
                                                      mCount = count;
                                                  }
    
                                                  @Override
                                                  public int getCount() throws RemoteException {
                                                      return mCount;
                                                  }
                                              };
    
        @Override
        public void onCreate() {
            mCount = 0;
            Toast.makeText(this, "Service onCreate", Toast.LENGTH_SHORT).show();
            super.onCreate();
        }
    
        @Override
        public void onDestroy() {
            Toast.makeText(this, "Service onDestroy", Toast.LENGTH_SHORT).show();
            super.onDestroy();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Toast.makeText(this, Integer.toString(mCount), Toast.LENGTH_SHORT).show();
            return super.onStartCommand(intent, flags, startId);
        }
    
        public int getCount() {
            return mCount;
        }
    
        /**
         * 服务被绑定时调用
         * 返回值用于让调用者和服务通信,传入ServiceConnection的public void onServiceConnected(ComponentName name, IBinder service)函数第二个参数
         */
        @Override
        public IBinder onBind(Intent intent) {
            return myBinder;
        }
    }

    AndroidManifest.xml中注册服务

    <service android:name=".MyAIDLService">
        <intent-filter>
            <action android:name="com.trinea.android.demo.remote.MyAIDLServiceAction" />
        </intent-filter>
    </service>

    其中的action name用于后面客户端调用时匹配

    客户端(代码见AndroidAidlClient@GoogleCode):

    (1) 拷贝aidl文件
    将服务器端的aidl文件拷贝到客户端,客户端也会在gen目录下自动生成同名的java文件。服务器端和客户端文件目录如下:

    服务器端目录结构:,客户端目录结构:

    PS:在运行过程中发现一个小问题,即服务器端服务已经启动,而客户端始终无法BindService成功,报错

    ERROR/AndroidRuntime(2104): java.lang.SecurityException: Binder invocation to an incorrect interface。后发现是因为服务器端和客户端aidl文件的包名不同导致生成的java文件不同。

    (2) 服务绑定
    跟一般的服务绑定类似,只是获取服务的接口不同而已,这里我们通过Stub.asInterface函数获得,代码如下
    客户端调用远程服务代码
    private MyAIDLInterface   binder = null;
    private Intent            myAIDLServiceIntent;
    private ServiceConnection con    = new ServiceConnection() {
    
                                         @Override
                                         public void onServiceDisconnected(ComponentName name) {
    
                                         }
    
                                         @Override
                                         public void onServiceConnected(ComponentName name, IBinder service) {
                                             binder = MyAIDLInterface.Stub.asInterface(service);
                                         }
                                     };
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.client);
        
        boundAIDLServiceBtn = (Button)findViewById(R.id.boundAIDLService);
        myAIDLServiceIntent = new Intent("com.trinea.android.demo.remote.MyAIDLServiceAction");
        boundAIDLServiceBtn.setOnClickListener(new OnClickListener() {
    
            @Override
            public void onClick(View v) {
                boolean result = bindService(myAIDLServiceIntent, con, Context.BIND_AUTO_CREATE);
                if (!result) {
                    binder = null;
                    Toast.makeText(getApplicationContext(), "服务绑定失败。", Toast.LENGTH_SHORT).show();
                }
            }
        });
    
        getBoundAIDLServiceProBtn = (Button)findViewById(R.id.getBoundAIDLServicePro);
        getBoundAIDLServiceProBtn.setOnClickListener(new OnClickListener() {
    
            @Override
            public void onClick(View v) {
                try {
                    if (binder != null) {
                        Toast.makeText(getApplicationContext(), "Service count:" + binder.getCount(),
                                       Toast.LENGTH_SHORT).show();
                    } else {
                        Toast.makeText(getApplicationContext(), "请先绑定服务。", Toast.LENGTH_SHORT).show();
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    
        unboundAIDLServiceBtn = (Button)findViewById(R.id.unboundAIDLService);
        unboundAIDLServiceBtn.setOnClickListener(new OnClickListener() {
    
            @Override
            public void onClick(View v) {
                if (binder != null) {
                    unbindService(con);
                    binder = null;
                }
            }
        });
    }

    上面的三个按钮boundAIDLServiceBtn、getBoundAIDLServiceProBtn、unboundAIDLServiceBtn分别用于绑定服务、得到服务中属性值、解除绑定服务。

    从上面可以看出我们先定义ServiceConnection对象用于和服务器通信,在onServiceConnected函数中通过MyAIDLInterface.Stub.asInterface(service)返回用户定义的接口对象,通过此对象调用远程接口api。在boundAIDLServiceBtn的onClick函数中绑定服务,在getBoundAIDLServiceProBtn的onClick函数中调用服务的api,在boundAIDLServiceBtn的onClick函数中取消绑定服务,

    现在我们运行两个项目即可,在客户端绑定服务再获取服务属性显示。

    3、生命周期

    通过bindService绑定服务,若服务未启动,会先执行Service的onCreate函数,再执行onBind函数,最后执行ServiceConnection对象的onServiceConnected函数,客户端可以自动启动服务。若服务已启动但尚未绑定,先执行onBind函数,再执行ServiceConnection对象的onServiceConnected函数。若服务已绑定成功,则直接返回。这里不会自动调用onStartCommand函数。

    通过unbindService解除绑定服务,若已绑定成功,会先执行Service的onUnbind函数,再执行onDestroy函数,注意这里不会执行ServiceConnection对象的onServiceDisconnected函数,因为该函数是在服务所在进程被kill或是crash时被调用。

     
    参考:
  • 相关阅读:
    使用ltp4j碰到Can't find dependent libraries报错信息的问题解决
    记录遭遇挖矿程序kthrotlds的失败处理经历
    腾讯云短信服务的申请和验证使用详细流程
    再谈腾讯云centos服务器不能登录的解决过程
    腾讯云centos服务器不能登录的解决过程
    解决tomcat部署项目中碰到的几个问题
    设计模式之二——从江湖情报变动通知各门派看观察者模式
    web.xml配置详解
    pom.xml详解
    mysql workbench EER model 乱码
  • 原文地址:https://www.cnblogs.com/trinea/p/2701390.html
Copyright © 2011-2022 走看看