资讯专栏INFORMATION COLUMN

Aidl进程间通信详细介绍

CoreDump / 2552人阅读

摘要:实际开发中案例操作通信业务需求多进程通信应用服务端某客户端调试工具。当服务端和客户端位于同一个进程时,方法调用不会走跨进程的过程,当两者处于不同晋城市,方法调用走过程,这个逻辑由的内部代理类完成。

目录介绍

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原理

关于aidl应用案例

https://github.com/yangchong2...

关于所有的博客笔记均已开源,是markdown格式,链接地址:https://github.com/yangchong2...

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 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文件,包名要和实体类包名一致。我之前这个地方没加,导致出现错误!

如图所示:

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 CREATOR = new Creator() {
        @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基本信息,方便后期多进程通信测试

/**
 * 
 *     @author yangchong
 *     blog  :
 *     time  : 2018/05/30
 *     desc  : 用于aidl多进程通信服务service
 *     revise:
 * 
*/ 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 getAppInfo(String sign) throws RemoteException { List 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需要用到!


    
        
        
    
3.4 客户端操作步骤

3.4.1 拷贝服务端的AIDL文件到目录下

注意:复制时不要改动任何东西!

如图所示:

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 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信息截图

具体可以通过实际案例操作:后来发现跨进程通信原来挺好玩的……项目地址:https://github.com/yangchong2...

如图所示:

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的权限,需要通过权限才能调用


    

//在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文件

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对象。拿到接口对象之后,我们就能够调用相应方法进行自己的处理

参考文章

Android 进阶7:进程通信之 AIDL 的使用:https://blog.csdn.net/u011240...

Android中AIDL的使用详解:https://www.jianshu.com/p/d1f...

Android Aidl的使用:https://blog.csdn.net/menglon...

安卓中AIDL的使用:https://blog.csdn.net/qq_3200...

关于其他内容介绍 01.关于博客汇总链接

1.技术博客汇总

2.开源项目汇总

3.生活博客汇总

4.喜马拉雅音频汇总

5.其他汇总

02.关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

简书:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo...

开源中国:https://my.oschina.net/zbj161...

泡在网上的日子:http://www.jcodecraeer.com/me...

邮箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault头条:https://segmentfault.com/u/xi...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77281.html

相关文章

  • 进程通信--IPC

    摘要:前言进程间通信简称就是指进程与进程之间进行通信一般来说一个只有一个进程但是可能会有多个线程所以我们用得比较多的是多线程通信比如但是在一些特殊的情况下我们会需要多个进程或者是我们在远程服务调用时就需要跨进程通信了设置多进程设置多进程的步骤很 前言: 进程间通信(Inter-Process Communication),简称IPC,就是指进程与进程之间进行通信.一般来说,一个app只有一个...

    thursday 评论0 收藏0
  • 02.Android之IPC机制问题

    摘要:中为何新增来作为主要的方式运行机制是怎样的机制有什么优势运行机制是怎样的基于通信模式,除了端和端,还有两角色一起合作完成进程间通信功能。 目录介绍 2.0.0.1 什么是Binder?为什么要使用Binder?Binder中是如何进行线程管理的?总结binder讲的是什么? 2.0.0.2 Android中进程和线程的关系?什么是IPC?为何需要进行IPC?多进程通信可能会出现什么问...

    Donne 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<