android跨进程通信(IPC):使用AIDL
AIDL的作用
AIDL(AndroidInterfaceDefinitionLanguage)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocesscommunication,IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDLIPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。
选择AIDL的使用场合
官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。如果不需要进行不同应用程序间的并发通信(IPC),youshouldcreateyourinterfacebyimplementingaBinder;或者你想进行IPC,但不需要处理多线程的,则implementyourinterfaceusingaMessenger。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService。如何绑定服务,请参考我的另一篇文章:http://blog.csdn.net/singwhatiwanna/article/details/9058143。这里先假设你已经了解如何使用bindService。
如何使用AIDL
1.先建立一个android工程,用作服务端
创建一个android工程,用来充当跨进程通信的服务端。
2.创建一个包名用来存放aidl文件
创建一个包名用来存放aidl文件,比如com.ryg.sayhi.aidl,在里面新建IMyService.aidl文件,如果需要访问自定义对象,还需要建立对象的aidl文件,这里我们由于使用了自定义对象Student,所以,还需要创建Student.aidl和Student.java。注意,这三个文件,需要都放在com.ryg.sayhi.aidl包里。下面描述如何写这三个文件。
IMyService.aidl代码如下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.ryg.sayhi.aidl;
importcom.ryg.sayhi.aidl.Student;
interfaceIMyService{
ListgetStudent();
voidaddStudent(inStudentstudent);
}
说明:
aidl中支持的参数类型为:基本类型(int,long,char,boolean等),String,CharSequence,List,Map,其他类型必须使用import导入,即使它们可能在同一个包里,比如上面的Student,尽管它和IMyService在同一个包中,但是还是需要显示的import进来。
另外,接口中的参数除了aidl支持的类型,其他类型必须标识其方向:到底是输入还是输出抑或两者兼之,用in,out或者inout来表示,上面的代码我们用in标记,因为它是输入型参数。
在gen下面可以看到,eclipse为我们自动生成了一个代理类
publicstaticabstractclassStubextendsandroid.os.Binderimplementscom.ryg.sayhi.aidl.IMyService
可见这个Stub类就是一个普通的Binder,只不过它实现了我们定义的aidl接口。它还有一个静态方法
publicstaticcom.ryg.sayhi.aidl.IMyServiceasInterface(android.os.IBinderobj)
这个方法很有用,通过它,我们就可以在客户端中得到IMyService的实例,进而通过实例来调用其方法。
Student.aidl代码如下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.ryg.sayhi.aidl;
parcelableStudent;
说明:这里parcelable是个类型,首字母是小写的,和Parcelable接口不是一个东西,要注意。
Student.java代码如下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
packagecom.ryg.sayhi.aidl;
importjava.util.Locale;
importandroid.os.Parcel;
importandroid.os.Parcelable;
publicfinalclassStudentimplementsParcelable{
publicstaticfinalintSEX_MALE=1;
publicstaticfinalintSEX_FEMALE=2;
publicintsno;
publicStringname;
publicintsex;
publicintage;
publicStudent(){
}
publicstaticfinalParcelable.CreatorCREATOR=new
Parcelable.Creator(){
publicStudentcreateFromParcel(Parcelin){
returnnewStudent(in);
}
publicStudent[]newArray(intsize){
returnnewStudent[size];
}
};
privateStudent(Parcelin){
readFromParcel(in);
}
@Override
publicintdescribeContents(){
return0;
}
@Override
publicvoidwriteToParcel(Parceldest,intflags){
dest.writeInt(sno);
dest.writeString(name);
dest.writeInt(sex);
dest.writeInt(age);
}
publicvoidreadFromParcel(Parcelin){
sno=in.readInt();
name=in.readString();
sex=in.readInt();
age=in.readInt();
}
@Override
publicStringtoString(){
returnString.format(Locale.ENGLISH,"Student[%d,%s,%d,%d]",sno,name,sex,age);
}
}
说明:通过AIDL传输非基本类型的对象,被传输的对象需要序列化,序列化功能java有提供,但是androidsdk提供了更轻量级更方便的方法,即实现Parcelable接口,关于android的序列化,我会在以后写文章介绍。这里只要简单理解一下就行,大意是要实现如下函数
readFromParcel:从parcel中读取对象
writeToParcel:将对象写入parcel
describeContents:返回0即可
Parcelable.CreatorCREATOR:这个照着上面的代码抄就可以
需要注意的是,readFromParcel和writeToParcel操作数据成员的顺序要一致
3.创建服务端service
创建一个service,比如名为MyService.java,代码如下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
/
@authorscott
/
publicclassMyServiceextendsService
{
privatefinalstaticStringTAG="MyService";
privatestaticfinalStringPACKAGE_SAYHI="com.example.test";
privateNotificationManagermNotificationManager;
privatebooleanmCanRun=true;
privateListmStudents=newArrayList();
//这里实现了aidl中的抽象函数
privatefinalIMyService.StubmBinder=newIMyService.Stub(){
@Override
publicListgetStudent()throwsRemoteException{
synchronized(mStudents){
returnmStudents;
}
}
@Override
publicvoidaddStudent(Studentstudent)throwsRemoteException{
synchronized(mStudents){
if(!mStudents.contains(student)){
mStudents.add(student);
}
}
}
//在这里可以做权限认证,returnfalse意味着客户端的调用就会失败,比如下面,只允许包名为com.example.test的客户端通过,
//其他apk将无法完成调用过程
publicbooleanonTransact(intcode,Parceldata,Parcelreply,intflags)
throwsRemoteException{
StringpackageName=null;
String[]packages=MyService.this.getPackageManager().
getPackagesForUid(getCallingUid());
if(packages!=null&&packages.length>0){
packageName=packages[0];
}
Log.d(TAG,"onTransact:"+packageName);
if(!PACKAGE_SAYHI.equals(packageName)){
returnfalse;
}
returnsuper.onTransact(code,data,reply,flags);
}
};
@Override
publicvoidonCreate()
{
Threadthr=newThread(null,newServiceWorker(),"BackgroundService");
thr.start();
synchronized(mStudents){
for(inti=1;i<6;i++){
Studentstudent=newStudent();
student.name="student#"+i;
student.age=i5;
mStudents.add(student);
}
}
mNotificationManager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
super.onCreate();
}
@Override
publicIBinderonBind(Intentintent)
{
Log.d(TAG,String.format("onbind,intent=%s",intent.toString()));
displayNotificationMessage("服务已启动");
returnmBinder;
}
@Override
publicintonStartCommand(Intentintent,intflags,intstartId)
{
returnsuper.onStartCommand(intent,flags,startId);
}
@Override
publicvoidonDestroy()
{
mCanRun=false;
super.onDestroy();
}
privatevoiddisplayNotificationMessage(Stringmessage)
{
Notificationnotification=newNotification(R.drawable.icon,message,
System.currentTimeMillis());
notification.flags=Notification.FLAG_AUTO_CANCEL;
notification.defaults|=Notification.DEFAULT_ALL;
PendingIntentcontentIntent=PendingIntent.getActivity(this,0,
newIntent(this,MyActivity.cwww.shanxiwang.netlass),0);
notification.setLatestEventInfo(this,"我的通知",message,
contentIntent);
mNotificationManager.notify(R.id.app_notification_id+1,notification);
}
classServiceWorkerimplementsRunnable
{
longcounter=0;
@Override
publicvoidrun()
{
//dobackgroundprocessinghere.....
while(mCanRun)
{
Log.d("scott",""+counter);
counter++;
try
{
Thread.sleep(2000);
}catch(InterruptedExceptione)
{
e.printStackTrace();
}
}
}
}
}
说明:为了表示service的确在活着,我通过打log的方式,每2s打印一次计数。上述代码的关键在于onBind函数,当客户端bind上来的时候,将IMyService.StubmBinder返回给客户端,这个mBinder是aidl的存根,其实现了之前定义的aidl接口中的抽象函数。
问题:问题来了,有可能你的service只想让某个特定的apk使用,而不是所有apk都能使用,这个时候,你需要重写Stub中的onTransact方法,根据调用者的uid来获得其信息,然后做权限认证,如果返回true,则调用成功,否则调用会失败。对于其他apk,你只要在onTransact中返回false就可以让其无法调用IMyService中的方法,这样就可以解决这个问题了。
4.在AndroidMenifest中声明service
[html]viewplaincopy在CODE上查看代码片派生到我的代码片
android:name="com.ryg.sayhi.MyService"
android:process=":remote"
android:exported="true">
说明:上述的是为了能让其他apk隐式bindService,通过隐式调用的方式来起activity或者service,需要把category设为default,这是因为,隐式调用的时候,intent中的category默认会被设置为default。
5.新建一个工程,充当客户端
新建一个客户端工程,将服务端工程中的com.ryg.sayhi.aidl包整个拷贝到客户端工程的src下,这个时候,客户端com.ryg.sayhi.aidl包是和服务端工程完全一样的。如果客户端工程中不采用服务端的包名,客户端将无法正常工作,比如你把客户端中com.ryg.sayhi.aidl改一下名字,你运行程序的时候将会crash,也就是说,客户端存放aidl文件的包必须和服务端一样。客户端bindService的代码就比较简单了,如下:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
importcom.ryg.sayhi.aidl.IMyService;
importcom.ryg.sayhi.aidl.Student;
publicclassMainActivityextendsActivityimplementsOnClickListener{
privatestaticfinalStringACTION_BIND_SERVICE="com.ryg.sayhi.MyService";
privateIMyServicemIMyService;
privateServiceConnectionmServiceConnection=newServiceConnection()
{
@Override
publicvoidonServiceDisconnected(ComponentNamename)
{
mIMyService=null;
}
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice)
{
//通过服务端onBind方法返回的binder对象得到IMyService的实例,得到实例就可以调用它的方法了
mIMyService=IMyService.Stub.asInterface(service);
try{
Studentstudent=mIMyService.getStudent().get(0);
showDialog(student.toString());
}catch(RemoteExceptione){
e.printStackTrace();
}
}
};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Buttonbutton1=(Button)findViewById(R.id.button1);
button1.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewview){
if(view.getId()==R.id.button1){
IntentintentService=newIntent(ACTION_BIND_SERVICE);
intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
MainActivity.this.bindService(intentService,mServiceConnection,BIND_AUTO_CREATE);
}
}
publicvoidshowDialog(Stringmessage)
{
newAlertDialog.Builder(MainActivity.this)
.setTitle("scott")
.setMessage(message)
.setPositiveButton("确定",null)
.show();
}
@Override
protectedvoidonDestroy(){
if(mIMyService!=null){
unbindService(mServiceConnection);
}
super.onDestroy();
}
}
运行效果
可以看到,当点击按钮1的时候,客户端bindService到服务端apk,并且调用服务端的接口mIMyService.getStudent()来获取学生列表,并且把返回列表中第一个学生的信息显示出来,这就是整个ipc过程,需要注意的是:学生列表是另一个apk中的数据,通过aidl,我们才得到的。另外,如果你在onTransact中返回false,将会发现,获取的学生列表是空的,这意味着方法调用失败了,也就是实现了权限认证。
|
|