Aidl进程间通信详细介绍

目录介绍

  • 1.问题答疑
  • 2.Aidl相关属性介绍
    • 2.1 AIDL所支持的数据类型
    • 2.2 服务端和客户端
    • 2.3 AIDL的基本概念
  • 3.实际开发中案例操作
    • 3.1 aidl通信业务需求
    • 3.2 操作步骤伪代码
    • 3.3 服务端操作步骤
    • 3.4 客户端操作步骤
    • 3.5 测试
  • 4.可能出现的问题
    • 4.1 客户端在子线程中发起通信访问问题
    • 4.2 什么情况下会导致远程调用失败
    • 4.3 设置aidl的权限,需要通过权限才能调用
  • 5.部分源码解析
    • 5.1 服务端aidl编译生成的java文件
    • 5.2 客户端绑定服务端service原理

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

关于aidl应用案例

1.问题答疑

  • 1.1.0 AIDL所支持的数据类型有哪些?
  • 1.1.1 提供给客户端连接的service什么时候运行?
  • 1.1.2 Stub类是干什么用的呢?
  • 1.1.3 如何解决远程调用失败的问题?

2.Aidl相关属性介绍

2.1 AIDL所支持的数据类型

  • 在AIDL中,并非支持所有数据类型,他支持的数据类型如下所示:
    • 基本数据类型(int、long、char、boolean、double、float、byte、short)
    • String和CharSequence
    • List:只支持ArrayList,并且里面的每个元素必须被AIDL支持
    • Map: 只支持HashMap, 同样的,里面的元素都必须被AIDL支持,包括key和value
    • Parcelable:所有实现了Parcelable接口的对象
    • AIDL: 所有的AIDL接口本身也可以在AIDL 文件中使用

2.2 服务端和客户端

  • 2.2.1 服务端
    • 注意:服务端就是你要连接的进程。服务端给客户端一个Service,在这个Service中监听客户端的连接请求,然后创建一个AIDL接口文件,里面是将要实现的方法,注意这个方法是暴露给客户端的的。在Service中实现这个AIDL接口即可
  • 2.2.2 客户端
    • 客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转换成AIDL接口所属的类型,最后调用AIDL的方法就可以了。

2.3 AIDL的基本概念

  • AIDL:Android Interface Definition Language,即Android接口定义语言;用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

3.实际开发中案例操作

3.1 aidl通信业务需求

  • aidl多进程通信应用——服务端:某app;客户端:app调试工具。注意:aidl多进程通信是指两个独立app之间的通信……
  • 打开app调试工具,可以通过绑定服务端某app的service,获取到公司app的信息,比如渠道,版本号,签名,打包时间,token等属性
  • 通过app调试工具,可以通过aidl接口中的方法设置属性,设置成功后,查看某app是否设置属性成功

3.2 操作步骤伪代码

  • 3.2.1 服务端
    • 步骤1:新建定义AIDL文件,并声明该服务需要向客户端提供的接口
    • 补充,如果aidl中有对象,则需要创建对象,并且实现Parcelable
    • 步骤2:在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法(onCreat、onBind()、blabla)
    • 步骤3:在AndroidMainfest.xml中注册服务 & 声明为远程服务
  • 3.2.2 客户端
    • 步骤1:拷贝服务端的AIDL文件到目录下
    • 步骤2:使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
    • 步骤3:通过Intent指定服务端的服务名称和所在包,绑定远程Service

3.3 服务端操作步骤

  • 3.3.1 创建一个aidl文件【注意:在main路径下创建】
    • 可以看到里面有一个AppInfo,注意这个类需要自己创建,并且手动导包进来。否则编译时找不到……
// ICheckAppInfoManager.aidl
package cn.ycbjie.ycaudioplayer;
import cn.ycbjie.ycaudioplayer.AppInfo;
// Declare any non-default types here with import statements

interface ICheckAppInfoManager {

    //获取app信息,比如token,版本号,签名,渠道等信息
    List<AppInfo> getAppInfo(String sign);
    
    boolean setToken(String sign,String token);

    boolean setChannel(String sign,String channel);

    boolean setAppAuthorName(String sign,String name);

}
  • 3.3.2 创建一个AppInfo类,实现Parcelable接口
    • 这个类就是需要用的实体类,因为是跨进程,所以实现了Parcelable接口,这个是Android官方提供的,它里面主要是靠Parcel来传递数据,Parcel内部包装了可序列化的数据,能够在Binder中自由传输数据。
    • 注意:如果用到了自定义Parcelable对象,就需要创建一个同名的AIDL文件,包名要和实体类包名一致。我之前这个地方没加,导致出现错误!
    • 如图所示:
  • image
import android.os.Parcel;
import android.os.Parcelable;

public class AppInfo  implements Parcelable {

    private String key;
    private String value;

    public AppInfo(String key, String value) {
        this.key = key;
        this.value = value;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.key);
        dest.writeString(this.value);
    }

    public AppInfo() {
    }

    protected AppInfo(Parcel in) {
        this.key = in.readString();
        this.value = in.readString();
    }

    public static final Creator<AppInfo> CREATOR = new Creator<AppInfo>() {
        @Override
        public AppInfo createFromParcel(Parcel source) {
            return new AppInfo(source);
        }

        @Override
        public AppInfo[] newArray(int size) {
            return new AppInfo[size];
        }
    };

    @Override
    public String toString() {
        return "AppInfo{" +
                "key='" + key + '\'' +
                ", value='" + value + '\'' +
                '}';
    }
}
  • 3.3.3 在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法(onCreat、onBind()等)
    • 重写的onBinde()方法中返回Binder对象,这个Binder对象指向IAdvertManager.Stub(),这个Stub类并非我们自己创建的,而是AIDL自动生成的。系统会为每个AIDL接口在build/source/aidl下生成一个文件夹,它的名称跟你命名的AIDL文件夹一样,里面的类也一样。
    • 创建binder对象,在这个getAppInfo方法中,可以设置app基本信息,方便后期多进程通信测试
/**
 * <pre>
 *     @author yangchong
 *     blog  :
 *     time  : 2018/05/30
 *     desc  : 用于aidl多进程通信服务service
 *     revise:
 * </pre>
 */
public class AppInfoService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.i("AppInfoService--IBinder:");
        return binder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtils.i("AppInfoService--onCreate:");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LogUtils.i("AppInfoService--onDestroy:");
    }

    /**
     * 1.核心,Stub里面的方法运行的binder池中。
     * 2.Stub类并非我们自己创建的,而是AIDL自动生成的。
     *   系统会为每个AIDL接口在build/generated/source/aidl下生成一个文件夹,它的名称跟你命名的AIDL文件夹一样
     * 3.Stub类,是一个内部类,他本质上是一个Binder类。当服务端和客户端位于同一个进程时,方法调用不会走跨进程的transact过程,
     *   当两者处于不同晋城市,方法调用走transact过程,这个逻辑由Stub的内部代理类Proxy完成。
     */
    private final IBinder binder = new ICheckAppInfoManager.Stub() {
        @Override
        public List<AppInfo> getAppInfo(String sign) throws RemoteException {
            List<AppInfo> list=new ArrayList<>();
            String aidlCheckAppInfoSign = AppToolUtils.getAidlCheckAppInfoSign();
            LogUtils.e("AppInfoService--AppInfoService",aidlCheckAppInfoSign+"-------------"+sign);
            if(!aidlCheckAppInfoSign.equals(sign)){
                return list;
            }
            list.add(new AppInfo("app版本号(versionName)", BuildConfig.VERSION_NAME));
            list.add(new AppInfo("app版本名称(versionCode)", BuildConfig.VERSION_CODE+""));
            list.add(new AppInfo("打包时间", BuildConfig.BUILD_TIME));
            list.add(new AppInfo("app包名", getPackageName()));
            list.add(new AppInfo("app作者", SPUtils.getInstance(Constant.SP_NAME).getString("name","杨充")));
            list.add(new AppInfo("app渠道", SPUtils.getInstance(Constant.SP_NAME).getString("channel")));
            list.add(new AppInfo("token", SPUtils.getInstance(Constant.SP_NAME).getString("token")));
            list.add(new AppInfo("App签名", AppToolUtils.getSingInfo(getApplicationContext(), getPackageName(), AppToolUtils.SHA1)));
            return list;
        }


        @Override
        public boolean setToken(String sign, String token) throws RemoteException {
            if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){
                return false;
            }
            SPUtils.getInstance(Constant.SP_NAME).put("token",token);
            LogUtils.i("AppInfoService--setToken:"+ token);
            return true;
        }

        @Override
        public boolean setChannel(String sign, String channel) throws RemoteException {
            if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){
                return false;
            }
            SPUtils.getInstance(Constant.SP_NAME).put("channel",channel);
            LogUtils.i("AppInfoService--setChannel:"+ channel);
            return true;
        }

        @Override
        public boolean setAppAuthorName(String sign, String name) throws RemoteException {
            if(!AppToolUtils.getAidlCheckAppInfoSign().equals(sign)){
                return false;
            }
            SPUtils.getInstance(Constant.SP_NAME).put("name",name);
            LogUtils.i("AppInfoService--setAppAuthorName:"+ name);
            return true;
        }
    };
}
  • 3.3.4 在AndroidMainfest.xml中注册服务 & 声明为远程服务
    • 在清单文件注册即可,需要设置action。这个在客户端中绑定服务service需要用到!
<service android:name=".service.AppInfoService"
    android:process=":remote"
    android:exported="true">
    <intent-filter>
        <action android:name="cn.ycbjie.ycaudioplayer.service.aidl.AppInfoService"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

3.4 客户端操作步骤

  • 3.4.1 拷贝服务端的AIDL文件到目录下
    • 注意:复制时不要改动任何东西!
    • 如图所示:


      image
  • 3.4.2 通过Intent指定服务端的服务名称和所在包,绑定远程Service
    • 通过Intent指定服务端的服务名称和所在包,进行Service绑定;
    • 创建ServiceConnection对象
    /**
     * 跨进程绑定服务
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        //通过Intent指定服务端的服务名称和所在包,与远程Service进行绑定
        //参数与服务器端的action要一致,即"服务器包名.aidl接口文件名"
        intent.setAction("cn.ycbjie.ycaudioplayer.service.aidl.AppInfoService");
        //Android5.0后无法只通过隐式Intent绑定远程Service
        //需要通过setPackage()方法指定包名
        intent.setPackage(packName);
        //绑定服务,传入intent和ServiceConnection对象
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
    
    
    /**
     * 创建ServiceConnection的匿名类
     */
    private ServiceConnection connection = new ServiceConnection() {
        //重写onServiceConnected()方法和onServiceDisconnected()方法
        // 在Activity与Service建立关联和解除关联的时候调用
        @Override public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "无法绑定aidlServer的AIDLService服务");
            mBound = false;
        }
    
        //在Activity与Service建立关联时调用
        @Override public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "完成绑定aidlServer的AIDLService服务");
            //使用IAppInfoManager.Stub.asInterface()方法获取服务器端返回的IBinder对象
            //将IBinder对象传换成了mAIDL_Service接口对象
            messageCenter = ICheckAppInfoManager.Stub.asInterface(service);
            mBound = true;
            if (messageCenter != null) {
                try {
                    //链接成功
                    Toast.makeText(MainActivity.this,"链接成功",Toast.LENGTH_SHORT).show();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    
  • 3.4.3 使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
    • 通过步骤3.4.2完成了跨进程绑定服务,接下来通过调用方法获取到数据。这里可以调用getAppInfo方法获取到服务端[app]的数据
    private void getAppInfo() {
        //如果与服务端的连接处于未连接状态,则尝试连接
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试",
                    Toast.LENGTH_SHORT).show();
            return;
        }
        if (messageCenter == null) {
            return;
        }
        try {
            List<AppInfo> info = messageCenter.getAppInfo(Utils.getSign(packName));
            if(info==null || (info.size()==0)){
                Toast.makeText(this, "无法获取数据,可能是签名错误!", Toast.LENGTH_SHORT).show();
            }else {
                mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
                FirstAdapter adapter = new FirstAdapter(info, this);
                mRecyclerView.setAdapter(adapter);
                adapter.setOnItemClickListener(new FirstAdapter.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, int position) {
    
                    }
                });
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    

3.5 测试

  • 最后看看通过测试工具[客户端]跨进程获取服务端app信息截图

4.可能出现的问题

4.1 客户端在子线程中发起通信访问问题

  • 当客户端发起远程请求时,客户端会挂起,一直等到服务端处理完并返回数据,所以远程通信是很耗时的,所以不能在子线程发起访问。由于服务端的Binder方法运行在Binder线程池中,所以应采取同步的方式去实现,因为它已经运行在一个线程中呢。

4.2 什么情况下会导致远程调用失败

  • Binder是会意外死亡的。如果服务端的进程由于某种原因异常终止,会导致远程调用失败,如果我们不知道Binder连接已经断裂, 那么客户端就会受到影响。不用担心,Android贴心的为我们提供了连个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知。
// 在创建ServiceConnection的匿名类中的onServiceConnected方法中
// 设置死亡代理
messageCenter.asBinder().linkToDeath(deathRecipient, 0);


/**
 * 给binder设置死亡代理
 */
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {

    @Override
    public void binderDied() {
        if(messageCenter == null){
            return;
        }
        messageCenter.asBinder().unlinkToDeath(deathRecipient, 0);
        messageCenter = null;
        //这里重新绑定服务
        attemptToBindService();
    }
};

4.3 设置aidl的权限,需要通过权限才能调用

<!--给aidl多进程通信,服务加入权限验证功能-->
<permission android:name="aidl.AppInfoService"
    android:protectionLevel="normal"/>
    

//在AppInfoService服务中验证权限
@Nullable
@Override
public IBinder onBind(Intent intent) {
    LogUtils.i("AppInfoService--IBinder:");
    int check = checkCallingOrSelfPermission("aidl.AppInfoService");
    if(check == PackageManager.PERMISSION_DENIED){
        return null;
    }
    return binder;
}

5.部分源码解析

5.1 服务端aidl编译生成的java文件

  • 5.1.1 首先找到aidl编译生成的Java文件


    image
  • 5.1.2 分析生成的java文件
    • 这个ICheckAppInfoManager.java就是系统为我们生成的相应java文件,简单说下这个类。它声明了三个方法getAppInfo,setToken和setChannel,分明就是我们AIDL接口中的三个方法。同时他声明了3个id用来标识这几个方法,id用于标识在transact过程中客户端请求的到底是哪个方法。接着就是我们的Stub,可以看到它是一个内部类,他本质上是一个Binder类。当服务端和客户端位于同一个进程时,方法调用不会走跨进程的transact过程,当两者处于不同晋城市,方法调用走transact过程,这个逻辑由Stub的内部代理类Proxy完成。
    • 这个Stub对象之所以里面有我们AIDL的接口,正是因为官方替我们做好了,我们只要在这里具体实现就好了。

5.2 客户端绑定服务端service原理

  • 客户端也非常简单,首先我们连接到服务端Service,在连接成功时,也就是onServiceConnected方法里,通过asInterface(service)方法可以将服务端的Binder对象转换成客户端所需的AIDL的接口的对象。这种转换是区分进程的,如果是同一进程,那么此方法返回的就是Stub本身,否则返回的就是系统Stub.proxy对象。拿到接口对象之后,我们就能够调用相应方法进行自己的处理

参考文章

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

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

推荐阅读更多精彩内容

  • 目录介绍 1.问题答疑 2.Aidl相关属性介绍2.1 AIDL所支持的数据类型2.2 服务端和客户端2.3 AI...
    杨充211阅读 932评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,783评论 1 23
  • 一、IPC简介 (1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨...
    遥遥的远方阅读 7,069评论 0 3
  • 本篇包含进程间通信——AIDL所涉及到的知识的自我总结(内容详细) 通过前段时间对AIDL的学习以及最近一些资料的...
    arvinljw阅读 2,938评论 0 17