您的位置:首页 > 移动开发 > Android开发

Android进程间通讯AIDL使用及问题详解(雷惊风)

2017-01-10 21:48 363 查看
   之前对AIDL用的不是很多,今天抽时间对其做一下详细的了解,下面本人主要从以下几个方面对AIDL做一下总结:

1.什么是AIDL?

2.为什么Android中要有AIDL?

3.什么时候使用AIDL?

4.具体怎么实现AIDL?

下面我们就进入今天的分析,分析代码Demo会在最后附上下载地址。

 

一.那么首先什么是AIDL呢?

AIDL全称为Android Interface definition language,顾名思义它是一种Android内部进程通信接口的描述语言,他妈怎么这么绕啊,简单的说它就是Android进程(现在可以先知道每一个App就是一个单独的进程【一个App也可以定义不同的进程】)间通信的桥梁,通过它我们可以定义进程间的通信接口(通过它我们可以在进程间进行相互操作)。

 

二.为什么Android中要有AIDL呢?

因为Android中进程与进程之间是不能相互访问的,每一个进程只能访问自己进程内的数据及操作,每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行自己的操作,相互之间不能通信。

 

三.什么时候使用AIDL?

Android官方文档介绍AIDL中有这么一句话:【Note: Using AIDL is necessary onlyif you
allow clients from different applications to access your servicefor IPC and want to handle multithreading in your service. If youdo not
need to perform concurrent IPC across different applications, you should create yourinterface by implementing a Binder or,if you
want to perform IPC, butdo not need to handle multithreading, implement yourinterface using
a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.】只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。

 

四.怎么实现AIDL?

下面AIDL我们分成两步:

1.Server端代码编写,及为我们提供服务的一端,比如我们有好几个App,有一个App中有一个超牛逼的算法,我们另外几个App中也要用这个算法,这个Server端就是提供算法的那个App。

2.编写Client端代码,Client端为其他几个App。

 

1.下面我们看一下第一步:server端代码编写。

我们以客户端将两个int值传递给服务端,服务端进行相加后返回为例:

AS中新建一个项目,创建过程跟普通项目没有区别。然后创建我们的*.aidl文件,名称可以自己定,我这里就叫IMyAidlInterface.aidl,在我们新建项目的app文件路径上右键new->AIDL->AIDL
File,如下图:



创建成功的话会在项目中新增如下目录结构:



可以看见与java同级出现了一个aidl目录,其下边的包路径与java包路径相同,再看我们新创建的.aidl文件内容,默认会有一个void
basicTypes()方法,其中参数为aidl支持的几种传参基本数据类型(Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),如果要传递自定义类型,首先要让自定义类型支持parcelable协议),然后我们可以删掉默认方法,添加我们自己的方法,如下:



然后Build->rebuild project,在之前eclipse中会自动生成.java文件,AS不行,我们必须重新编译一下项目,然后会在如下文件目录结构中出现我们梦寐以求的.java文件。



其代码如下:

/*
* This file is auto-generated.  DO NOT MODIFY.
* Original file: D:\\lylsoft\\android\\androidstudio\\mydemo\\AidlClient\\app\\src\\main\\aidl\\com\\jason\\aidl\\aidldemo\\IMyAidlInterface.aidl
*/
package com.jason.aidl.aidldemo;
// Declare any non-default types here with import statements
public interface IMyAidlInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.jason.aidl.aidldemo.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.jason.aidl.aidldemo.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.jason.aidl.aidldemo.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.jason.aidl.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jason.aidl.aidldemo.IMyAidlInterface))) {
return ((com.jason.aidl.aidldemo.IMyAidlInterface)iin);
}
return new com.jason.aidl.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jason.aidl.aidldemo.IMyAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int add(int a, int b) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}

可以看到,在我们编译的出的.java文件中有一个Stub内部类,我们在MainActivity同目录下创建一个Service,创建一个Stub类实现add()方法后在onBind()方法中返回,代码如下:

package com.jason.aidl.aidldemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.jason.aidl.aidldemo.Person;

public class MyAidlService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Log_LYL", "Onstart");
return super.onStartCommand(intent, flags, startId);

}
@Override
public IBinder onBind(Intent intent) {
return stub;
}
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
};
}

到这里我们的Server端代码就完成了?没有,我们要知道,我们其他项目中没有这个Service,所以我们要想在其他App中打开这个service必须通过隐式意图,所以我们必须在我们的Manifest.xml中添加一个action,如下:
<service
android:name=".MyAidlService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.lyl.aidl"/>
</intent-filter>
</service>

到这里服务端工作算是基本完成了。

2.下面看第二步编写Client端代码。

新建一个Client端项目,我们将服务端整个aidl下的文件统统考本到Client端与java同级目录下,如下图:



编译项目后会在Client端也生成一个.java文件,且与Server端生成的.java文件一模一样,然后我们就可以在我们的activity_main.xml中布局,然后在MainAcitivity中进行绑定service进行远程调用运算了,xml代码与activity代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="客户端"
android:textColor="@android:color/holo_red_dark"
android:textSize="15sp" />

<EditText
android:id="@+id/et_num1"
android:layout_width="200dp"
android:layout_height="wrap_content" />

<EditText
android:id="@+id/et_num2"
android:layout_width="200dp"
android:layout_height="wrap_content" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="add"
android:text="加运算" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="结果:" />

<TextView
android:id="@+id/tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2" />
</LinearLayout>
</LinearLayout>

package com.jason.aidl.client.aidlclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.jason.aidl.aidldemo.IMyAidlInterface;

import java.util.List;

public class MainActivity extends AppCompatActivity {
private EditText et_num1, et_num2;
private TextView tv;
private IMyAidlInterface mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_num1 = (EditText) findViewById(R.id.et_num1);
et_num2 = (EditText) findViewById(R.id.et_num2);
tv = (TextView) findViewById(R.id.tv);
Intent intent = new Intent();
intent.setAction("com.lyl.aidl");

Intent intent1 = new Intent(createExplicitFromImplicitIntent(this, intent));
bindService(intent1, mServiceC, Context.BIND_AUTO_CREATE);
}

public void add(View v) {
int a = Integer.valueOf(et_num1.getText().toString());
int b = Integer.valueOf(et_num2.getText().toString());
try {
int res = mService.add(a, b);
tv.setText(res+"");

} catch (RemoteException e) {
e.printStackTrace();
}
}

ServiceConnection mServiceC = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMyAidlInterface.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

/**
* 兼容Android5.0中service的intent一定要显性声明
*
* @param context
* @param implicitIntent
* @return
*/
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
//通过queryIntentActivities()方法,查询Android系统的所有具备ACTION_MAIN和CATEGORY_LAUNCHER
//的Intent的应用程序,点击后,能启动该应用,说白了就是做一个类似Home程序的简易Launcher 。
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}

// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);

// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);

// Set the component to be explicit
explicitIntent.setComponent(component);

return explicitIntent;
}
}

这里用到了createExplicitFromImplicitIntent()方法是因为Android5.0中service的intent一定要显性声明,否则会报如下错误:

01-10 19:17:43.733 14662-14662/com.jason.aidl.client.aidlclient E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jason.aidl.client.aidlclient, PID: 14662
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.jason.aidl.client.aidlclient/com.jason.aidl.client.aidlclient.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.lyl.aidl }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2381)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2443)
at android.app.ActivityThread.access$800(ActivityThread.java:157)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5344)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703)
Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.lyl.aidl }
at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1781)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1880)
at android.app.ContextImpl.bindService(ContextImpl.java:1858)
at android.content.ContextWrapper.bindService(ContextWrapper.java:539)
at com.jason.aidl.client.aidlclient.MainActivity.onCreate(MainActivity.java:37)
at android.app.Activity.performCreate(Activity.java:6033)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2334)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2443) 
at android.app.ActivityThread.access$800(ActivityThread.java:157) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
at android.os.Handler.dispatchMessage(Handler.java:102) 
at android.os.Looper.loop(Looper.java:135) 
at android.app.ActivityThread.main(ActivityThread.java:5344) 
at java.lang.reflect.Method.invoke(Native Method) 
at java.lang.reflect.Method.invoke(Method.java:372) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703) 

运行我们的Server端及Client端,在Client端的两个editText中输入两个数字点击按钮运行结果如下:



看见运行结果与我们预期一样,可见我们通过AIDL实现了App与App之间的远程调用。

 

以上只是一个简单的基本数据类型操作,那么我们怎么通过AIDL进行对象的操作呢,我们在之前代码上做些修改就行了,看下边吧。

我们在Server端与我们创建的.aidl同目录下创建一个Person.java的类与Person.aidl文件,目录结构如下



Person.java中成员变量包括一个name,一个age,ctrl+insert添加set与get方法,然后实现Parcelable接口序列化,里边有些方法需要自己实现Creator啊writeToParcel啊什么的,自己弄一下就行,两次ctrl+enter就行,然后自己写一个readFromParcel(Parcel
dest)方法,要保证赋值顺序与writeToParcel中一致,否则会出问题。具体可以看下边代码:
package com.jason.aidl.aidldemo;
import android.os.Parcel;
import android.os.Parcelable;
public class Person implements Parcelable {
private String name;
private int age;
public Person() {
}
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public void readFromParcel(Parcel dest) {
name = dest.readString();
age = dest.readInt();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

Person.java完成了,看一下Person.aidl文件代码,很简单,如下:
// Person.aidl.aidl
package com.jason.aidl.aidldemo;
// Declare any non-default types here with import statements
parcelable Person;

就两行代码一个引入包,一个parcelable Person;这里注意前边的那个单词首字母是消协的,对,没错,相信自己的眼睛。

然后修改我们的IMyAidlInterface.aidl代码如下:
// IMyAidlInterface.aidl
package com.jason.aidl.aidldemo;

// Declare any non-default types here with import statements
import com.jason.aidl.aidldemo.Person;
interface IMyAidlInterface {
//处理基本类型;
int add(int a,int b);
//处理对象;
String inPerson(in Person p);
String outPerson(out Person p);
String inOutPerson(inout Person p);
}

上面的代码中你需要手动引入Person类,因为在这里系统不会帮你引入,切记切记。还有你还会发现在方法的参数类型前有in、out、inout几个东西,不懂不要后边我们再分析。
Rebuild我们的项目你会发现报如下错误:



擦,为什么呢,因为AS是gradle构建项目的,如果不配置,它默认不会从aidl文件夹下寻找资源,所以我们需要在我们app下的build.gradle中添加如下配置:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}

之后在次rebuild就没问题了,运行后你会发现我们生成的.java文件中多了上边的几个刚刚操作对象的方法,代码就不粘了,太多了,都是系统生成的,本篇就不分析.java中的代码了,然后修改Service中的代码如下:
package com.jason.aidl.aidldemo;

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

import com.jason.aidl.aidldemo.Person;

public class MyAidlService extends Service {

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Log_LYL", "Onstart");
return super.onStartCommand(intent, flags, startId);

}

@Override
public IBinder onBind(Intent intent) {
return stub;
}

IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}

@Override
public String inPerson(Person p) throws RemoteException {
String old = "name:" + p.getName() + " age:" + p.getAge();
Log.d("Log_LYL:inPerson_", old);
p.setName("李四");
p.setAge(13);
return "name:" + p.getName() + " age:" + p.getAge();
}

@Override
public String outPerson(Person p) throws RemoteException {
String old = "name:" + p.getName() + " age:" + p.getAge();
Log.d("Log_LYL:outPerson_", old);
p.setName("周六");
p.setAge(20);
return "name:" + p.getName() + " age:" + p.getAge();
}

@Override
public String inOutPerson(Person p) throws RemoteException {
String old = "name:" + p.getName() + " age:" + p.getAge();
Log.d("Log_LYL:inOutPerson_", old);
p.setName("弓七");
p.setAge(57);
return "name:" + p.getName() + " age:" + p.getAge();
}
};
}

到这里Server端的代码就算是修改完成了,然后把我们添加了Person.java与Person.aidl文件的整个文件夹copy覆盖掉我们Client端之前拷贝的包重新编译,rebuild项目,报错... 没改app下的build.gradle吧,改。然后修改我们Client端的MainActivity中的textView赋值的try
catch代码块,如下:

try {
int res = mService.add(a, b);
Person p1 = new Person();
p1.setName("刘大");
p1.setAge(3);
Person p2 = new Person();
p2.setName("赵二");
p2.setAge(3);
Person p3 = new Person();
p3.setName("张三");
p3.setAge(3);

tv.setText(res + "\n" + mService.inPerson(p1) + "\n" + mService.outPerson(p2) + "\n" + mService.inOutPerson(p3)+"\n" + p1.toString() + "\n" + p2.toString() + "\n" + p3.toString());

} catch (RemoteException e) {
e.printStackTrace();
}

然后运行Server端与Client端结果如下:



再看我们Server端服务中的打印信息:



我们通过Client端的显示与Server端的打印信息分析一下上边定义的时候的那个in、out、inout的作用,再看一下我们在.aidl中定义的三个方法:String inPerson(inPerson
p);String outPerson(outPerson p);String inOutPerson(inoutPerson
p);
先看第一个inPerson中定义的为in,从Client端我们在textView赋值中调用inPerson传入的Person值name为“刘大”,在服务端打印中可知我们在服务端顺利接收到了,然后我们将Person的name值改为“李四”,但是我们Client端的Person本身没有改变(通过UI中Person{name=’刘大’,age=3}可知)。
Client端调用outPerson()传入的p2对象在服务端没有接收到(通过01-10 20:24:11.038 6451-6478/com.jason.aidl.aidldemo D/Log_LYL:outPerson_: name:null
age:0打印信息可知),但是服务端修改了p2我们在Client端收到了修改后的p2(通过Person{name=’周六’,age=20}可知)。
Client端调用inoutPerson()传入p3对象在服务端可以接收到修改后在Client端也能发现P3被修改了。
由以上三种情况可以知道in的作用是Client端给Server端传递数据,Server段修改修改数据对客户端没有影响;而参数为out时,Client端给服务端传的值服务端是收不到的,服务端可以修改Client端传递过去的值,会影响到Client端队形;最后inout就是二者的结合了,Client端传递的值Server端能够收到,Server端修改了数据,会影响Client端对象数据,他们是双向操作的。
 
到此关于AIDl前边的几个问题就都回答完了,总结一下我在编写过程中遇到的几个问题:
1.Client端在绑定Server端使用隐式方式时,要兼容Android5.0以上版本。
2.传递对象实现Parcelable接口时readFromParcel(Parcel
dest)中获取变量值顺序要与writeFromParcel(Parcel dest)一致。
3.传递对象时,记得配置buidle.grable,两端都要配置。
好了,AIDL基础篇就到这里吧,希望对你有帮助,谢谢!最后附上代码下载地址: http://download.csdn.net/detail/liuyonglei1314/9734165
Aidl更深入了解可以看我的另一篇博文:
http://blog.csdn.net/liuyonglei1314/article/details/54849197
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: