安卓IPC之aidl使用--aidl常见使用

Posted zy9011

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓IPC之aidl使用--aidl常见使用相关的知识,希望对你有一定的参考价值。

android上有很多跨进程的通讯方法例如aidl,messenger,ContentProvider,BroadCast,Socket等等,安卓进程间通信(IPC)那肯定要谈到AIDL。

你知道你需要进程间通信、需要AIDL(以及Binder),那么可以默认你对这些概念已经有了一些了解,你(大致)知道它们是什么,它们有什么用,所以为了节约大家的眼力和时间。
安卓IPC之aidl使用(一)–aidl常见使用
安卓IPC之aidl使用(二)—aidl本地实现
安卓IPC之aidl使用(三)—System aidl调用

AIDL简单介绍

AIDL:Android Interface Definition Language,即Android接口定义语言

Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。我们知道建立AIDL服务的步骤有一下三个步骤:
建立一个扩展名为aidl的文件。该文件的语法类似于Java代码,但会稍有不同
建立一个服务类(Service的子类)
实现由aidl文件生成的Java接口

AIDL 语法

其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别——毕竟它只是被创造出来简化Android程序员工作的,太复杂不好——所以在这里我就着重的说一下它和 Java 不一样的地方。主要有下面这些点:

  • 文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。

  • 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包, 就算目标文件与当前正在编写的 .aidl 文件在同一个包下 ——在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Person.java ,另一个叫做 Person.aidl ,它们都在com.losileeya.aidlmaster包下 ,现在我们需要在 .aidl 文件里使用 Peerson 对象,那么我们就必须在 .aidl 文件里面写上 *import com.losileeya.aidlmaster.Person;哪怕 .java 文件和 .aidl 文件就在一个包下。

    默认支持的数据类型包括:

    • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
    • String 类型。
    • CharSequence类型。
    • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
    • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
  • 定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag 是这样的: AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
    另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意, 请不要滥用定向 tag ,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。

  • 两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
    注: 所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。

AIDL实现步骤

我的编译环境为Android Studio2.1.3,SDK Version 24,JDK 1.8

service端

.AIDL生成

鼠标移到module上面去,点击右键,然后 new->AIDL->AIDL File,按下鼠标左键就会弹出一个框提示生成AIDL文件了。生成AIDL文件之后,项目的目录会变成这样的:

里面的代码按照你的意思来,必需以interface开头,并且参数里面使用了引用的parcelable对象,我们需要import,示例如下:

// IRemoteAidl.aidl
package com.losileeya.aidlmaster;
import com.losileeya.aidlmaster.Person;
// Declare any non-default types here with import statements
//这个文件的作用是引入了一个序列化对象 Person 供其他的AIDL文件使用
//注意:Person.aidl与Person.java的包名应当是一样的
parcelable Person;
interface IRemoteAidl 
   int add(int num1,int num2);//计算2个数之和
     //传参时除了Java基本类型以及String,CharSequence之外的类型
       //都需要在前面加上定向tag,具体加什么量需而定
   List<Person>addPerson(in Person p);//添加一个parcelable

好了我们这里提到了parcelable对象,所以我们需要1个Person.aidl与Person.java而且须保证包名应当是一样的

Parcelable 类型的使用

先写Person.java,并且实现parcelable接口,android studio早就替我们提供了这样的插件。

package com.losileeya.aidlmaster;
import android.os.Parcel;
import android.os.Parcelable;
/**
 * User: Losileeya (847457332@qq.com)
 * Date: 2016-07-10
 * Time: 00:37
 * 类描述:实现Parcelable序列化
 *
 * @version :
 */
public class Person implements Parcelable 
    private String name;
    private int age;

    @Override
    public int describeContents() 
        return 0;
    

    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeString(this.name);
        dest.writeInt(this.age);
    

    public Person() 
    
    public Person(String name, int age) 
        this.name = name;
        this.age = age;
    
    protected Person(Parcel in) 
        this.name = in.readString();
        this.age = in.readInt();
    

    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() 
        @Override
        public Person createFromParcel(Parcel source) 
            return new Person(source);
        

        @Override
        public Person[] newArray(int size) 
            return new Person[size];
        
    ;

Person.aidl

package com.losileeya.aidlmaster;
// Declare any non-default types here with import statements
parcelable Person;

到这里我们就已经将AIDL文件新建并且书写完毕了,clean 一下项目,如果没有报错,这一块就算是大功告成了。

编写Service

在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。

接下来我直接贴上我写的服务端代码:

package com.losileeya.aidlmaster;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

/**
 * User: Losileeya (847457332@qq.com)
 * Date: 2016-07-09
 * Time: 12:28
 * 类描述:aidl实现功能的逻辑代码
 *
 * @version :
 */
public class IRemoteService extends Service
    private ArrayList<Person>persons;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        persons=new ArrayList<>();
        return mBinder;
    
    private IBinder mBinder=new IRemoteAidl.Stub()
        @Override
        public int add(int num1, int num2) throws RemoteException 
            Log.d("TAG","服务已开启");
            return num1+num2;
        

        @Override
        public List<Person> addPerson(Person p) throws RemoteException 
            persons.add(p);
            return persons;
        
    ;

整体的代码结构很清晰,大致可以分为2块:第一块是 重写 IRemoteAidl.Stub 中的方法 。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。第三块是 重写 onBind() 方法 。在里面返回写好的 IRemoteAidl.Stub 。

manifest 配置

        <service android:name=".IRemoteService">
            <intent-filter>
                <action android:name="com.losileeya.aidlmaster.IRemoteService"/>
            </intent-filter>
        </service>

到这里我们的服务端代码就编写完毕了,真正是扯得多,代码少。

Client代码编写

首先就是拷贝AIDL文件夹到客户端目录下(与java目录同级)

然后需要重新写一个Person.java,也可以直接从服务端拷贝过来,必须保证Person.java和aidl的文件是相同的包名。

获得IRemoteAidl和ServiceConnection

 private ServiceConnection conn=new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            iRemoteAidl=   IRemoteAidl.Stub.asInterface(service);
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            iRemoteAidl=null;
        
    ;

bindService

 private void bindService() 
        Intent intent=new Intent();
        intent.setComponent(new ComponentName("com.losileeya.aidlmaster","com.losileeya.aidlmaster.IRemoteService"));
        bindService(intent,conn, Context.BIND_AUTO_CREATE);

    

ComponentName里面的绝对不能写错,前面一个是包名,后面是启动的服务类,

这样基本就写好了,可以通信了。

## MainActivity
package com.losileeya.aidlclient;
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.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.losileeya.aidlmaster.IRemoteAidl;
import com.losileeya.aidlmaster.Person;

import java.util.ArrayList;
import java.util.Iterator;

public class MainActivity extends AppCompatActivity implements View.OnClickListener 
    private IRemoteAidl iRemoteAidl=null;
    private EditText etNum1,etNum2,etResult;
    private Button btnCalculate;
    private ArrayList<Person>datas;
    private TextView tv_desc;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        bindService();
    

    private void initView() 
        etNum1= (EditText) findViewById(R.id.et_num1);
        etNum2= (EditText) findViewById(R.id.et_num2);
        etResult= (EditText) findViewById(R.id.et_result);
        btnCalculate= (Button) findViewById(R.id.btn_calculate);
        tv_desc= (TextView) findViewById(R.id.tv_desc);
        btnCalculate.setOnClickListener(this);
    

    private void bindService() 
        Intent intent=new Intent();
        intent.setComponent(new ComponentName("com.losileeya.aidlmaster","com.losileeya.aidlmaster.IRemoteService"));
        bindService(intent,conn, Context.BIND_AUTO_CREATE);

    


    private ServiceConnection conn=new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            iRemoteAidl=   IRemoteAidl.Stub.asInterface(service);
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            iRemoteAidl=null;
        
    ;
    @Override
    protected void onDestroy() 
        super.onDestroy();
        unbindService(conn);
    

    @Override
    public void onClick(View v) 
        int num1=Integer.parseInt(etNum1.getText().toString().trim());
        int num2=Integer.parseInt(etNum2.getText().toString().trim());

        try 
            int result=iRemoteAidl.add(num1,num2);
            etResult.setText(result+"");
            datas= (ArrayList<Person>) iRemoteAidl.addPerson(new Person("小a",21));
           Iterator i=datas.iterator();
           while (i.hasNext())
               tv_desc.setText(i.next().toString());
           

         catch (RemoteException e) 
            e.printStackTrace();
            etResult.setText("失败");
        
    

效果如下:

demo传送门:AIDLMaster

以上是关于安卓IPC之aidl使用--aidl常见使用的主要内容,如果未能解决你的问题,请参考以下文章

安卓IPC之aidl使用---System aidl调用

安卓IPC之aidl使用---aidl本地实现

安卓IPC跨进程通讯:AIDL+Retrofit——AndLinker的初步使用

IPC之 AIDL 的使用

Android IPC 之 AIDL 使用

Android IPC 之 AIDL 使用