结合使用浅谈在Android中使用AIDL实现IPC

前言:欢迎转载,共同交流总结进步。如果转载请注明本文的原出处。

先解释一下什么是IPCIPCinter-process communication的简写,意思是进程间通信。简单地说就是在不同进程间传递数据或者调用对方暴露的接口方法。

我们都知道,在Android系统中,一个应用程序对应于Linux层的一个进程。不同的应用程序在不同的进程中运行。一个进程通常是不能访问另一个进程的内存空间的。那么,问题来了,我们如何实现IPC呢?当然有不止一种方法,不过本文我们讨论下面这一种,也算是主流的吧:使用AIDL来实现IPC(其实AIDL在底层走的是Binder通信机制,有兴趣的同学可以在底层深入研究下Binder)。

AIDL是安卓接口定义语言的英文简写。结合例子说吧:比如你要写一个Service服务,它提供几个数学算法函数,返回计算结果。你希望别的应用程序都可以调用你这个服务里的这几个方法。那这肯定是属于进程间通讯IPC啦。你想让别的进程跨进程调你,需要什么呢?

1.向别人暴露自己的接口方法。

2.一种在进程间传递数据和远程调用的机制。

AIDL帮你完成以上两点。你需要在你的服务里,写一个xxxx.aidl文件,里面放上你要暴露的所有方法声明;而xxxx.aidl编写完成后,ADT帮你自动生成的同名xxxx.java文件中,默默地使用的Binder机制来实现数据传递和远程调用。

方便理解的背景知识就写到这里吧。下面我写了一个小例子,结合例子代码,我来浅谈一下AIDL在何种情况使用,具体怎么用。

例子大致要实现这样一个功能:先写一个CalcService计算器服务程序,它将暴露加法运算和减法运算这2个方法供其他应用程序调用(注意哦,不是在服务自身的应用里调,是让别的应用调)。实现这个服务程序开机自启动(自己先启动起来别人才能调到嘛,手工启动神马的比较土);再写一个普通小应用程序,调用服务程序暴露的方法。界面如下:

相信界面您一看就明白了,比较丑,但是实用,可以说明进程间通信和调用。

类似这种,开发者想要提供比较公共,比较基础或者核心而私密的功能,供系统中别的程序随时调用的时候,都可以使用AIDL声明并暴露出这些接口,然后把具体实现写在服务里。而服务一直运行着,随时等待调用。

先上CalcService工程的代码结构:

 

       

各文件作用:

1.src/Calc.aidl声明要暴露的接口方法

2.src/CalcImpl,java负责接口方法的具体实现

3.src/CalcService.java将接口放入服务中,并暴露出来

4.ListenBoot.java负责实现服务开机自启动(当然这个非必须,你也可以再在本工程中弄个Activity或者Activity+按钮什么的手工启动服务)

 

5.gen/Calc.java当你写完src/Calc.aidlADT会自动生成。里面Stub类最为关键,它由系统自动生成,负责帮助我们利用Binder机制进行数据传递和远程调用,同时第2步具体的实现类CalcImpl要继承这个Stub类,第3步在服务中加入并暴露接口,也要用到它。(将来调用方程序那里也要放一份,因为调用方也要知道服务端是谁,暴露了哪些接口,客户端也是要通过Binder机制进行数据传递和发起调用等等的)

6.R.java工程中所有的资源索引,系统自动生成

 

7.其余布局,资源,manifest等文件不太重要,暂略,注意事项我在后面说明。

先写Calc.aidl来声明要暴露的接口,代码:

package com.blog.calc;
interface Calc
{
	float sum(float arg0, float arg1);
	float sub(float arg0, float arg1);
}

写完保存,ADT会自动帮我们在gen目录下生成同名的java文件Calc.java,自动生成的代码如下所示: 

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.blog.calc;
public interface Calc extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.blog.calc.Calc
{
private static final java.lang.String DESCRIPTOR = "com.blog.calc.Calc";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.blog.calc.Calc interface,
 * generating a proxy if needed.
 */
public static com.blog.calc.Calc asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.blog.calc.Calc))) {
return ((com.blog.calc.Calc)iin);
}
return new com.blog.calc.Calc.Stub.Proxy(obj);
}
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_sum:
{
data.enforceInterface(DESCRIPTOR);
float _arg0;
_arg0 = data.readFloat();
float _arg1;
_arg1 = data.readFloat();
float _result = this.sum(_arg0, _arg1);
reply.writeNoException();
reply.writeFloat(_result);
return true;
}
case TRANSACTION_sub:
{
data.enforceInterface(DESCRIPTOR);
float _arg0;
_arg0 = data.readFloat();
float _arg1;
_arg1 = data.readFloat();
float _result = this.sub(_arg0, _arg1);
reply.writeNoException();
reply.writeFloat(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.blog.calc.Calc
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public float sum(float arg0, float arg1) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
float _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeFloat(arg0);
_data.writeFloat(arg1);
mRemote.transact(Stub.TRANSACTION_sum, _data, _reply, 0);
_reply.readException();
_result = _reply.readFloat();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public float sub(float arg0, float arg1) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
float _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeFloat(arg0);
_data.writeFloat(arg1);
mRemote.transact(Stub.TRANSACTION_sub, _data, _reply, 0);
_reply.readException();
_result = _reply.readFloat();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_sub = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public float sum(float arg0, float arg1) throws android.os.RemoteException;
public float sub(float arg0, float arg1) throws android.os.RemoteException;
}

自动生成的Calc.java文件里的Stub类,就是系统自动生成帮助我们使用的Binder机制来实现数据传递和远程调用的。

至此,接口方法也声明了,通信机制系统也自动实现了。

那我们下一步该做些什么呢?很简单,不能说空话啊,既然声明了这俩接口方法,我们要实现它啊。(注意:实现类CalcImpl必须继承上面自动生成的Calc.Stub类,只有这样,实现类才能既有实现,又涵盖Binder通信机制),CalcImpl.java代码如下:

package com.blog.calc;

import android.os.RemoteException;

public class CalcImpl extends Calc.Stub{
	@Override
	public float sum(float arg0, float arg1) throws RemoteException {
		// TODO Auto-generated method stub
		float result;
		result = arg0+arg1;
		return result;
	}

	@Override
	public float sub(float arg0, float arg1) throws RemoteException {
		// TODO Auto-generated method stub
		float result;
		result = arg0-arg1;
		return result;
	}
}


 最后写一个服务CalcService,在服务里的onBind方法通过返回值暴露接口,这样,任何程序bind我们的CalcService服务之后,都可以远程调用其暴露的接口方法,传参,获取返回值等。

package com.blog.calc;

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

public class CalcService extends Service {
	private Calc.Stub ICalc = new CalcImpl();

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return ICalc;
	}
	
	public void onCreate()
	{
		Log.v("create", "我的Calc服务onCreate");
	}

	public void onStart(Intent intent, int startId)
	{
		Log.v("start", "我的Calc服务onStart");
	}
	
	public void onDestroy()
	{
		Log.v("destroy", "我的Calc服务onDestroy");
	}
}

 

服务里面包含一个Calc.Stub接口类型的私有成员ICalc(名字随便起,不过貌似这样规范点),我们用真正的实现类CalcImpl对象来实例化它。最后,我们在onBind方法中,把这个接口返回,将来任何一个客户端程序一bind我们的服务,就可以拿到此接口的一个实例,以达到远程调用暴露的接口方法,传参,获取返回值等。这就是暴露接口的意思和步骤,当然,客户端程序只是知道,接口有哪几个函数,参数列表,返回值等,具体实现它是不知道的,它只能绑定服务后,得到接口对象,调用。

最后,服务也写好了,再把服务搞成开机自启动的,方便!就是写个ListenBoot.java监听开机广播,一开机就通过intent启动我们的CalcService服务,代码如下:

package com.blog.calc;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class ListenBoot extends BroadcastReceiver{

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED"))
		{
			Intent CalcServiceAddr = new Intent();
			CalcServiceAddr.setAction("com.blog.calc.action.CALC_SERVICE_ADDR");
			context.startService(CalcServiceAddr);
		}
	}

}

 

这里要注意啦,要监听开机广播,manifest.xml文件里是要加权限的,下面我把AndroidManifest.xml也贴上来吧:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blog.calc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        
        <service 
            android:name=".CalcService" >
            <intent-filter >
                <action android:name="com.blog.calc.action.CALC_SERVICE_ADDR" />
            </intent-filter>
            
        </service>
        
        <receiver 
            android:name=".ListenBoot">
            <intent-filter >
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        
    </application>
    
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

</manifest>


OK,至此,我们的CalcService服务终于写完了,按我上面的各文件作用介绍,再回顾一下流程吧,连上模拟器,调试一下,能不能开机启动呢。RunAs AndroidApplication把它搞到模拟器上运行,显示装上了,重启模拟器,看能不能开机服务自启动?

我们看到,刚才写的服务已经开机自己启动了,也就是说,开机状态下,它随时等待着被别的应用程序调用。

下面我们当然就开始写一个小客户端应用程序来调用它啦,工程起名叫CallRemoteCalcService吧。

CallRemoteCalcService工程代码结构如下:

各文件作用:

1.src/com.blog.calc/Calc.java 这个文件是从CalcService工程里拷过来的,就是那边刚才自动生成的接口类,因为作为一个客户端应用程序,你总得先知道你想调的服务是谁,有哪些接口,你才可以从远程去调它。比如,你打电话让室友帮你去食堂买份饭,你得先知道一个室友的电话号码,还得知道食堂有哪些种类的饭菜。你才能远程调用他/她,他/她负责帮你实现买宫保鸡丁饭这个服务。(从Binder层次说,通信的客户端进程是需要知道接口和获得服务代理对象的,客户端进程也需要在通信中做一些事:知道接口,封装传递参数给Binder驱动,发起调用,捕获返回值。把接口类加入调用方就是让系统帮我们调用方进程做这些。但在客户端看来,似乎是他就像是调本地某个类中的方法一样方便。唯一差别是,他调的只是一个远程接口,具体实现它并不知道) 注意,该文件要放到和CalcService工程里此文件所在的包名相同的包下。

2.src/CallRemoteCalcServiceActivity.java 客户端小程序的Activity,界面我在最前面已经贴过啦,作用你一看就懂的。

3.其他文件作用不是特别大,暂略,有需要注意的地方我后面会说明。

下面是CallRemoteCalcServiceActivity.java的代码:

package com.blog.callcalc;

import com.blog.calc.Calc;

import android.app.Activity;
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.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class CallRemoteCalcServiceActivity extends Activity {
	private EditText arg0EditText, arg1EditText, resultEditText;
	private Button sumBtn, subBtn;
	private Calc mCalc;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        arg0EditText = (EditText) findViewById(R.id.arg0);
        arg1EditText = (EditText) findViewById(R.id.arg1);
        resultEditText = (EditText) findViewById(R.id.result);
        sumBtn = (Button) findViewById(R.id.sum);
        subBtn = (Button) findViewById(R.id.sub);
        
       
        
        Intent CalcServiceAddr = new Intent();
        CalcServiceAddr.setAction("com.blog.calc.action.CALC_SERVICE_ADDR");
          
        ServiceConnection conn = new ServiceConnection() {
			
			@Override
			public void onServiceDisconnected(ComponentName name) {
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void onServiceConnected(ComponentName name, IBinder service) {
				// TODO Auto-generated method stub
				mCalc = Calc.Stub.asInterface(service);
			}
		};
		bindService(CalcServiceAddr, conn, Context.BIND_AUTO_CREATE);
		
		sumBtn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				float arg0 = Float.parseFloat((arg0EditText.getText().toString()));
			    float arg1 = Float.parseFloat((arg1EditText.getText().toString()));
			    try {
			    	resultEditText.setText(mCalc.sum(arg0, arg1)+"");
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		
		subBtn.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				float arg0 = Float.parseFloat((arg0EditText.getText().toString()));
			    float arg1 = Float.parseFloat((arg1EditText.getText().toString()));
			    try {
					resultEditText.setText(mCalc.sub(arg0, arg1)+"");
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
        
    }
}

 

简单说一下,所有界面控件得结合最前面贴的图对应一下。

1.先创建所有界面上的文本框和按钮对应的成员变量

2.创建一个远程接口Calc类型的成员变量mCalc(作用:一会绑定服务时,要把绑到的服务那边暴露的接口赋给它,之后用它随便调吧,从Binder角度说,它就是一个代理对象)

3.然后构建绑定远程服务方法bindService(Intent, ServiceConnection, int)的三个参数:第一个可以理解成要绑定的服务地址,第二个是到服务的连接,在实例化此连接时,你需要重写其onServiceConnected方法,在里面把连上的服务那边的暴露的接口赋给mCalc(mCalc = Calc.Stub.asInterface(service);),第三个参数时标志位,意思是只要绑定存在,如果服务死了或没启动,就启一下。

4.这样一旦绑定建立,我们的接口对象mCalc就得到了真正的,可供调用的CalcService服务暴露的接口,你用mCalc.sum(),mCalc.sub()随便调吧。

5.按下sum按钮时,我们得到两EditText的值,转成float。之后使用mCalc调用远程服务的sum接口,算出结果,把结果填充到最下面的EditText框里。

6.按下sub按钮时,同理。

下面是运行效果(左面是点加法按钮后,右面是点减法按钮后):

最后,还是贴上manifest和布局代码吧。AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blog.callcalc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="调用远程的计算器服务" >
        <activity
            android:label="调用远程的计算器服务"
            android:name=".CallRemoteCalcServiceActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


布局main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <TextView 
        android:id="@+id/t1"
        android:text="第一个数:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <EditText 
        android:id="@+id/arg0"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:numeric="decimal"
        />
    
    <TextView 
        android:id="@+id/t2"
        android:text="第二个数:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <EditText 
        android:id="@+id/arg1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:numeric="decimal"
        />
    
    <Button 
        android:id="@+id/sum"
        android:text="调用远程计算器的加法"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button 
        android:id="@+id/sub"
        android:text="调用远程计算器的减法"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/t3"
        android:text="运算结果:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <EditText
        android:id="@+id/result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:editable="false"
        />"
</LinearLayout>


 由于目前工作正好用到这一方面,为了使用,仓促学习了,有许多东西理解的还不够到位或有差错,工作使用完后,我想着得总结一下,不能用完就丢,自己就回顾了自己的学习理解过程,又写了个简单的小例子和解析。正好开通了CSDN博客,就作为我这个新手的第一篇文章吧。如果哪里写的不太对,希望各位前辈一定指点一二,不胜感谢。如果正好帮到了某个新手,我也很高兴,在探讨和总结中共同进步吧。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值