欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-29 15:30:40
意义: 由于每个应用进程都有自己的独立进程空间,在android平台上,一个进程通常不能访问另一个进程的内存空间,而我们经常需要夸进程传递对象,就需要把对象分解成操作对象...

意义:

由于每个应用进程都有自己的独立进程空间,在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则会创建两个完全独立的进程。