Android AIDL

Contents

Android AIDL. 1

定义AIDL接口... 1

1.创建.aidl文件... 2

2.实现接口... 4

3.暴露接口给客户端... 5

IPC之间传递对象... 7

调用IPC方法... 9

 

Android AIDL

Android AIDL跟其他IDL语言相似。AIDL定义一套client端和service端都支持的编程接口,供IPC使用。Android的进程无法访问另一个进程的数据。所以,需要把数据分解成操作系统能识别的基本数据类型穿过进程界限,接收一方在重新组装。这一过程比较枯燥,所以Android系统用AIDL为你实现了。

注意:只有别的应用程序需要通过进程间通信访问你的service的时候才需要AIDL。如果你不需要应用程序之间并发远程调用,可以用AIDL,实现相应接口的Binder。或者你需要IPC,但不想支持多线程,那么就用Messenger来实现service。

在你开始设计AIDL接口之前,首先要注意AIDL调用就是直接函数调用。你不能假设这个调用发生在哪个线程中。到底在哪个线程来执行取决于调用来自于本地进程还是远程进程。具体的说:

  • 本地进程发起的调用仍在调用线程执行。如果在UI进程调用的AIDL接口,那么仍然在UI进程执行AIDL接口的code,如果在其他子线程执行调用,那么AIDL的代码就在子线程中执行。(如果是这种情况,那么你应该不需要AIDL,只需实现一个Binder就可以了。)
  • 从远端应用程序调用过来,会从系统维护的本地进程交互线程池中取出一个线程来执行。你必须为不知道的线程调用准备好,也有可能很多线程同时调进来。也就是说AIDL接口的实现者必须是线程安全的。
  • Oneway关键字修饰远程调用,更改调用行为。用oneway修饰的方法远程调用不阻塞,传输完数据直接返回。接口实现端就像一个正常普通远程调用一样接收调用。如果oneway修饰的方法被本进程调用,那么依然是象同步调用一样。

定义AIDL接口

首先用java编程语言定义一个.aidl扩展名的文件,保存在实现方和调用方源代码中(/src文件夹)下。

当你编译带.aidl文件的时候,Android SDK工具会基于.aidl生成一个IBinder接口,保存在项目的/gen路径下。服务端要实现这个IBinder接口。应用客户端绑定这一服务并通过IPC经由IBinder调用这些方法。

创建一个AIDL绑定的服务,需要这么几步:

  1. 创建.aidl文件

这个文件定义了编程接口和方法

  1. 实现接口

Android SDK工具基于.aidl文件生成接口文件。这个接口有一个名叫Stub的内部抽象类,Stub扩展了Binder并实现了AIDL接口中声明的方法。

  1. 暴露接口给客户端

实现一个Service重写onBind(),onBind()返回实现了Stub的类。

注意:AIDL接口在第一次发布之后,以后的任何改动都必须向后兼容。否则,可能会影响调用者的功能。因为.aidl必须复制到调用者的应用中,调用者才能使用你提供的服务,所以必须保证原始的接口的可用性。

1.创建.aidl文件

AIDL语法很简单,你可以声明接口,一个或多个方法,定义参数和返回值。参数和返回值可以是任意类型,甚至可以是其他AIDL生成的接口。

你只需用Java编程语言构建.aidl文件。每个.aidl文件定义一个单独的接口,并声明接口和方法。

默认情况下,AIDL支持下面几种文件类型:

  • Java编程语言的所有基本数据类型(比如:int,long,char,boolean等等)
  • String
  • CharSequence
  • List
    • List中所有元素必须是以上列出的支持的类型或其他AIDL产生的接口或你已经声明的Parcelable。你可以选择性的使用更通用的类表示List(比如List)。尽管生成接口的时候声明的是List,在实际使用中对侧接收到的具体的类通常是ArrayList。
  • Map
    • Map中所有元素必须是以上列出的支持的类型或其他AIDL产生的接口或你已经声明的Parcelable。这里不支持通用的Map(比如:形如Map)。尽管生成接口的时候声明的是Map,在实际使用中对侧接收到的具体的类通常是HashMap。

不在以上列表的类型,即使跟借口在同一个包中,也需要使用import声明。

定义服务接口需要注意:

  • 方法可以有一个或多个参数,也可以没有参数,可以有一个返回值,也可以返回空。
  • 所有非基本数据类型需要一个方向声明,以表示数据流向。方向可以是:in, out或者inout(请看下面的例子)。
  • 基本数据类型默认是in,不能是其他方向。

!注意:方向限制必须设置成真正需要的方向,因为数据编排开销很大。

  • .aidl文件中所有代码的注释都被包含在生成的IBinder接口中(除了import和包语句前面的注释)。
  • AIDL不支持静态数据,至支持方法。

这是一个.aidl文件的例子:

 

 

 

// IRemoteService.aidl

  package com.example.android;

  

  // Declare any non-default types here with import statements

  

  /** Example service interface */

  interface IRemoteService {

      /** Request the process ID of this service, to do evil things with it. */

      int getPid();

  

      /** Demonstrates some basic types that you can use as parameters

       * and return values in AIDL.

       */

      void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,

              double aDouble, String aString);

  }

编译项目的时候,只需要把你的.aidl文件保存在项目的/src目录下,SDK工具会自动生成IBinder接口并保存在你的项目的/gen路径下。生成的文件的文件名与.aidl相同,但扩展名是.java(比如:IRemoteService.aidl生成IRemoteService.java)。

如果你用Android Studio,增量编译几乎瞬间就能生成binder类。如果你不用Android Studio,Gradle工具会在你下次编译应用的时候生成binder类。你应该在完成.idle后马上用gradle assembleDebug(或gradle assembleRelease)编译以下你的工程,这样你的代码就可以链接到生成的类上了。

2.实现接口

当你编译你的应用时,Android SDK工具根据.aidl为你生成了同名的.java的接口文件。生成的文件包括一个名为Stub的子类,Stub是父类接口的抽象实现并声明了.aidl文件的所有方法。

注意:Stub还定义了一些辅助方法,最重要的是asInterface(),输入时一个IBinder(通常是客户端的回调onServiceConnected()里得到的),输出是一个Stub接口的实例。如何具体投射请看调用一个IPC方法。

为了实现从.aidl生成的接口,继承通用的Binder接口(例如:YourInterface.Stub)并实现继承自.aidl文件的方法。

这里有个匿名实例实现了名为IRemoteService(通过IRemoteService定义的)的接口的例子

 

 

 

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {

      public int getPid(){

          return Process.myPid();

      }

      public void basicTypes(int anInt, long aLong, boolean aBoolean,

          float aFloat, double aDouble, String aString) {

          // Does nothing

      }

  };

现在mBinder是Stub(一个Binder)类的一个实例了,mBinder定义了RPC接口的服务。下一步,暴露这个实例给客户端,以便让客户端和服务端交互。

实现AIDL接口的时候有几个规则需要注意:

  • 函数调用并不一定在主线程执行,所以需要考虑多线程,把服务做成线程安全的。
  • 默认情况下,RPC调用是同步的。如果你知道服务器的反应时间在毫秒级别,那么不要在主线程调用服务,否则可能会导致应用程序不反应(Android可能会显示ANR对话框),在调用这段你应该用一个单独的线程调用服务器。
  • 服务端抛出的异常不会发送到调用一侧。

3.暴露接口给客户端

服务接口实现完后,需要把它暴露给客户端,这样客户端才能绑定它。暴露服务接口,只需要扩展Service组件并实现onBind(),onBind()返回实现Stub的实例。这里有一个暴露IRemoteService接口的例子。

 

 

public class RemoteService extends Service {
   
@Override
    public void onCreate() {
       
super.onCreate();
    }

   
@Override
    public IBinder onBind(Intent intent) {
       
// Return the interface
        return mBinder;
    }

   
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
       
public int getPid(){
           
return Process.myPid();
        }
       
public void basicTypes(int anInt, long aLong, boolean aBoolean,
           
float aFloat, double aDouble, String aString) {
           
// Does nothing
        }
    };
}

现在,客户端(比如一个Activity)调用bindService()来连接服务,客户端的回调onServiceConnected()会接收到从服务的onBind()方法返回的mBinder的实例。

客户端必须由权限访问接口类,这样如果客户端和服务端在单独的应用中,客户端的应用中/src目录下就必须有一个.aidl文件的复制(以便生成android.os.Binder接口,供客户端访问)。

客户端在回调函数onServiceConnected()中得到IBinder后,需调用YourserviceInterface.Stub.asInterface(service)将返回的参数转换成YourServiceInterface类型。例如:

 

 

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
   
// Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
       
// Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

   
// Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
       
Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService =
null;
    }
};

IPC之间传递对象

可以通过IPC接口传递类。但必须保证在IPC通道的另一端也能访问该类,并且该类需支持Parcelable接口。因为只有支持Parcelable接口,Android系统才能将类对象组装成基本数据类型,以便在不同进程之间传输。

支持Parcelable接口的类需满足以下几点:

  1. 类实现Parcelable接口
  2. 实现writeToParcel,将当前的对象写入Parcel
  3. 类添加一个静态域CREATOR,这是一个实现了Parcelable.Creator的接口
  4. 最后,创建一个.aidl文件声明你的Parcelable类(就像下面的Rect.aidl文件)。

如果你用自定义编译,不要把.aidl文件添加到你的编译中。就像C语言中的头文件,.aidl文件是不会编译的。

AIDL用这些方法和它生成的这些域组装和反组装你的对象。

这是一个Rect.aidl文件创建了一个Parcelable Rect类。

package android.graphics;

  

  // Declare Rect so AIDL can find it and knows that it implements

  // the parcelable protocol.

  parcelable Rect;

下面是一个Rect类实现了Parcelable协议

import android.os.Parcel;

  import android.os.Parcelable;

  

  public final class Rect implements Parcelable {

      public int left;

      public int top;

      public int right;

      public int bottom;

  

      public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {

          public Rect createFromParcel(Parcel in) {

              return new Rect(in);

          }

  

          public Rect[] newArray(int size) {

              return new Rect[size];

          }

      };

  

      public Rect() {

      }

  

      private Rect(Parcel in) {

          readFromParcel(in);

      }

  

      public void writeToParcel(Parcel out, int flags) {

          out.writeInt(left);

          out.writeInt(top);

          out.writeInt(right);

          out.writeInt(bottom);

      }

  

      public void readFromParcel(Parcel in) {

          left = in.readInt();

          top = in.readInt();

          right = in.readInt();

          bottom = in.readInt();

      }

  

      public int describeContents() {

          return 0;

      }

  }

Rect类的组装很简单。关于其他数据类型的组装,详情请参考Parcel。

!注意:不要忘了从其他进程接收数据的安全问题。在这个例子中,Rect从Parcel中读出四个数字,不管调用方想做什么,你要保证这些数据的值在你可以处理范围内。如何保证应用安全,详情请看安全和权限。

调用IPC方法

这里是调用端通过AIDL接口调用服务的步骤:

  1. 引入.aidl文件到你的项目/src路径下
  2. 声明IBinder接口的一个实例
  3. 实现ServiceConnection
  4. 调用Context.bindService(),传入ServiceConnection的实现
  5. 在onServiceConnected()函数中,你会收到一个IBinder的实例。调用YourInterface.Stub.asInterface((IBinder)service)把参数转换成YourInterface类型。
  6. 调用在你的接口上定义的方法。你要负责捕获DeadObjectException,当连接断开的时候会抛出这个异常。你还要负责捕获SecurityException异常,参与IPC的两个进程在AIDL定义上有冲突的时候会抛出这个异常。
  7. 要断开服务,通过接口实例调用Context.unbindService()。

调用IPC服务的时候一些说明:

  • 进程之间的对象是引用计数的。
  • 可以通过方法参数传输匿名类。

这里有个AIDL调用的例子。

public static class Binding extends Activity {
   
/** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
   
/** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

   
Button mKillButton;
   
TextView mCallbackText;

   
private mHandler InternalHandler;
   
private boolean mIsBound;

   
/**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

       
// Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (
Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (
Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(
false);

        mCallbackText = (
TextView)findViewById(R.id.callback);
        mCallbackText.setText(
"Not attached.");
        mHandler =
new InternalHandler(mCallbackText);
    }

   
/**
     * Class for interacting with the main interface of the service.
     */

    private ServiceConnection mConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(
true);
            mCallbackText.setText(
"Attached.");

           
// We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            }
catch (RemoteException e) {
               
// In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

           
// As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                   
Toast.LENGTH_SHORT).show();
        }

       
public void onServiceDisconnected(ComponentName className) {
           
// This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(
false);
            mCallbackText.setText(
"Disconnected.");

           
// As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                   
Toast.LENGTH_SHORT).show();
        }
    };

   
/**
     * Class for interacting with the secondary interface of the service.
     */

    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(
true);
        }

       
public void onServiceDisconnected(ComponentName className) {
            mSecondaryService =
null;
            mKillButton.setEnabled(
false);
        }
    };

   
private OnClickListener mBindListener = new OnClickListener() {
       
public void onClick(View v) {
           
// Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(
IRemoteService.class.getName());
            bindService(intent, mConnection,
Context.BIND_AUTO_CREATE);
            intent.setAction(
ISecondary.class.getName());
            bindService(intent, mSecondaryConnection,
Context.BIND_AUTO_CREATE);
            mIsBound =
true;
            mCallbackText.setText(
"Binding.");
        }
    };

   
private OnClickListener mUnbindListener = new OnClickListener() {
       
public void onClick(View v) {
           
if (mIsBound) {
               
// If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                   
try {
                        mService.unregisterCallback(mCallback);
                    }
catch (RemoteException e) {
                       
// There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

               
// Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(
false);
                mIsBound =
false;
                mCallbackText.setText(
"Unbinding.");
            }
        }
    };

   
private OnClickListener mKillListener = new OnClickListener() {
       
public void onClick(View v) {
           
// To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
               
try {
                   
int pid = mSecondaryService.getPid();
                   
// Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText(
"Killed service process.");
                }
catch (RemoteException ex) {
                   
// Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                           
Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

   
// ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
       
/**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */

        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value,
0));
        }
    };

   
private static final int BUMP_MSG = 1;

   
private static class InternalHandler extends Handler {
       
private final WeakReference<TextView> weakTextView;

       
InternalHandler(TextView textView) {
            weakTextView =
new WeakReference<>(textView);
        }

       
@Override
        public void handleMessage(Message msg) {
           
switch (msg.what) {
               
case BUMP_MSG:
                   
TextView textView = weakTextView.get();
                   
if (textView != null) {
                        textView.setText(
"Received from service: " + msg.arg1);
                    }
                   
break;
               
default:
                   
super.handleMessage(msg);
            }
        }
    }
}

 

 

你可能感兴趣的:(Android AIDL)