前言:欢迎转载,共同交流总结进步。如果转载请注明本文的原出处。
先解释一下什么是IPC:IPC是inter-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.aidl后ADT会自动生成。里面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博客,就作为我这个新手的第一篇文章吧。如果哪里写的不太对,希望各位前辈一定指点一二,不胜感谢。如果正好帮到了某个新手,我也很高兴,在探讨和总结中共同进步吧。