Android AIDL进程通信机制详解

AIDL简介

什么是AIDL?

AIDL:Android Interface Definition Language。Android接口定义语言,是Android为方便进程间通信而设计的一门语言。

为什么要设计AIDL?

Androi进程间通信除却AIDL还有多种方式,如:
1.Bundle/Intent传递数据
2.ContentProvider
3.文件/数据库
4.Socket
5.BroadcastReceiver
6.Binder

如果对Android进程比较了解的人肯定会质疑说我遗漏了Messenger,是的但是我想说Messenger就是基于AIDL实现的。

7.Messenger

那疑问又来了,既然已经有了这么多通信方式为什么还要设计AIDL?或者说既然已经有了Messenger为什么还要使用AIDL呢?

AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理。
而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。

AIDL有哪些语法?
  1. 以.aidl为文件后缀而不是.java。
  2. 基础数据类型都可以适用,适用定义的parcelable类型数据,List Map等有限适用。static field 不适用。
  3. AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。
  4. 引用的对象必须使用完整的包名,即使在同一包内。

AIDL使用

接下来以代码为例,详细讲解AIDL.

建立服务端service

Android Studio下新建一个项目(Moudle或者Proiect),本文新建的是Moudle。
鼠标点击项目名—->new—->AIDL—->AIDL file.会自动在src下生成aidl文件夹及对应的包。
项目结构如下:
这里写图片描述

1.新建对象.aidl

如果AIDL中传递的是基本的数据类型,则不需要这个步骤。本文为了演示效果,传递JavaBean对象。

// Book.aidl
package com.demon.aidlservice;

//一句代码,声明Book为parcelable类型
parcelable Book;
2.新建JavaBean

定义基本数据变量,完成构造方法和setter/getter。
然后implements Parcelable,Android studio 直接alt+enter就可自动生成所需代码。
最后手动实现readFromParcel(Parcel dest),如代码所示即可。

public class Book implements Parcelable {
    private int id;
    private String name;

    public Book() {
    }

    public Book(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected Book(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

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

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(id);
        parcel.writeString(name);
    }

    /**
     * 参数是一个Parcel,用它来存储与传输数据
     * 必须实现用于自动生成BookManger中调用
     * @param dest
     */
    public void readFromParcel(Parcel dest) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        id = dest.readInt();
        name = dest.readString();
    }
}
3.新建BookManager.aidl

用于管理传递的数据。

package com.demon.aidlservice;

// 必须是完成的包名
import com.demon.aidlservice.Book;
interface BookManger {

    List<Book> getBooks();
    //in out inout可以指定数据的传递方向
    void addBook(inout Book book);
}
4.修改gradle文件

由于Android默认.java文件只能在java文件目录下编译,为了是Book.java在aidl文件编译通过必须在gradle中添加如下代码。

android {
    .....
sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
    .....
}

PS:必须先编译通过,才能继续编写服务端代码
编译的过程中会自动在generated—source—aidl—debug—-包名文件夹下生成BookManger.java。这样服务端才能调用BookManger对象。
此时若编译通过,程序能运行起来。就说明没有问题,接下来编写服务端代码即可。

5.编写Service代码

核心是重写BookManger.Stub方法用于建立一个Binder对象,用于传递数据。可见AIDL的实现部分也使用了Binder机制。然后再重写数据处理方法,对需要传递的数据进行处理就行了。其余代码比较简单,就不介绍了。

public class MyService extends Service {
    private static final String TAG = "MyService";
    List<Book> bookList = new ArrayList<>();

    private BookManger.Stub mBinder = new BookManger.Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            return bookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            book.setId(100);
            bookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
        Book book = new Book(1, "Java");
        bookList.add(book);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return mBinder;
    }
}
6.修改AndroidManifest.xml,注册Service
.....
  <!--指定可以被外部程序访问的两个“true”-->
  <service
            android:name=".MyService"
            android:enabled="true" 
            android:exported="true">
            <intent-filter>
                  <!--指定隐式调用的name-->
                <action android:name="com.demon.aidlservice.MyService"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
 ......

至于MainActivity,因为我们把这个程序当做一个服务端程序,所以MainActivity什么内容都可以不需要。编译运行,如果没问题,接下来编写客户端程序即可。

编写客户端Client

客户端程序项目目录结构如下:
这里写图片描述

1.复制AIDL文件

直接将服务端的AIDL相关的整个文件夹复制过来即可,注意AIDL所在的包名必须与服务端的相同,切勿修改。
注意: java.lang.SecurityException: Binder invocation to an incorrect interface
如果遇到如上异常,属于进程间通信异常,很可能是因为服务端与客户端的AIDL包名不一致所导致。

2.修改gradle文件

为了使.java在AIDL文件夹中通过编译,需要修改项目gradle文件,如同上面第4步即可。

然后编译运行,如果没问题,直接继续。

3.编写Activity事务

根据需求直接编写Activity的事务,本文仅为了演示,故代码很简单,如下:


public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Button aidl;
    private BookManger manger;//获取AIDL对象
    private List<Book> bookList = new ArrayList<>();
    private boolean flag;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: ");
        setContentView(R.layout.activity_main);
        aidl = (Button) findViewById(R.id.aidl);
        aidl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!flag) {
                    BindService();
                }
                for (int i = 0; i < bookList.size(); i++) {
                    Book book = bookList.get(i);
                    Log.i(TAG, "onClick: " + book.getId() + "," + book.getName());
                }
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i(TAG, "onStart: ");
        BindService();
    }

    /**
     * 连接服务端
     */
    private void BindService() {
        Intent intent = new Intent();
        intent.setAction("com.demon.aidlservice.MyService");
        //Android5.0后隐式调用,必须调用此方法,service所在的包名
        intent.setPackage("com.demon.aidlservice");
        bindService(intent, connection, BIND_AUTO_CREATE);
    }


    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected: 服务器已连接!");
            manger = BookManger.Stub.asInterface(iBinder);
            Book book = new Book(2, "Java");
            try {
                manger.addBook(book);
                bookList = manger.getBooks();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            flag = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected: 服务器连接断开!");
            flag = false;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (flag) {
            unbindService(connection);
            flag = false;
        }
    }
}

其实代码与正常的service没有太大的区别,主要是在AIDL提供的接口上进行操作。

4.测试结果截图

这里写图片描述
结果符合预期。

结语

AIDL是一门跨进程Client——Service间通信机制语言,没有熟练掌握前还是比较抽象的。
最好还是自己能写一个demo走一下流程,就会很清晰。

代码下载:

CSDN下载:http://download.csdn.net/detail/demonliuhui/9923204
不想下载,只看代码的点这里,GitHub:
AIDLClient:https://github.com/DeMonLiu623/DeMonTest/tree/master/AIDLClient
AIDLService:https://github.com/DeMonLiu623/DeMonTest/tree/master/AIDLService

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值