每个应用程序都运行在自己的独立进程中,并且可以启动另一个应用进程的服务,而且经常需要在不同的进程间传递数据对象。
在Android平台,一个进程不能直接访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。
AIDL (Android Interface Definition Language) 用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。
如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
简单的例子:客户端向服务端发送一个id,服务端根据id找到对应的信息,打包数据发送给客户端,客户端解包得到信息。
一、服务端(service)
1、新建工程service作为服务端,并且创建一个包名为:com.example.remote.service的包。
2、在这个包名下右键创建一个AIDL文件夹。
在aidl文件夹下创建 Student.aidl
注意parcelable 是小写!
package com.example.remote.service;
parcelable Student;
在com.example.remote.service这个包下创建一个自定义类型Student,实现 Parcelable 接口。这个类必须和aidl文件是同一个包名!
package com.example.remote.service;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
* Created by Administrator on 2019/3/17.
*/
public class Student implements Parcelable{
int id;
String name;
double price;
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;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Student(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Log.w("www","打包");
dest.writeInt(id);
dest.writeString(name);
dest.writeDouble(price);
}
public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>(){
@Override
public Student createFromParcel(Parcel source) {
Log.w("www","解包");
int id = source.readInt();
String name = source.readString();
double price = source.readDouble();
return new Student(id,name,price);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
}
在aidl文件夹下面创建 IStudentService,aidl,定义AIDL接口。
basicTypes()这个方法是默认方法,可以删除。
注意:导入import com.example.remote.service.Student;即使它们在同一个包下面!
// IStudentService.aidl
package com.example.remote.service;
import com.example.remote.service.Student;
// Declare any non-default types here with import statements
interface IStudentService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
Student getStudentById(int id);
}
3、在进程间通信中真正起作用的并不是 AIDL 文件,而是系统据此而生成的文件,可以在以下目录中查看系统生成的文件。之后需要使用到当中的内部静态抽象类 Stub。
运行一下项目,便可得到IStudentService。
4、创建MyRemoteService 继承 Service,供客户端远程绑定。
package com.example.remote.service;
import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* Created by Administrator on 2019/3/17.
*/
public class MyRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.w("www","onBind()");
return new StudentService();
}
@Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
Log.w("www","unbindService()");
}
class StudentService extends IStudentService.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public Student getStudentById(int id) throws RemoteException {
Log.w("www","getStudentById()");
return new Student(id,"Tom",10000);
}
}
}
5、AndroidManifest.xml创建。
服务端的Service需要被客户端来远程绑定,所以客户端要能够找到这个Service,可以通过先指定包名,之后再配置Action值或者直接指定Service类名的方式来绑定Service。
<service android:name="com.example.remote.service.MyRemoteService"
android:exported="true"
android:enabled="true"
>
<intent-filter>
<action android:name="com.example.remote.service.MyRemoteService.action"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
二、客户端(client)
1、新建工程client作为客户端。
2、把服务端的AIDL文件以及Student类复制过来。
注意包名要一致!
3、添加布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.serviceclient.MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="bind service"/>
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button2"
android:text="获得远程服务"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button3"
android:text="unbind service"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
4、MainActivity编写代码。
在这之前也要运行一下项目,便可得到IStudentService。
package com.example.serviceclient;
import android.app.Activity;
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.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.example.remote.service.IStudentService;
import com.example.remote.service.Student;
import java.util.List;
public class MainActivity extends Activity implements View.OnClickListener{
private static final String BIND_SERVICE_ACTION = "com.example.remote.service.MyRemoteService.action";
private Button button1;
private Button button2;
private Button button3;
private EditText et;
private ServiceConnection conn = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
button2 = (Button)findViewById(R.id.button2);
button3 = (Button)findViewById(R.id.button3);
et = (EditText)findViewById(R.id.et);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
button3.setOnClickListener(this);
}
private IStudentService iStudentService =null;
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button1:
if(conn == null){
Intent serviceIntent = new Intent();
serviceIntent.setAction(BIND_SERVICE_ACTION);
serviceIntent.setComponent(new ComponentName("com.example.server", "com.example.remote.service.MyRemoteService"));
final Intent eintent = new Intent(achieveExplicitFromImplicitIntent(this, serviceIntent));
Log.w("www",""+eintent);
conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.w("www","onServiceConnected()");
iStudentService = IStudentService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
startService(serviceIntent);
bindService(serviceIntent, conn, Context.BIND_AUTO_CREATE);
Toast.makeText(this,"bind service",Toast.LENGTH_SHORT).show();
Log.w("www",""+conn);
}else
{
Toast.makeText(this,"is bind service",Toast.LENGTH_SHORT).show();
}
break;
case R.id.button2:
if(iStudentService != null){
int id = Integer.parseInt(et.getText().toString());
try {
Student student = iStudentService.getStudentById(id);
Toast.makeText(this,student.toString(),Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case R.id.button3:
if(conn != null){
unbindService(conn);
conn = null;
iStudentService =null;
Toast.makeText(this,"unbind service",Toast.LENGTH_SHORT).show();
Log.w("www",""+conn);
}else {
Toast.makeText(this,"is unbind service",Toast.LENGTH_SHORT).show();
}
break;
}
}
public Intent achieveExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
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;
Log.w("www","packageName = " + packageName);
Log.w("www","className = " + className);
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;
}
}
注意:
1、android 5.0之前是可以通过隐式意图打开其他app的服务的,5.0之后只能通过显式意图来打开。
Intent serviceIntent = new Intent();
serviceIntent.setAction(BIND_SERVICE_ACTION);
serviceIntent.setComponent(new ComponentName("com.example.server", "com.example.remote.service.MyRemoteService"));
2、Intent 除了必要的Action,还需要ComponentName,参数packageName:"AIDLServer->AndroidMainfest.xml->package",参数className:"AIDLServer->Service完整的包名+ServiceName"。调用achieveExplicitFromImplicitIntent方法,获取正确的Intent。
3、魅族手机上需要先开启提供service的app也就是说要先启动service app 同时service app的service也要启动,才能让client app用service app aidl服务才能收到消息。
需要添加以下代码:
startService(serviceIntent);
三、结果
点击第一个按钮绑定服务,输入id为3,点击第二个按钮获取信息,Toast出来。第三个按钮为解绑服务。
打印信息: