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

实例讲解Android中的AIDL内部进程通信接口使用

程序员文章站 2024-02-29 17:01:46
首先描述下我们想要实现的内容,我们希望在一个应用中通过点击按钮,去操作另一个进程中应用的音乐播放功能。 如图,我们点击“播放”时,系统就会去远程调用我们提供的一个...

首先描述下我们想要实现的内容,我们希望在一个应用中通过点击按钮,去操作另一个进程中应用的音乐播放功能。

实例讲解Android中的AIDL内部进程通信接口使用

如图,我们点击“播放”时,系统就会去远程调用我们提供的一个service(与当前service不是同一个应用哦),然后操作service中的音乐播放,点击“停止”则会终止播放。想要重新播放的话,必须先点“销毁service”,再点播放按钮哦。(至于这里为什么要先点销毁按钮才能播放,完全是为了给大家展示下,远程调用service时,怎么去解绑service)。

在这个例子中,我们用到了一个非常重要的概念,aidl。

aidl(android interface definition language)android接口描述语言,其主要的作用就是允许我们在不同的进程间进行通信。

      我们都知道每个应用程序都运行在各自的进程中,并且android平台是不允许不同进程间进行直接的对象数据等传递的。如果我们非要进行进程间的通讯,那么我们就必须将我们的数据对象分解成一个个微小的、可以被操作系统理解的数据单元,然后有序的通过进程边界的检查。

      如果让我们自己实现,那么这个过程都愁死一批批的程序人了。幸好android,为我们解决了这个问题,这就是aidl出现的原因了。

      aidl (android interface definition language)是一种idl 语言,用于生成可以在android设备上两个进程之间进行进程间通信(ipc)的代码。如果在一个进程中(例如activity)要调用另一个进程中(例如service)对象的操作,就可以使用aidl生成可序列化的参数。
aidl ipc机制是面向接口的,像com或corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

 

     注意:aidl只支持方法,不能定义静态成员,并且方法也不能有类似public等的修饰符;aidl运行方法有任何类型的参数和返回值,在java的类型中,以下的类型使用时不需要导入包(import),基本数据类型、string、map、list.当然为了避免出错,建议只要使用了,就导入包。
     
使用aidl的步骤:

服务端(提供服务):

第一步:定义一个*.aidl文件,该文件里是符合aidl语言规范的接口定义,里面定义了外部应用可以访问的方法。当我们保存该文件的时候,eclipse会自动为我们在gen文件夹下生成一个相应的java接口文件。例如remoteserviceinterface.java

 第二步:定义一个自己的service,    并将其注册到androidmanifest.xml文件中,例如:

<service android:name="myremoteservice">
 <intent-filter>
 <action android:name="cn.com.chenzheng_java.remote"/>
 </intent-filter>
 </service>

注意这里一定要提供一个intent-filter,我们的客户端进程就是通过该action访问到服务端进程的哦。

我们都知道,在实现自己的service时,为了其他应用可以通过bindservice来和我们的service进行交互,我们都要实现service中的onbind()方法,并且返回一个继承了binder的内部类;在这里,eclipse自动为我们生成的remoteserviceinterface.java中有一个实现了binder的内部类,remoteserviceinterface.stub。aidl要求我们,在这里不能再直接去实现binder类了,而是去实现,aidl提供给我们的stub类。 实现stub类的同时,aidl还要求我们同时实现我们在接口中定义的各种服务的具体实现。至此为止,我们的服务端已经和我们的aidl文件绑定到一起了哦。

客户端:

第一步:客户端要想使用该服务,肯定要先知道我们的服务在aidl文件中到底对外提供了什么服务,对吧?所以,第一步,我们要做的就是,将aidl文件拷贝一份到客户端的程序中(这里一定要注意,包路径要和服务端的保持一致哦,例如服务端为cn.com.chenzheng_java.remote.a.aidl,那么在客户端这边也应该是这个路径)。

第二步:我们都知道,想要和service交互,我们要通过bindservice方法,该方法中有一个serviceconnection类型的参数。而我们的主要代码便是在该接口的实现中。

第三步:在serviceconnection实现类的onserviceconnected(componentname name, ibinder service)方法中通过类似remoteserviceinterface = remoteserviceinterface.stub.asinterface(service);方式就可以获得远程服务端提供的服务的实例,然后我们就可以通过remoteserviceinterface 对象调用接口中提供的方法进行交互了。(这里的关键是通过*.stub.asinterface(service);方法获取一个aidl接口的实例哦)

我们前面在服务端中说过了,必须提供一个intent-filter来匹配请求是否合法,所以我们在客户端访问服务的时候,还必须传递包含了匹配action的intent哦。

下边整体是代码:
 远程服务端:

实例讲解Android中的AIDL内部进程通信接口使用

remoteserviceinterface.aidl

package cn.com.chenzheng_java.remote; 
/**aidl的语法和interface的语法稍微有些不同, 
*它里面只能有方法,并且java中的那些修饰词如public等,在这里是不支持的. 
*当我们在eclipse中添加一个以.aidl结尾的aidl文件时,如果你的格式正确,那么 
*在gen目录下,你就会看到系统根据你提供aidl文件自动为你生成的相应的java类 
*@author chenzheng_java 
*/ 
 
 
 interface remoteserviceinterface { 
 
   void startmusic(); 
   void stopmusic(); 
} 

myremoteservice.java

package cn.com.chenzheng_java.remote; 
 
import java.io.ioexception; 
 
import android.app.service; 
import android.content.intent; 
import android.media.mediaplayer; 
import android.os.ibinder; 
import android.os.remoteexception; 
/** 
 * @description 远程service 
 * @author chenzheng_java 
 * 
 */ 
public class myremoteservice extends service { 
  mediaplayer mediaplayer; 
  @override 
  public ibinder onbind(intent intent) { 
    return new mybinder(); 
  } 
  @override 
  public void oncreate() { 
    /* 
     * mediaplayer.create方法第一个参数实际上为context对象,这里我们直接传递service给它, 
     * 是因为service本身也是继承了context的。 
     */ 
     mediaplayer = mediaplayer.create(myremoteservice.this, r.raw.aiweier); 
    super.oncreate(); 
  } 
   
  /** 
   * @description 这里一定要注意,继承的不再是binder,而是系统自动为我们生成的 
   *       binder的一个内部类,叫做stub。我们通过继承stub类,然后实现aidl 
   *       中定义的方法,便等于对接口的方法进行了具体的实现。 
   * @author chenzheng_java 
   * 
   */ 
  private class mybinder extends remoteserviceinterface.stub{ 
 
    public void startmusic() throws remoteexception { 
       
       mediaplayer.start(); 
       
    } 
 
    public void stopmusic() throws remoteexception { 
      mediaplayer.stop(); 
      try { 
        mediaplayer.prepare(); 
      } catch (illegalstateexception e) { 
        e.printstacktrace(); 
      } catch (ioexception e) { 
        e.printstacktrace(); 
      } 
    } 
     
  } 
 
} 

remoteserviceactivity.java

package cn.com.chenzheng_java.remote; 
 
import android.app.activity; 
import android.os.bundle; 
/** 
 * 很多人不理解,这里面基本上什么也没实现,为什么还要提供呢? 
 * 其实原因是这样的,service代码我们写好了,也在配置文件中注册了, 
 * 这样,手机系统就会识别了吗?不是的哦,你至少得将该service所在的 
 * 应用运行一次才可以哦。要不手机怎么知道你添加了一个service啊,对吧! 
 * @author chenzheng_java 
 * 
 */ 
public class remoteserviceactivity extends activity { 
  @override 
  public void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main); 
  } 
} 

androidmanifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
   package="cn.com.chenzheng_java.remote" 
   android:versioncode="1" 
   android:versionname="1.0"> 
  <uses-sdk android:minsdkversion="8" /> 
 
  <application android:icon="@drawable/icon" android:label="@string/app_name"> 
    <activity android:name=".remoteserviceactivity" 
         android:label="@string/app_name"> 
      <intent-filter> 
        <action android:name="android.intent.action.main" /> 
        <category android:name="android.intent.category.launcher" /> 
      </intent-filter> 
    </activity> 
     
  <service android:name="myremoteservice"> 
    <intent-filter> 
    <action android:name="cn.com.chenzheng_java.remote"/> 
    </intent-filter> 
  </service> 
 
  </application> 
</manifest> 

客户端:

实例讲解Android中的AIDL内部进程通信接口使用

activity代码:

package cn.com.chenzheng_java; 
 
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.util.log; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.widget.button; 
import cn.com.chenzheng_java.remote.remoteserviceinterface; 
/*** 
 * @author chenzheng_java 
 * @description 通过当前activity去调用不同进程中的远程service 
 */ 
public class localserviceactivity extends activity implements onclicklistener { 
  string action_name = "cn.com.chenzheng_java.remote"; 
  boolean flag = false; 
  button button_start ; 
  button button_stop ; 
  button button_destroy ; 
   
  @override 
  public void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.music); 
     
    button_start = (button) findviewbyid(r.id.button1); 
    button_stop = (button) findviewbyid(r.id.button2); 
    button_destroy = (button) findviewbyid(r.id.button3); 
     
    button_start.setonclicklistener(this); 
    button_stop.setonclicklistener(this); 
    button_destroy.setonclicklistener(this); 
  } 
 
  remoteserviceinterface remoteserviceinterface ; 
   
  private class myserviceconnection implements serviceconnection{ 
    public void onserviceconnected(componentname name, ibinder service) { 
      remoteserviceinterface = remoteserviceinterface.stub.asinterface(service); 
      try { 
        log.i("flag", flag+""); 
        if(flag){ 
          log.i("通知", "已经开始唱歌"); 
          remoteserviceinterface.startmusic(); 
        }else{ 
          log.i("通知", "已经停止唱歌"); 
          remoteserviceinterface.stopmusic(); 
        } 
         
         
      } catch (remoteexception e) { 
        e.printstacktrace(); 
      } 
       
    } 
 
    public void onservicedisconnected(componentname name) { 
       
    } 
     
  } 
   
  private myserviceconnection serviceconnection = new myserviceconnection(); 
   
  public void onclick(view v) { 
    if(v == button_start){ 
      flag = true; 
      intent intent = new intent(action_name); 
      /** 
       * context.bind_auto_create 当绑定service时,如果发现尚未create,那么就先create一个,然后绑定 
       */ 
      bindservice(intent, serviceconnection ,context.bind_auto_create); 
    } 
     
    if(v == button_stop){ 
      log.i("通知", "已经点击了停止按钮"); 
      flag = false; 
      intent intent = new intent(action_name); 
      bindservice(intent, serviceconnection ,context.bind_auto_create); 
      try { 
        remoteserviceinterface.stopmusic(); 
      } catch (remoteexception e) { 
        e.printstacktrace(); 
      } 
    } 
     
    if(v == button_destroy){ 
      flag = false; 
      intent intent = new intent(action_name); 
      bindservice(intent, serviceconnection ,context.bind_auto_create); 
      unbindservice(serviceconnection); 
    } 
     
  } 
} 

music.xml

<?xml version="1.0" encoding="utf-8"?> 
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:layout_width="match_parent" android:layout_height="match_parent"> 
  <button android:text="播放" android:id="@+id/button1" 
    android:layout_width="wrap_content" android:layout_height="wrap_content"></button> 
  <button android:text="停止" android:id="@+id/button2" 
    android:layout_width="wrap_content" android:layout_height="wrap_content"></button> 
  <button android:text="销毁service" android:id="@+id/button3" 
    android:layout_width="wrap_content" android:layout_height="wrap_content"></button> 
</linearlayout> 


其他没有粘贴出来的代码都是由系统默认生成的。

aidl与传递对象

除了上面我们提到的通过service提供音乐播放等类似的服务之外,我们还可以通过service将对象传递回来哦,你知道怎么用吗,先看例子:

实例讲解Android中的AIDL内部进程通信接口使用

当我们点击“获取”时,会从另一个线程的service中获取一个对象,然后将里面的内容读出来。

对于aidl实现以对象的方式交互。主要步骤如下:

服务端:
第一:定义一个实体类,这里是beauty,定义一个服务接口aidl文件remotebeauty.aidl,这里有一点需要注意,我们引用自定义的实体类到aidl中时需要通过import导入包,但是你会发现,即使你导入了包,还是提示找不到,这时候,你要做的是,建一个以实体类名称命名的aidl文件,如beauty.aidl,在里面添加一句pracelable beauty。

第二:开始编写beauty,这里一定要注意,它一定要实现pracelable接口,该接口是一个序列化的接口,功能和serializable相似,但是功能更加的迅速。此外,在该beauty内部一定要声明一个public static final pracelable.creator<t>creator对象!!除了里面的那个t代表实体类之外,其他的都不准改变哦。

第三:在androidmanifest.xml中注册service。并定义好访问该service的action字符串。

客户端:
客户端这边相应的要简单很多,但是要注意的一点是,要将实体类还有aidl文件都拷贝过来哦,而且要保证路径完全一致!!
实例讲解Android中的AIDL内部进程通信接口使用
beauty.java

package cn.com.chenzheng_java.service; 
 
import android.os.parcel; 
import android.os.parcelable; 
/** 
 * 
 * @author chenzheng_java 
 * @description parcelable是android提供的一个比serializable效率更高的序列号接口 
 *       这里必须要继承parcelable哦,不序列号怎么可以传递……对吧?! 
 * 在实体类我们要做两件重要的事情: 
 * 第一:实现parcelable接口 
 * 第二:定义一个parcelable.creator类型的creator对象 
 * 第三:要提供一个beauty.aidl文件,其中内容为parcelable beauty,定义了之后,在其他aidl文件中引用beauty时便不会提示出错了。 
 * @since 2011/03/18 
 * 
 */ 
public class beauty implements parcelable { 
 
  string name ; 
  int age ; 
  string sex ; 
   
  public string getname() { 
    return name; 
  } 
 
  public void setname(string name) { 
    this.name = name; 
  } 
 
  public int getage() { 
    return age; 
  } 
 
  public void setage(int age) { 
    this.age = age; 
  } 
 
  public string getsex() { 
    return sex; 
  } 
 
  public void setsex(string sex) { 
    this.sex = sex; 
  } 
 
  @override 
  public int describecontents() { 
    return 0; 
  } 
 
  /** 
   * 将对象序列号 
   * dest 就是对象即将写入的目的对象 
   * flags 有关对象序列号的方式的标识 
   * 这里要注意,写入的顺序要和在createfromparcel方法中读出的顺序完全相同。例如这里先写入的为name, 
   * 那么在createfromparcel就要先读name 
   */ 
  @override 
  public void writetoparcel(parcel dest, int flags) { 
       
      dest.writestring(name); 
      dest.writeint(age); 
      dest.writestring(sex); 
  } 
  /** 
   * 在想要进行序列号传递的实体类内部一定要声明该常量。常量名只能是creator,类型也必须是 
   * parcelable.creator<t> 
   */ 
  public static final parcelable.creator<beauty> creator = new creator<beauty>() { 
     
    /** 
     * 创建一个要序列号的实体类的数组,数组中存储的都设置为null 
     */ 
    @override 
    public beauty[] newarray(int size) { 
      return new beauty[size]; 
    } 
     
    /*** 
     * 根据序列号的parcel对象,反序列号为原本的实体对象 
     * 读出顺序要和writetoparcel的写入顺序相同 
     */ 
    @override 
    public beauty createfromparcel(parcel source) { 
      string name = source.readstring(); 
      int age = source.readint(); 
      string sex = source.readstring(); 
      beauty beauty = new beauty(); 
      beauty.setname(name); 
      beauty.setage(age); 
      beauty.setsex(sex); 
       
      return beauty; 
    } 
  };    
   
} 

remoteservice.java

package cn.com.chenzheng_java.service; 
 
import android.app.service; 
import android.content.intent; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.util.log; 
/** 
 * 
 * @author chenzheng_java 
 * @description 提供服务的service 
 * 
 */ 
public class remoteservice extends service { 
 
  @override 
  public ibinder onbind(intent intent) { 
    log.i("通知", "执行了onbind"); 
    return new mybinder(); 
  } 
 
   
  private class mybinder extends remotebeauty.stub{ 
 
    @override 
    public beauty getbeauty() throws remoteexception { 
       
      beauty beauty = new beauty(); 
      beauty.setname("feifei"); 
      beauty.setage(21); 
      beauty.setsex("female"); 
       
      return beauty; 
    }} 
   
   
   
} 

serviceactivity.java

package cn.com.chenzheng_java.service; 
 
import android.app.activity; 
import android.os.bundle; 
/** 
 * @description 进程之间对象数据的传递 
 * @author chenzheng_java 
 * 
 */ 
public class serviceactivity extends activity { 
  @override 
  public void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main); 
  } 
} 

beauty.aidl

parcelable beauty; 

remotebeauty.aidl

package cn.com.chenzheng_java.service; 
import cn.com.chenzheng_java.service.beauty; 
 interface remotebeauty { 
 
  beauty getbeauty(); 
   
} 

manifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
   package="cn.com.chenzheng_java.service" 
   android:versioncode="1" 
   android:versionname="1.0"> 
  <uses-sdk android:minsdkversion="8" /> 
 
  <application android:icon="@drawable/icon" android:label="@string/app_name"> 
    <activity android:name=".serviceactivity" 
         android:label="@string/app_name"> 
      <intent-filter> 
        <action android:name="android.intent.action.main" /> 
        <category android:name="android.intent.category.launcher" /> 
      </intent-filter> 
    </activity> 
 
<!-- service开始 --> 
  <service android:name="remoteservice"> 
    <intent-filter> 
      <action android:name="cn.com.chenzheng_java.remote2"/> 
    </intent-filter> 
  </service> 
<!-- service结束 --> 
  </application> 
</manifest> 

客户端:

实例讲解Android中的AIDL内部进程通信接口使用

clientactivity.java

package cn.com.chenzheng_java.client; 
 
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.util.log; 
import android.view.view; 
import android.view.view.onclicklistener; 
import android.widget.button; 
import android.widget.textview; 
import cn.com.chenzheng_java.service.beauty; 
import cn.com.chenzheng_java.service.remotebeauty; 
 
public class clientactivity extends activity implements onclicklistener { 
  textview textview ; 
  button button ; 
  string actionname = "cn.com.chenzheng_java.remote2"; 
  remotebeauty remotebeauty; 
   
  @override 
  public void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main); 
    textview = (textview) findviewbyid(r.id.textview1); 
    button = (button) findviewbyid(r.id.button1); 
     
    button.setonclicklistener(this); 
  } 
 
  private class myserviceconnection implements serviceconnection{ 
 
    @override 
    public void onserviceconnected(componentname name, ibinder service) { 
      log.i("通知", "链接成功!"); 
      remotebeauty = remotebeauty.stub.asinterface(service); 
      try { 
        beauty beauty = remotebeauty.getbeauty(); 
        textview.settext("美女 姓名:"+beauty.getname()+" 年龄:"+beauty.getage() +" 性别:"+beauty.getsex()); 
         
         
      } catch (remoteexception e) { 
        e.printstacktrace(); 
      } 
    } 
 
    @override 
    public void onservicedisconnected(componentname name) { 
       
    } 
     
  } 
  myserviceconnection connection = new myserviceconnection(); 
  @override 
  public void onclick(view v) { 
    intent intent = new intent(actionname); 
    bindservice(intent, connection, context.bind_auto_create); 
  } 
} 

另外beauty.java 以及remotebeauty.aidl都是从服务端系统中拷贝过来的哦。

如果你想你的service在系统开机时自启动。可以在service的androidmanifest.xml中加上这样的配置。

<receiver android:name=".startbroadcastreceiver"> 

      <intent-filter>

  <action android:name="android.intent.action.boot_completed"/>

      </intent-filter>    

</receiver>