深入解读Android的内部进程通信接口AIDL

作者:zhoulc 时间:2022-09-09 04:02:02 

意义:

由于每个应用进程都有自己的独立进程空间,在android平台上,一个进程通常不能访问另一个进程的内存空间,而我们经常需要夸进程传递对象,就需要把对象分解成操作对象可以理解的基本单元,并且有序的通过进程边界。

定义:

AIDL(Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

说明以及实现流程:

AIDL接口和普通的java接口没有什么区别,只是扩展名为.aidl,保存在src目录下,如果其他应用程序需要IPC,则也需要在src目录下创建同样的AIDL文件,创建完毕之后,通过ADT工具,会在工程的gen目录下生成相对应的.java文件。

一般实现两个进程之间的通信需要实现下面几个步骤

(1)在Eclipse的android工程目录下面创建一个.aidl扩展名的文件,语法和java定义接口的语法差不多,不过需要自己手动import对应的包名。(比如需要用到list集合,则需要import java.util.List;)

(2)如果aidl文件符合规范,ADT工具会帮助编译器在gen目录下生成相对应的.java文件。

(3)需要继承实现一个服务类,跨进程调用的基础。

(4)在service端实现AIDL接口,如果有回调则在client端实现callback的AIDL接口。

(5)在AndroidManifest.xml注册service。

注意:

实现AIDL,我们需要注意以下五点

(1)AIDL只支持接口方法,不能公开static变量。

(2)AIDL接口方法如果有参数,则需要注意in、out、inout的使用规则,对于基本数据类型,默认是in类型,可以不需要添加声明,非基本可变对象需要在变量名之前添加方法类型

in表示输入参数,调用者把值传递给使用者使用。

out表示输出参数,调用者把容器传递给使用者填充,然后自己使用处理。

inout标书输入输出参数,传送相应的值并接收返回。

列举一个out的使用例子:
服务端传参数给客户端,客户端填充,服务端调用完之后,可以读取到客户端填写的内容,具体的例子后面将给出。

(3)AIDL定义的接口名必须和文件名一致。

(4)oneway表示用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式。

(5)AIDL传递非基本可变长度变量(非final对象),需要实现parcelable接口。
 parcel一般都用在Binder通信,通过read和write方法进行客户端与服务端的数据传递(通信)。
比如:frameworks层服务端与hardware客户端的Binder通信


reply->writeInt32(getCardReaderSize());
int mid = data.readInt32();

用来存放parcel数据的是内存(RAM),而不是永远介质(Nand等)。

parcelable定义了把数据写入parcel和从parcel读出数据的接口,一个类的实例,如果需要封装到消息中去,就必须实现这一接口,如果实现了这个接口,该类的实例就是可以“被打包”。
Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这个变量需要继承 Parcelable.Creator 接口。


package com.zlc.provider;

import android.os.Parcel;
import android.os.Parcelable;

public class Students implements Parcelable{
 private int stu_id;
 private String stu_name;
 public Students(Parcel source){
   stu_id = source.readInt();
   stu_name = source.readString();
 }
 public int getStu_id() {
   return stu_id;
 }
 public void setStu_id(int stu_id) {
   this.stu_id = stu_id;
 }
 public String getStu_name() {
   return stu_name;
 }
 public void setStu_name(String stu_name) {
   this.stu_name = stu_name;
 }
 @Override
 public int describeContents() {
   // TODO Auto-generated method stub
   return 0;
 }
 @Override
 public void writeToParcel(Parcel dest, int flags) {
   // TODO Auto-generated method stub
   dest.writeInt(stu_id);
   dest.writeString(stu_name);
 }
 //Interface that must be implemented and provided as a public CREATOR field that generates instances of your Parcelable class from a Parcel.
 public final static Parcelable.Creator<Students> CREATOR = new Parcelable.Creator<Students>() {

@Override
   public Students createFromParcel(Parcel source) {
     // TODO Auto-generated method stub
     return new Students(source);
   }

@Override
   public Students[] newArray(int size) {
     // TODO Auto-generated method stub
     return new Students[size];
   }
 };
}


实例:

下面列举一个例子,主要实现客户端调用服务端然后回调回来,具体实现功能改变客户端的文字和图片显示,这个例子暂时效果是图片的更改直接使用客户端已经准备好的图片,接下来几篇博客会基于这个功能完善,到达服务端可以发送文字、图片、文件句柄(I/O流),并且直接由服务端通过方法名称直接调用客户端方法,客户端只需要注册对应的view并且提供相应的方法给服务端使用,后面的两部的完善主要用到反射和重写MemoryFile(达到parcelable序列化效果)来实现。

(1)首先按照我们上面的步骤需要创建aidl文件,分别创建调用和回调的aidl文件,为了阐述更详细一些,小编把parcelable对象也添加进去,仅仅作为测试。
IMyAidlService.aidl主要由服务端实现客户端调用  


package com.zlc.aidl;
import com.zlc.aidl.DemoParcelable;
import com.zlc.aidl.AIDLCallback;
interface IMyAidlService{
 void registerClient(AIDLCallback cb);//注册回调
 void saveDemoInfo(in DemoParcelable demo);//实际调用方法
}

AIDLCallback.aidl主要由客户端实现,服务端调用


package com.zlc.aidl;
import com.zlc.aidl.DemoParcelable;
import java.util.List;
interface AIDLCallback {
 int returnResult(out List<DemoParcelable> list,int a);//回调给客户端
 void testMethod(out Bundle params);//用来测试参数in/out的使用
}

DemoParcelable.aidl声明传递对象:


package com.zlc.aidl;
parcelable DemoParcelable;

补充一点:out和in参数区别其实很明显我们直接查看adt生成在gen目录下对应的java文件就可以看出区别:当是out参数的时候是执行完之后从parcel对象读取值,而in参数时是写到parcel对象里面传过去。
我们看下当testMethod分别是out和in修饰时生成的文件
当时out的时候是从parcel对象里面读数据


mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply, 0);
_reply.readException();
if ((0!=_reply.readInt())) {
params.readFromParcel(_reply);
}

当时in的时候是从parcel对象里面取数据


if ((params!=null)) {
_data.writeInt(1);
params.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_testMethod, _data, _reply, 0);
_reply.readException();

(2)实现一个服务类用来实现进程之间通信MyAidlService.java,贴出部分代码,详细代码会在后面上传。


@Override
 public IBinder onBind(Intent intent) {
   // TODO Auto-generated method stub
   Log.d(TAG, "MyAidlService onBind");
   return mBinder;
 }

private final IMyAidlService.Stub mBinder = new IMyAidlService.Stub() {
   private AIDLCallback cb;

@Override
   public void saveDemoInfo(DemoParcelable demo) throws RemoteException {
     if (demo != null) {
       if ("meinv1".equals(demo.getDemo_name())) {
         demo.setDemo_name("meinv2");
       }
       list.add(demo);
       Log.d(TAG, "saveDemoInfo list.size = " + list.size() + " list = " + list);
       cb.returnResult(list, 5);
       Bundle params = new Bundle();
       cb.testMethod(params);
       int width = params.getInt("width", 0);
       int height = params.getInt("height", 0);
       Log.d(TAG, "width = " + width + " height = "+height);
     }
   }

@Override
   public void registerClient(AIDLCallback cb) throws RemoteException {
     cb.asBinder().linkToDeath(new DeathRecipient() {
       @Override
       public void binderDied() {
         try {
           Log.i(TAG, "[ServiceAIDLImpl]binderDied.");
         } catch (Throwable e) {
         }
       }
     }, 0);
   }
 };

   
(3)实现客户端连接并且实现callback方法 


private ServiceConnection mRemoteConnection = new ServiceConnection() {

@Override
   public void onServiceDisconnected(ComponentName name) {
     // TODO Auto-generated method stub
     Log.d(TAG, "onServiceDisconnected");
   }

@Override
   public void onServiceConnected(ComponentName name, IBinder service) {
     // TODO Auto-generated method stub
     Log.d(TAG, "onServiceConnected");
     mRemoteService = (IMyAidlService) IMyAidlService.Stub
         .asInterface(service);
     if(mRemoteService != null)
       Log.d(TAG, "onServiceConnected success");
   }
 };
……
btn.setOnClickListener(new OnClickListener() {

@Override
     public void onClick(View v) {
       // TODO Auto-generated method stub
       String actionName = "com.zlc.aidl.server.MyAidlService";
       Intent intent = new Intent(actionName);
       boolean ret = bindService(intent, mRemoteConnection,
           Context.BIND_AUTO_CREATE);
       Log.d(TAG, " ret ?=" + ret);
       if (ret) {
         new Thread(new Runnable() {

@Override
           public void run() {
             // TODO Auto-generated method stub
             try {
               DemoParcelable demo = new DemoParcelable();
               List<String> list = new ArrayList<String>();
               list.add("like dance");
               demo.setDemo_id((Integer) img.getTag());
               demo.setDemo_name("meinv1");
               demo.setDemo_list(list);
               mRemoteService.registerClient(callback);
               mRemoteService.saveDemoInfo(demo);
             } catch (Exception e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
             }
           }
         }).start();

}
     }
   });
 }
……
private final AIDLCallback callback = new AIDLCallback.Stub() {

@Override
   public int returnResult(List<DemoParcelable> list, int a)
       throws RemoteException {
     if (list != null)
       Log.d(TAG, "list.size = " + list.size()+"  a="+a);
     for (DemoParcelable demoParcelable : list) {
       doFresh(demoParcelable);
     }
     return 0;
   }

@Override
   public void testMethod(Bundle outParams) throws RemoteException {
     // TODO Auto-generated method stub
     if (outParams != null) {
       outParams.putInt("width", 11);
       outParams.putInt("height", 12);
     }

}
 };

   
(4)在androidManifest.xml里面注册service服务。   
注意一点:android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
通过ps直接看pid进程号就可以看出。
让应用的组件在一个单独的进程中运行,如果带冒号: ,则创建一个专属于当前进程的进程,如果不带冒号,需要使用标准的命名规范命名进程名,例如com.xxx.xxx.xxx,而且该进程是全局共享的进程,即不同应用的组件都可以运行于该进程。
这可以突破应用程序的24M(或16M)内存限制。
总之,使用带:remote的属性的进程id pid不同,父进程ID PPID是一样的。而使用不带冒号的remote则会创建两个完全独立的进程。

标签:Android,AIDL
0
投稿

猜你喜欢

  • OpenCV+Qt实现图像处理操作

    2022-08-28 18:46:44
  • Android RecyclerBarChart绘制使用教程

    2023-06-19 12:18:36
  • Android自定义实现图片加文字功能

    2022-08-03 22:05:49
  • android仿音悦台页面交互效果实例代码

    2023-03-27 12:55:54
  • c# 调用Win32Api关闭当前应用的方法

    2023-09-22 20:51:26
  • 带着新人看java虚拟机01(推荐)

    2023-05-28 04:38:16
  • C#读写文本文件的方法

    2022-02-01 15:20:51
  • java设计模式--原型模式详解

    2023-11-25 05:08:24
  • c# 实现语音合成

    2021-06-16 00:17:47
  • SpringBoot中使用 RabbitMQ的教程详解

    2023-10-22 01:02:42
  • Android分屏多窗口的实践代码

    2021-06-21 21:34:58
  • Java Map所有的值转为String类型

    2022-09-05 11:53:09
  • Android开发教程之如何屏蔽View的重复点击

    2021-05-27 10:06:56
  • javaweb实现在线支付功能

    2022-02-08 19:17:34
  • SpringBoot持久化层操作支持技巧

    2023-11-24 06:40:32
  • Java多线程实现简易微信发红包的方法实例

    2023-04-16 11:46:15
  • DataGridView不显示最下面的新行、判断新增行、删除行操作

    2022-11-26 16:14:36
  • Java设计模式之java原型模式详解

    2022-04-01 03:16:40
  • Kotlin标准函数与静态方法基础知识详解

    2021-12-18 08:12:13
  • Android快速实现发送邮件实例

    2022-06-10 03:39:48
  • asp之家 软件编程 m.aspxhome.com