Android 进程间通信——AIDL

作者: 朋永 | 来源:发表于2018-01-18 09:27 被阅读31次

    AIDL(Android Interface Definition Language)——进程间通信的一种机制。它允许您定义客户端和服务端通过使用进程间通信(IPC)进行通信的编程接口。在Android上,一个进程无法正常访问另一个进程的内存。所以说,他们需要将他们的对象分解成操作系统能够理解的原语,并且把这些对象放在你的边界上。编写这些代码非常繁琐,所以Android使用AIDL来处理它。

    Demo下载地址http://blog.csdn.net/vnanyesheshou/article/details/79047650

    1 使用AIDL的必要条件

    • 只有当你需要来自不同应用的客户端通过IPC(进程间通信)通信来访问你的服务时,并且想在服务里处理多线程的业务,这时就需要使用AIDL。
    • 如果你不需要同时对几个应用进程IPC操作,你最好通过实现Binder接口来创建你的接口。
    • 如果你仍需要执行IPC操作,但不需要处理多线程,使用Messenger来实现接口即可。

    2 AIDL的使用

    使用Java编程语言语法在.aidl文件中定义您的AIDL接口,然后将其保存在承载服务的应用程序和任何其他绑定到该服务的应用程序的源代码(在src /目录中)。
    当应用程序构建包含.aidl文件时,Android SDK工具将生成一个基于.aidl文件的IBinder接口,并将其保存在项目的gen /目录中。 该服务必须适当地实现IBinder接口。 然后,客户端应用程序可以绑定到服务并从IBinder调用方法来执行IPC。

    使用AIDL 创建绑定的服务,具体步骤:

    1. 创建.aidl文件
      这个文件用方法签名来定义编程接口。
    2. 实现接口
      Android SDK工具根据你的.aidl文件以Java编程语言生成一个接口 。这个接口有一个名为Stub的内部抽象类,它继承了Binder并实现了AIDL接口中的方法。你必须继承这个 Stub类并实现这些方法。
    3. 将接口公开给客户端
      实现一个服务并重写onBind() 来返回你的Stub类的实现。

    2.1 创建.aidl文件

    AIDL使用简单的语法,可以用一个或多个方法(可以接收参数和返回值)来声明接口。参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。
    必须使用Java编程语言构建.aidl文件。 每个.aidl文件都必须定义一个接口,并且只需要接口声明和方法签名。

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

    • Java编程语言中的所有基本类型(如int,long,char,boolean等)
    • String
    • CharSequence
    • List
      List中的所有元素都必须是支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受的元素之一。 列表可以选择性地用作“通用”类(例如List <String>)。 对方收到的实际具体类始终是一个ArrayList,尽管生成的方法是使用List接口。
    • Map
      Map中的所有元素都必须是此列表中受支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受元素之一。 通用映射(如Map <String,Integer>形式的映射)不被支持。对方接收的实际具体类总是一个HashMap,尽管该方法是使用Map接口生成的。

    对于上面没有列出的每种附加类型,即使它们在与接口相同的包中定义,也必须包含一条import语句。

    在定义服务接口时,注意:

    • 方法可以采用零个或多个参数,并返回一个值或void。
    • 所有非原始参数都需要一个指向数据的方向标签。in,out或者inout(见下面的例子)。基本数据默认是in的,不能以其他方式。
      警告:您应该将方向限制在真正需要的地方,因为编组参数非常昂贵。
    • 包含在.aidl文件中的所有代码注释都包含在生成的IBinder接口中(导入和包装语句之前的注释除外)。
    • 只支持方法; 您不能在AIDL中公开静态字段。

    如下是一个.aidl 例子。IRemoteService.aidl

    package com.zpengyong.aidl;
    
    interface IRemoteService {
        void sendMessage(in String str);
        
        boolean play();
        
        boolean pause();
        
        boolean stop();
    }
    

    只需将.aidl文件保存在项目src/目录中,SDK工具会在项目gen/目录中生成IBinder接口文件。生成的文件名与.aidl文件名相匹配,但带有.java扩展名(例如IRemoteService.aidl结果IRemoteService.java)。

    2.2 实现接口

    IRemoteService.java接口文件包含一个名为Stub的类 ,它继承了Binder ,实现了IRemoteService接口,并声明.aidl文件中的所有方法。
    Stub还定义了一些辅助方法,最值得注意的是asInterface(),它接受一个IBinder(通常是传递给客户端的onServiceConnected()回调方法中的参数),并返回stub接口的一个实例。

    要实现从.aidl生成的接口,请继承生成的Binder接口(例如IRemoteService.Stub),并实现从.aidl文件继承的方法。
    下面是示例:

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){  
        public void sendMessage(String str){
            Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
            Message msg = new Message();
            msg.what = MSG_RECEIVE_MESSAGE;
            msg.obj = str;
            mHandler.sendMessage(msg);
        }
        
        public boolean play(){
            mService.play();
            return true;
        }
        
        public boolean pause(){
            mService.pause();
            return true;
        }
        
        public boolean stop(){
            mService.stop();
            return true;
        }
    };
    

    现在mBinder是Stub类的一个实例(一个Binder),它定义了服务的RPC接口。 在下一步中,这个实例被暴露给客户,以便他们可以与服务交互。

    <font color =red>在实现AIDL接口时,您应该注意一些规则</font>:

    • 传入的调用并不保证在主线程中执行,所以需要从头开始考虑多线程,并将服务正确地构建为线程安全的。
    • 默认情况下,RPC调用是同步的。如果您知道该服务需要超过几毫秒才能完成请求,则不应该从活动的主线程调用该服务,因为它可能会挂起应用程序(Android可能会显示“应用程序不响应”对话框,应该通常从客户端的一个单独的线程调用它们。
    • 抛出的任何异常都将被发回给调用者。

    2.3 将接口公开给客户端

    为了暴露你的服务的接口,扩展Service并实现onBind()返回实现生成的Stub的类的实例。 这里是一个示例服务,将IRemoteService示例接口公开给客户端。

    public class AIDLService 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 void sendMessage(String str){
                Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
                Message msg = new Message();
                msg.what = MSG_RECEIVE_MESSAGE;
                msg.obj = str;
                mHandler.sendMessage(msg);
            }
            
            public boolean play(){
                mService.play();
                return true;
            }
            
            public boolean pause(){
                mService.pause();
                return true;
            }
            
            public boolean stop(){
                mService.stop();
                return true;
            }
        };
    }
    

    现在,当一个客户端(比如一个activity)调用bindService()连接到这个服务时,客户端的onServiceConnected()回调会收到mBinder(服务onBind() 方法返回的 实例)。
    客户端还必须能够访问接口类,所以如果客户端和服务在不同的应用程序中,那么客户端的应用程序必须在其src/目录中拥有该.aidl文件的副本(这会生成android.os.Binder 接口 - 为客户端提供对AIDL方法的访问)。
    当客户端收到onServiceConnected()回调,得到IBinder,它必须调用 IRemoteService.Stub.asInterface(service)转换成IRemoteService类型。例如:

    private IRemoteService mIRemoteService;
    private ServiceConnection mConnection = new ServiceConnection() {
    
        // 当与服务端连接成功时,回调该方法。
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //转换
            mIRemoteService = IRemoteService.Stub.asInterface(service);
        }
    
        // 当与服务端连接异常断开时,回调该方法。
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIRemoteService = null;
        }
    };
    

    3 调用IPC方法

    以下是调用类必须用来调用AIDL定义的远程接口的步骤:

    1. 将.aidl文件包含在项目src /目录中。
    2. 声明一个IBinder接口的实例(基于AIDL生成)。
    3. 实现<font color=red>ServiceConnection.
    4. 调用<font color=red>Context.bindService()</font>,传入你的ServiceConnection实现。
    5. 在onServiceConnected()实现中,将收到一个IBinder实例。 调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回的参数强制转换为YourInterfaceName类型。
    6. 调用你在接口上定义的方法。 您应该始终捕获连接断开时引发的DeadObjectException异常; 这将是远程方法抛出的唯一异常。
    7. 要断开连接,调用Context.unbindService()。

    如下:

    package com.zpengyong.aidlclient;
    
    import com.zpengyong.aidl.IRemoteService;
    import com.zpengyong.aidl.IRemoteServiceCallback;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.text.Editable;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    public class MainActivity extends Activity implements OnClickListener {
        private final static String TAG = "MainActivity";
    
        private TextView mStateText, mMusicState;
        private Button mBtnHello, mBtnBind, mBtnStart, mBtnPause, mBtnStop;
        private EditText mTextMessage;
        
        private IRemoteService mIRemoteService;
        
        private final int STATE_DISCONNECTED = 1;
        private final int STATE_CONNECTING = 2;
        private final int STATE_CONNECTED = 3;
        private final int STATE_DISCONNECTING = 4;
        //与服务端的连接状态
        private int mBindState = STATE_DISCONNECTED;
    
        private ServiceConnection mConnection = new ServiceConnection() {
    
            // 当与服务端连接成功时,回调该方法。
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected");
                mIRemoteService = IRemoteService.Stub.asInterface(service);
                mStateText.setText("connected");
                mBindState = STATE_CONNECTED;
                mBtnBind.setText("解绑");
                try {
                    mIRemoteService.registerCallback(mIRemoteServiceCallback);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            // 当与服务端连接异常断开时,回调该方法。
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "onServiceDisconnected");
                mIRemoteService = null;
                mStateText.setText("disconnected");
                mBindState = STATE_DISCONNECTED;
                mBtnBind.setText("绑定");
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mStateText = (TextView) findViewById(R.id.connectState);
            mBtnHello = (Button) findViewById(R.id.sendMessage);
            mBtnBind = (Button)findViewById(R.id.bind);
            mBtnStart = (Button)findViewById(R.id.start_play);
            mBtnPause = (Button)findViewById(R.id.pause);
            mBtnStop = (Button)findViewById(R.id.stop_play);
            mBtnHello.setOnClickListener(this);
            mBtnStart.setOnClickListener(this);
            mBtnPause.setOnClickListener(this);
            mBtnStop.setOnClickListener(this);
            mBtnBind.setOnClickListener(this);
            mTextMessage = (EditText) findViewById(R.id.message);
            mMusicState = (TextView)findViewById(R.id.musicState);
        }
    
        private void bind() {
            mBindState = STATE_CONNECTING;
            Intent intent = new Intent();
            // Android 5.0 以上显示绑定服务
            intent.setComponent(new ComponentName("com.zpengyong.aidl", "com.zpengyong.aidl.AIDLService"));
            // 绑定服务
            this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            mStateText.setText("connecting");
        }
    
        private void unbind() {
            mBindState = STATE_DISCONNECTING;
            try {
                mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mStateText.setText("disconnecting");
            //解除与Service的连接
            unbindService(mConnection);
            mBindState = STATE_DISCONNECTED;
            mStateText.setText("disconnected");
            mBtnBind.setText("绑定");
            mIRemoteService = null;
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if(mBindState != STATE_DISCONNECTED){
                unbind();
            }
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.sendMessage:
                String str = mTextMessage.getText().toString();
                if(str == null ||str.length() == 0)
                    return;
                if(mIRemoteService == null)
                    return;
                try {
                    mIRemoteService.sendMessage(str);
                } catch (RemoteException e1) {
                    e1.printStackTrace();
                }
                break;
            case R.id.bind:
                if(mBindState == STATE_DISCONNECTED){
                    bind();
                }else if(mBindState == STATE_CONNECTED){
                    unbind();
                }
                break;
            case R.id.start_play:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.play();
                    Log.i(TAG, "play ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.pause:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.pause();
                    Log.i(TAG, "pause ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                };
                break;
            case R.id.stop_play:
                if(mIRemoteService == null)
                    return;
                try {
                    boolean ret = mIRemoteService.stop();
                    Log.i(TAG, "stop ret="+ret);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
            }
        }
    }
    

    效果图如下:


    这里写图片描述

    4 服务端回调客户端

    如上的列子中只有客户端调用服务端的方法,并不能服务端调用客户端。

    在之前的IRemoteService.aidl文件中添加接口

    package com.zpengyong.aidl;
    import com.zpengyong.aidl.IRemoteServiceCallback;
    
    interface IRemoteService {
        
        void registerCallback(in IRemoteServiceCallback cb);
        
        void unregisterCallback(in IRemoteServiceCallback cb);
        
        void sendMessage(in String str);
        
        boolean play();
        
        boolean pause();
        
        boolean stop();
    }
    

    IRemoteServiceCallback.aidl中添加服务端调用客户端的接口。
    该文件服务度和客户端都需要包含该文件。

    package com.zpengyong.aidl;
    
    interface IRemoteServiceCallback {
        void stateChange(int value);
    }
    

    1 客户端实现回调接口
    要实现从IRemoteServiceCallback.aidl生成的接口,请继承生成的Binder接口(IRemoteServiceCallback.Stub),并实现从IRemoteServiceCallback.aidl文件继承的方法。

        private IRemoteServiceCallback mIRemoteServiceCallback = new IRemoteServiceCallback.Stub() {
            
            @Override
            public void stateChange(int value) throws RemoteException {
                Log.i(TAG, "stateChange value="+value);
                if(value == 1){
                    mMusicState.setText("开始播放");
                }else if(value == 2){
                    mMusicState.setText("暂停播放");
                }else if(value == 3){
                    mMusicState.setText("停止播放");
                }else if(value == 4){
                    mMusicState.setText("播放出错");
                }else {
                    mMusicState.setText("unknown");
                }
            }
        };
    

    2 注册回调
    客户端bindservice成功后会回调onServiceConnected,客户端可以获取到mIRemoteService,可以调用远端的放,这时可以通过调用远端方法注册回调接口实例。

        private ServiceConnection mConnection = new ServiceConnection() {
    
            // 当与服务端连接成功时,回调该方法。
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected");
                mIRemoteService = IRemoteService.Stub.asInterface(service);
                mStateText.setText("connected");
                mBindState = STATE_CONNECTED;
                mBtnBind.setText("解绑");
                try {
                    //注册回调。
                    mIRemoteService.registerCallback(mIRemoteServiceCallback);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    }
    

    3 服务端保存回调接口
    由于AIDl支持多个客户端绑定,并处理并发请求。所以这里要将回调接口存到列表中,避免后注册的将前面注册的回调接口覆盖。

    //aidl支持多个客户端绑定,并且处理并发进程间通信,所以这里要存列表中。
    final RemoteCallbackList<IRemoteServiceCallback> mCallbackList
            = new RemoteCallbackList<IRemoteServiceCallback>();
    
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
        
        public void registerCallback(IRemoteServiceCallback cb){
            if(cb != null)mCallbackList.register(cb);
        }
        
        public void unregisterCallback(IRemoteServiceCallback cb){
            if(cb != null)mCallbackList.unregister(cb);
        }
        。。。
    }       
    

    4 服务器调用客户端方法
    遍历回调list,分别调用其stateChange方法,实现服务端调用客户端,实现双方通信。

     private void callstateChange(int value){
         //遍历保存的IRemoteServiceCallback,发送状态改变的消息。
         int num = mCallbackList.beginBroadcast();
         for(int i=0; i<num; i++){
             try {
                mCallbackList.getBroadcastItem(i).stateChange(value);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
         }
         mCallbackList.finishBroadcast();
     }
    

    当服务端调用回调接口的方法后,客户端的接口实现中就会收到响应。

    4 取消注册
    客户端unbindService前 调用取消注册的方法。

    private void unbind() {
        mBindState = STATE_DISCONNECTING;
        try {
            mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mStateText.setText("disconnecting");
        //解除与Service的连接
        unbindService(mConnection);
        mBindState = STATE_DISCONNECTED;
        mStateText.setText("disconnected");
        mBtnBind.setText("绑定");
        mIRemoteService = null;
    }
    

    客户端接收服务端的回调,效果显示如下:


    这里写图片描述

    相关文章

      网友评论

        本文标题:Android 进程间通信——AIDL

        本文链接:https://www.haomeiwen.com/subject/bbeioxtx.html