我们知道在Android系统中我们常常需要在两个APP之间进行通信,但是由于在Android系统中两个不同的应用程序是不能共享内存的,因此我们跨进程通讯时就会稍显麻烦。在Android系统提供了多种不同的跨进程通讯的方式,接下来我们简单的介绍一下这些跨进程通信的各种方式,然后重点介绍我们今天想要说的重点内容-AIDL。
进程间通信方式其实我们可以看成基于四大组件的方式,Activity,Service,ContentProvider及Broadcast。
(1) Bundle/Intent传递数据
这种方式相信大家都不会陌生,我们将数据存储于bundle或者Intent中,然后我们可以通过startActivity(Intent)去启动另一个Activity,这个Activity可以是其他应用程序的,通过这种方式我们可以实现跨进程通信,但是需要注意的是这种方式传递的数据必须是基本类型或者实现了Serializable或Parcellable接口的数据结构,并且这种传递方式对数据大小有相应的要求,数据量不能过大。
(2) ContentProider传递数据
这种方式相信大家都不会陌生,这种方式是由应用自己暴露出相应的数据,提供增删改查方法来供其他应用程序调用,然后其他应用程序就能通过这些暴露出来的接口进行数据的访问了。
(3) Broadcast传递数据
这种方式也是四大组件中的一种相信大家对它也十分熟悉了,我们就不多说了,就是使用广播将相应的数据发送给其他应用进程,如果其他应用进程感兴趣的话就可以接收这个广播,得到数据进行相应的处理了,但是这种方式较为被动,必须等到其他应用程序发送广播才可以,无法主动去获取数据,
(4) Service传递数据
对于Service来说,其实Messenger及AIDL均是基于Service来进行跨进程通信的,Messenger与AIDL的区别在于Messenger对于消息的处理是顺序执行的,需要排队,对于多客户端的情况支持不够,而AIDL可以并发,对多客户端的形式有较好的支持。
接下来我们就来详细介绍AIDL的进程间通信方式。
AIDL(Android Interface Definition Language)是一种接口定义语言,编译器通过*.aidl文件的描述信息生成符合通信协议的Java代码,我们无需自己去写这段繁杂的代码,只需要在需要的时候调用即可,通过这种方式我们就可以完成进程间的通信工作。
接下来我们通过相应的例子来进行相应的描述。首先我们想象以下场景,我们通信的两端,服务器端是一个动漫的资料库,其中存储着相应的动画人物,并且提供了一个查询接口,而客户端呢就是无数喜爱动漫的动漫爱好者了,首先我们来个简单的,客户端输入一个序号,服务器端就将其存储的对应于这个序号的动漫人物返回给客户端,我们需要以下几步来完成这个工作。
(1)创建服务器端,定义一个aidl文件,如下图所示:
首先创建服务器端,然后新建一个aidl文件,在Android Studio中直接new一个aidl文件即可。aidl文件十分简单,代码如下:
// ICartoonCharacters.aidl
package com.luckybear.aidlservice;
// Declare any non-default types here with import statements
interface ICartoonCharacters {
String queryCartoon(int num);
}
aidl文件需要注意的如下所示:
1. 接口名字需要与aidl文件名相同
2. 接口和方法前面不要加访问权限修饰符:public ,private,protected等,也不能用static final!
3. AIDL默认支持的类型包括Java基本类型,String,List,Map,CharSequence,除此之外的其他类型都需要import声明,对于使用自定义类型作为参数或者返回值,自定义类型需要实现Parcelable接口
定义完aidl之后先build一下,之后会在build目录下的aidl文件夹自动生成相应的ICartoonCharacters.java文件。
(2) 定义Service
创建我们的CartoonService,代码如下:
package com.luckybear.aidlservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
/**
* Created by luckyBear on 2017/6/4.
*/
public class CartoonService extends Service {
public IBinder myBinder = new CartoonBinder();
public String[] cartoonCharacters = {"Shuke", "Beita", "Tom", "Jerry"};
public String query(int num) {
return cartoonCharacters[num % cartoonCharacters.length];
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
private class CartoonBinder extends ICartoonCharacters.Stub {
@Override
public String queryCartoon(int num) throws RemoteException {
return query(num);
}
}
}
十分简单的代码,实现aidl中的接口并将IBinder通过onBind()返回,最后别忘了在AndroidManifest.xml中声明这个Service。代码如下:
<service android:name=".CartoonService">
<intent-filter>
<action android:name="android.intent.action.Cartoon" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
service>
至此我们的服务器端已经完成,接下来我们定义我们的客户端
(3) 创建客户端,拷贝aidl
我们需要创建我们的客户端并将aidl文件夹整体拷贝,注意,包名也要一样。如下图所示:
(4) 建立连接,调用接口进行查询。具体代码如下:
package com.luckybear.aidlclient;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
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.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.luckybear.aidlservice.ICartoonCharacters;
public class MainActivity extends AppCompatActivity {
ICartoonCharacters mCartoonCharacters;
CartoonConnection mCartoonConnection;
EditText mInputNumText;
Button mSearchButton;
TextView mResultNameText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mInputNumText = (EditText) findViewById(R.id.et_input_num);
mResultNameText = (TextView) findViewById(R.id.tv_result_name);
mSearchButton = (Button) findViewById(R.id.bt_search);
mSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int num = Integer.valueOf(mInputNumText.getText().toString());
try {
mResultNameText.setText("人物是:" + mCartoonCharacters.queryCartoon(num));
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
mCartoonConnection = new CartoonConnection();
Intent service = new Intent();
service.setAction("android.intent.action.Cartoon");
service.setPackage("com.luckybear.aidlservice");
bindService(service, mCartoonConnection, BIND_AUTO_CREATE);
}
private class CartoonConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mCartoonCharacters = ICartoonCharacters.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mCartoonCharacters = null;
}
}
}
代码也较为简单,省去所有的布局及按钮等相关的,我们做的就是定义一个CartoonConnection去实现ServiceConnection,在连接建立的时候得到binder,然后我们就能调用在服务器端定义的queryCartoon(int num)方法了,这样一来我们就能在客户端访问服务器端的相应内容,实现跨进程通信了。
实现效果如下图:
我们上面介绍的内容都是相当基础的,我们传递的数据都是非常非常简单的基本数据类型,那么对于复杂的数据类型我们要怎么处理呢,接下来我们就针对复杂数据的传递进行相应的讲解。
我们对上面的场景进行一些微调,假设我们的动漫迷希望的输入动漫人物的名字我们就能给出这是哪部动漫及这个动漫的首映年份,接下来我们来实现这个功能,我们再上一个的基础上稍作修改。
(1) 在服务器端新增类Cartoon.java,代码如下:
package com.luckybear.aidlservice;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by luckyBear on 2017/6/4.
*/
public class Cartoon implements Parcelable {
private String CartoonName;
private String produceYear;
public Cartoon(String name, String year) {
CartoonName = name;
produceYear = year;
}
public String display() {
return "动画是:" + CartoonName + " 首映年份是:" + produceYear;
}
protected Cartoon(Parcel in) {
CartoonName = in.readString();
produceYear = in.readString();
}
public static final Creator CREATOR = new Creator() {
@Override
public Cartoon createFromParcel(Parcel in) {
return new Cartoon(in);
}
@Override
public Cartoon[] newArray(int size) {
return new Cartoon[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(CartoonName);
dest.writeString(produceYear);
}
}
(2) 定义Cartoon.aidl,代码如下:
// Cartoon.aidl
package com.luckybear.aidlservice;
// Declare any non-default types here with import statements
parcelable Cartoon;
(3) 修改一下我们的ICartoonCharacters.aidl,如下定义:
// ICartoonCharacters.aidl
package com.luckybear.aidlservice;
// Declare any non-default types here with import statements
import com.luckybear.aidlservice.Cartoon;
interface ICartoonCharacters {
Cartoon queryCartoon(String name);
}
我们返回一个Cartoon数据结构。
(4) 修改CartoonService.java代码如下:
package com.luckybear.aidlservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Created by luckyBear on 2017/6/4.
*/
public class CartoonService extends Service {
public IBinder myBinder = new CartoonBinder();
public static Map cartoon = new HashMap();
static {
cartoon.put("Shuke", new Cartoon("舒克和贝塔", "1989"));
cartoon.put("Beita", new Cartoon("舒克和贝塔", "1989"));
cartoon.put("Tom", new Cartoon("猫和老鼠","1939"));
cartoon.put("Jerry", new Cartoon("猫和老鼠","1939"));
}
public Cartoon query(String name) {
return cartoon.get(name);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
private class CartoonBinder extends ICartoonCharacters.Stub {
@Override
public Cartoon queryCartoon(String name) throws RemoteException {
return query(name);
}
}
}
注意:我们需要尽量将Cartoon.java及Cartoon.aidl均放置于aidl目录下,这样方便互相访问及拷贝,但是我们需要在build.gradle中明确指定SourceSet,将aidl目录也包含进去,否则Cartoon.java类会无法找到。如下设置:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
(5) 将服务器端的aidl目录整个拷贝至客户端,仅需修改客户端的函数调用即可
mSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = mInputNumText.getText().toString();
try {
mResultNameText.setText(mCartoonCharacters.queryCartoon(name).display());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
好了,以上就是我们这次进程间通信介绍的全部内容,希望大家读有所获。