Android-Service组件之AIDL

转载请标明出处:http://blog.csdn.net/goldenfish1919/article/details/40427901

原文:http://developer.android.com/guide/components/aidl.html

AIDL跟你可能用过的其他的接口定义语言很相似,它允许你定义客户端和服务器为了使用跨进程IPC进行相互通讯的编程接口。在Android里面,一般情况下一个进程不可以访问其他进程的内存。为了能够交互,就需要把对象序列化成操作系统能理解的基本类型,然后再反序列化成对象,通过这种方式来跨越这个障碍。序列化的代码很繁琐,因此android系统使用AIDL来帮你做这件事。


注意:只有当你允许不同应用的客户端通过IPC来访问你的Service,并且你希望在Service中并发的处理请求的情况下才需要使用AIDL。如果你不需要处理不同应用的并发的IPC,那么你应该通过继承Binder来实现,或者如果你想要支持IPC但是不需要支持并发,那么你可以用Messenger来实现。不管怎么说,在你实现AIDL之前,你应该首先要理解Bound Services。

在你设计AIDL接口之前,你要明白对AIDL接口的调用是直接方法调用。你不应该假设是哪个线程发起的调用。调用是来自本地进程还是远程进程是不一样的,主要体现在:

(1)来自本地进程的调用是跟调用者在同一个线程中执行。如果这个线程是主线程,那么主线程就负责执行AIDL接口。如果是别的线程,它要负责在service中执行代码。因此,如果只有本地线程访问你的service,你是可以完全控制有哪些线程来访问它(但是这种情况下,你根本就不应该使用AIDL,而是应该继承Binder来创建接口)

(2)来自远程进程的调用是从系统在你的进程内部维护的一个线程中派发过来的。你必须需要时刻准备处理来自未知线程的请求,而且有可能是并发的请求。换句话说,AIDL的实现必须是线程安全的。

(3)“oneway”关键字可以修改远程调用的行为。当使用了这个而关键字的时候,远程调用不会阻塞。它只是简单的把数据发送出去然后就立即返回了。接口的实现最终会收到这个请求,并把它当做是一个普通的来自Binder线程池的远程调用。如果在本地调用的时候使用了这个关键字,它对调用没有任何影响,调用仍然是同步的。

定义AIDL接口

你必须要在.aidl文件中用java语法来定义你的AIDL接口,然后保存到源码目录中,要在service的应用和其他要绑定到service的应用中同时都部署。

当你构建包含了.aidl文件的应用的时候,android sdk工具会产生一个基于.aidl文件的IBinder接口,然后把这个接口保存到工程的gen/目录下。service必须要正确实现这个IBinder接口。客户端的应用就可以绑定到service上,并且使用IBinder做IPC调用。

使用AIDL创建bound service要遵循下面几个步骤:
(1)创建.aidl文件。这个文件定义了编程接口和方法的签名。
(2)实现接口。android sdk工具会基于.aidl文件产生java语言的接口,这个接口有一个抽象的内部类叫做Stub,它继承了Binder,并且实现了AIDL接口的方法,你必须要继承Stub,然后实现里面的抽象方法。
(3)把接口导出给客户端。实现一个Service,覆盖onBind()返回你实现的Stub。

注意:如果在发布了AIDL接口以后对AIDL接口做的任何修改都要保持后向兼容,这样才能不影响其他使用你的service的应用。也就是说,因为别的应用为了能访问你的service,必须要将你的.aidl文件copy它们的应用中,你必须要支持它们最初的接口。

1.创建.aidl文件
AIDL使用简单的语法来声明接口,接口可以有一个或者多个方法,方法可以有参数和返回值。参数和返回值可以是任意类型,甚至可以是别的AIDL接口。

你必须要使用java语言来构建.aidl文件。一个.aidl文件必须要定义一个接口(原文说只能一个,应该是不对的),但是只需要接口声明和方法签名。

默认情况下,AIDL支持下面的数据类型:

(1)java中的所有基本类型(比如int, long, char, boolean等等)
(2)String
(3)CharSequence
(4)List。list当中的元素必须是支持的数据类型之一,或者是其他AIDL接口或者是已经声明的parcelables。List可以用泛型的形式来使用,另一端接收到的实际的类型总是ArrayList,尽管方法声明是使用List接口。
(5)Map。map当中的元素必须是支持的数据类型之一,或者是其他AIDL接口或者是已经声明的parcelables。不支持泛型的map(比如:),另一端接收到的实际的类型总是HashMap,尽管方法声明是使用Map接口。

必须要给没在上面列出的类型包含一个import语句,就算他们是跟你的接口定义在同一个包下面也不例外。

当定义你的service接口的时候,要注意:
(1)方法可以有0个或者多个参数,可以有返回值,也可以返回void。
(2)所有的非基本类型的参数都需要一个方向的标识,表明数据的流向,要么是in,要么是out,或者是inout(看下面的例子)。基本类型默认是in,而且不能是其他的。注意:你应该按照真实的需要来做标记,因为序列化参数代价是很昂贵的。
(3).aidl文件中的注释也会包含在产生的IBinder接口中(除了在import和package语句之前的那些)
(4)只支持方法,不能在AIDL中导出static字段。

下面是一个.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工具会在gen/目录下产生IBinder接口。生成的文件名字跟.aidl的文件名一样,不过后缀是.java(比如:IRemoteService.aidl的结果是IRemoteService.java)

如果你用Eclipse来开发,增量构建几乎是立即就会生成binder类。如果你不是使用的eclipse,比如ANT工具会在你下次构建应用的时候生成binder类。一旦你写完.aidl文件以后,你就应该尽快用ant来构建你的应用,这样你的代码就可以跟生成类连接起来了。

2. 实现interface

当你构建应用的时候,androidsdk工具会产生一个跟你的.aidl文件名相匹配的java接口文件。产生的这个接口包含了一个叫做Stub的子类(可能是YourInterface.Stub),它是一个它的父接口的抽象实现类,它里面声明了.aidl文件中的所有的方法。

注意:Stub同时也定义了一些帮助方法,最著名的是asInterface(),这个方法会接收IBinde参数(一般是传递到客户端onServiceConnected()里面的那个),然后返回stub的接口实例。想了解更多关于如何做这个转化,可以参考调用IPC方法章节。

要实现从.aidl产生出来的接口,要继承产生的Binder接口,然后实现里面的虚方法。

下面是一个使用匿名内部类来实现IRemoteService接口的例子(是由例子中的IRemoteService.aidl产生的):
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类的一个实例,它定义了service的RPC接口。下一步,这个实例会被暴漏给客户端,然后客户端就可以使用它跟service进行交互了。

当实现AIDL接口的时候,要遵守以下几条规则:

(1)对service的调用不能保证一定是在主线程执行的。所以一开始你就应该考虑到多线程,然后把你的service设计成线程安全的。
(2)默认情况下,RPC调用是同步的。如果你明确知道service要花费数秒钟才能完成请求的话,那么你就不应该从主线程做调用,因为可能会导致ANR,你应该在一大单独的线程中做调用。
(3)service中抛出的任何异常都不会发送给客户端。

3. 把接口暴漏给客户端
一旦你实现了service的接口,你就需要把它暴漏给绑定到service上的接口。为了能暴漏service的接口,要继承Service,实现onBind(),然后返回实现了Stub的类。下面是service把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
        }
    };
}
现在当客户端调用bindService()连接到这个service的时候,客户端的onServiceConnected()回调会收到service的onBind()返回的mBinder实例。

客户端必须要能够访问接口,所以如果客户端和服务端是在不同的应用中的话,客户端的应用必须要在src/目录下有.aidl文件的一份拷贝(它会产生android.os.Binder,供客户端访问AIDL方法)。

当客户端在onServiceConnected()回调中收到IBinder以后,必须要调用ourServiceInterface.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接口非常重要,因为这允许系统把对象序列化成可以被跨进程反序列化的基本类型。

要创建一个支持Parcelable的类,必须要遵守:

(1)让你的类实现Parcelable接口。
(2)实现writeToParcel方法,它接收当对象的当前状态,并写到Parcel里面。
(3)添加一个叫做CREATOR的字段,它实现了Parcelable.Creator接口。
(4)最后,创建.aidl文件声明你的parcelable类(就像 Rect.aidl文件那样)。如果你使用了自定义的构建过程,不要把.aidl文件加到你的构建当中。跟C语言当中的头文件类似,.aidl不要编译。

AIDL会使用这些产生的方法和字段来序列化和反序列化你的对象。比如:下面是用来创建Rect类的Rect.aidl文件:
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 CREATOR = new
Parcelable.Creator() {
        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) {
        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();
    }
}
Rect的序列化还是很简单的,从Parcel(http://developer.android.com/reference/android/os/Parcel.html)的其他方法看下如何把其他类型的值写到Parcel中。

警告:不要忘记从别的进程收到数据的安全问题。本例中,Rect类要从Parcel中读取4个数字,你要确保这些数字是在可接受的范文之内的,不管调用者传递的是什么值。关于如何让你的应用原理恶意软件,可以参考安全和权限(http://developer.android.com/guide/topics/security/security.html)。

调用IPC方法

下面是调用者想要用AIDL调用远程接口要遵守的几个步骤:
1.在工程的src目录下包含.aidl文件
2.声明IBinder接口的一个实例(基于AIDL生成出来的)
3.实现ServiceConnection。
4.调用Context.bindService(),把ServiceConnection传递进去。
5.在你的onServiceConnected()实现中,你会收到一个IBinder实例(叫做service)。调用YourInterfaceName.Stub.asInterface((IBinder)service) 把返回的参数转化成YourInterface类型。
6.调用接口定义的方法,你应该总是捕获连接坏掉时会抛出的DeadObjectException异常。这也是远程方法可能抛出的唯一的异常。
7.要断开连接的话,调用Context.unbindService(),要把接口的实例传递进去。

调用IPC service的一些注意事项:
对象是跨进程引用计数的。
你可以把匿名对象作为方法参数进行发送。

了解更多关于绑定到service的信息,请参考Bound Services文档。

下面是一个展示了调用AIDL创建的service的例子,来自ApiDemos工程的远程service的例子:
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 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.");
    }


    /**
     * 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.
            bindService(new Intent(IRemoteService.class.getName()),
                    mConnection, Context.BIND_AUTO_CREATE);
            bindService(new Intent(ISecondary.class.getName()),
                    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 Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }


    };
}


你可能感兴趣的:(android)