Android 进程间通信——AIDL

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

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


这里写图片描述
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容