Android应用开发之Android进阶学习之使用远程服务AIDL实现进程间简单通信
凌雪 2018-09-21 来源 :网络 阅读 445 评论 0

摘要:本文将带你了解Android应用开发之Android进阶学习之使用远程服务AIDL实现进程间简单通信,希望本文对大家学Android有所帮助。

本文将带你了解Android应用开发之Android进阶学习之使用远程服务AIDL实现进程间简单通信,希望本文对大家学Android有所帮助。


引言
    作为Android四大组件之一的服务Service,相信大家都不会陌生,从初学者的时候就知道分为本地服务和远程服务。本地服务自不必多说,这篇就总结下如何使用远程服务,由于远程服务AIDL 底层涉及到Binder   机制(这个是Android核心IPC机制,以后打算专门整理出一系列关于Binder Java层的原理,可以说AIDL就是Binder机制的一种体现),本篇着重总结用法,结合小例子简单总结下详细使用步骤和注意事项。
    一、AIDL概述
AIDL全称(Android Interface Definition Language)是Android中进程间通信(IPC,全称Inter-Process   Communication)方式中的一种(也是极其重要且极其核心的一种,系统相当多的IPC都是通过AIDL),每一个进程都拥有自己的独立Dalvik VM实例,在Dalvik VM里拥有自己的一块独立的内存且在自己的内存上存储自己的数据,执行着各自的操作。每个进程之间都互不相知,彼此都是陌生地生活在封闭的空间。虽然也有其他方式比如BroadcastReceiver , Message 等,但是 BroadcastReceiver 占用的系统资源比较多,显然不适合频繁的跨进程通信;而Message 进行跨进程通信时请求队列是同步进行的,无法并发执行,于是AIDL应运而生,作为开发者,可以把它看成是一种客户端和服务端(暂且把提供服务的叫为服务端,使用服务的叫为客户端,因为Binder本质就是C/S模式的)双方都能理解的一种约定或者协议,就像是其他协议一样,要想通信必须按照双方约定的规则来编写一个具体的协议文件,至于系统如何进行通信那留到后面Binder系列再作说明,总之,通过AIDL我们开发者就可以简单的实现各种类型的跨进程通信。所有的AIDL文件大致可以分为两类:用来定义Parcelable对象供其他AIDL文件使用AIDL中非默认支持的数据类型和用来定义方法接口来完成跨进程通信的(所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用),总之,所有AIDL文件都仅仅是“定义”,而不涉及具体的实现。
    二、AIDL的基本语法
AIDL语法十分简单,基本上和Java中的接口的语法一样,但还是有些细微差别(注意:aidl接口中只能包含方法的定义,不能通过它来暴露一些静态字段):
1、AIDL的文件类型和存放路径
AIDL文件的后缀是 .aidl,存放在名为aidl文件夹(与java文件夹同级),并且与java文件不同,定义和实现是分开的(也许有点不恰当,先这样说吧)所谓定义就是我们编写的具体的aidl文件,部分实现指的是Android Studio帮我们自动生成的文件(.\build\generated\source\aidl),如果rebuild之后还是无法生成这部分的代码,说明你的aidl文件哪里出错了,一般都是包名、文件名没有对应上导致找不到。
2、AIDL支持的数据类型
2.1、默认支持的数据类型
AIDL默认支持一些基本数据类型,在使用这些数据类型的时候是不需要通过import命令手动导包的。
Java的八种基本数据类型: byte,short,int,long,float,double,boolean,char   String 和CharSequence   List支持泛型但List中的所有元素的类型必须是AIDL支持的或者是一个其他AIDL生成的接口或者是定义的Parcelable(实现Parcelable接口的JavaBean),接收方得到的总是ArrayList   Map不支持泛型Map中的所有元素必须是AIDL支持的类型之一或者是一个其他AIDL生成的接口,或者是定义的Parcelable,接收方得到的总是HashMap
    2.2、需要通过import导入使用的类型
    除了以上默认的数据类型之外,其他类型在使用之前必须通过import导包,即使目标与当前正在编写的 .aidl 文件在同一个包下(而在 Java 中同一个包内是不需要手动导入的),比如自定义的Parcelable类型时,必须使用import语句进行导入。
2.3、aidl语法中参数列表中的定向tag
所有非基本数据类型的参数在传递的时候都需要指定一个方向tag来指明数据的流向,可以是in、out或者inout,基本数据类型默认是in,这三个修饰符被称为定向tag用于在跨进程通信时指定数据流向。
in——在服务端目标方法可以得到一个与实参值相同的对象(注意不是同一个对象,它们在不同虚拟机实例中),简而言之,经序列化后传递服务端,服务端反序列化得到一个与之值相同的新的对象 out——在发起远程调用时传入的实参不会传到到服务端,而是在服务端新建一个对象传给目标方法,待目标方法返回后将这个对象传回客户端。另外,服务端是调用无参的构造方法来新建对象,这意味着你的参数所对应的类必须有有无参的构造方法。简而言之,客户端不会序列化该参数,而是服务端调用无参构造方法新建了一个对象,待目标方法返回后,将参数写入reply返回给客户端   inout——可以看成是定向tag inout基本上算是in、out的并集,inout已经通过反序列化客户端传过来的数据得到一个新的对象,不会再new 一个了
    三、AIDL的使用步骤
1、定义AIDL文件
    在Android Studio的对应Module下上右键在   New ——>AIDL——>AIDL File即可自动完成创建AIDL模板文件,然后我们可以根据具体业务定义详细的接口方法。
   
    2、在生成的AIDL模板文件下编写我们自己的方法,然后rebuild module
如果没有成功在build\generated\source\aidl路径下看到生成同样名称的aidl文件,就clean一次再build,一般错误的原因都是由于图中那几点没有对应上,再检查下路径,包名,注意不要去修改build\generated\source\aidl下的文件。
   
    3、实现AIDL对应的服务
AIDL本质上说是一种Service,Android   Studio自动为我们生成的只是一个通信框架,至于我们AIDL中定义的接口方法实现什么功能,得在Service里去实现,只需要去继承AIDL中的XXX.Stub即可得到对应的Binder对象,然后在Service的onBind回调中返回即可。
4、在Androidmanifest清单中声明服务,指定一个自定义的Action
此Action是用于给客户端调用远程接口的时候,通过匿名的形式启动服务的,只要服务端在清单上注册了对应的服务,在对应的客户端我们就可以通过匿名启动服务的形式来强行拉起注册的服务进程(即使服务端APP没有启动,因为Android手机在启动的时候会去把手机上所有安装了的APP清单扫描一遍,并把组件对象“快照”缓存起来)
5、在客户端Module中引入相同的AIDL
经过以上四步,服务端的远程服务编写完毕,客户端要想使用,必须得持有一套一模一样的aidl,所以为了保险起见,直接把服务端的copy过来放到一样的包一样的路径下即可完成引入AIDL文件(这里有个小技巧,先切为Project模式,然后在java同级目录下新建一个aidl文件夹,创建和服务端相同的包,再把服务端的AIDL copy过来,当然你不copy也可以,只要保证AIDL文件内容一模一样即可)
6、通过bindService启动远程服务
    至于为什么要使用bindService方式而不是startService不用多说了吧,而启动bindService需要传递三个对象分别是上下文实例、ServiceConnection实例、服务端对应的Action,连接成功之后就会触发ServiceConnection中onServiceConnected回调方法,在这个方法里会返回远程服务的Binder,得到了远程Binder实例我们就可以调用AIDL接口里的方法了。
    四、普通AIDL的应用
    目前我使用过的AIDL,主要可以分为三大场景:普通AIDL、带有远程回调接口的AIDL和需要引用自定义Parcelable的AIDL,针对三类场景我分别写了个例子,由于篇幅问题这篇只放了普通AIDL的简单例子。
1、封装基本的父类和一些工具类
package   com.crazymo.remoteserver.base; import android.app.Service;import   android.content.Intent;import android.os.IBinder;import   android.support.annotation.Nullable; /** * Auther: Crazy.Mo on   2018/5/7 13:09 * Summary:所有远程服务必须继承的父类 */public abstract class AbstractService extends Service   { protected IBinder   mBinder;  @Nullable @Override public IBinder   onBind(Intent intent)   {  if(mBinder==null){mBinder=initBinder();  }  return   mBinder;//与客户端成功连接上的时候返回给客户端使用的对象 }  protected abstract IBinder   initBinder();}
为了实现在高版本匿名启动服务而做的兼容处理
package   com.crazymo.client; import android.content.ComponentName;import   android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import   android.content.pm.PackageManager;import   android.content.pm.ResolveInfo;import android.util.Log; import   java.util.List;  /** * Auther: Crazy.Mo on 2018/5/3   9:35 * Summary:这是为了处理高版本下仅仅通过action 匿名启动服务引发的异常 */public class AIDLUtil   {  /**  *  * @param pContext  *   @param pConnection 实现ServiceConnection接口的类  * @param action 要启动服务的action  *   @return  */ public static boolean bindAIDLService(Context   pContext, ServiceConnection pConnection, String action){  boolean   isBind=false;  if(pContext!=null && action!=null &&   pConnection!=null) {try { Intent intent = new   Intent(getExplicitIntent(pContext, new   Intent().setAction(action))); isBind = pContext.bindService(intent,   pConnection, Context.BIND_AUTO_CREATE);}catch (Exception   e){ Log.e("AIDL",e.getMessage());}  }  return   isBind; }  private static Intent getExplicitIntent(Context   context, Intent implicitIntent) {  // Retrieve all services that   can match the given intent  PackageManager pm = context.getPackageManager();  List  resolveInfo = pm.queryIntentServices(implicitIntent, 0);  // Make   sure only one match was found  if (resolveInfo == null ||   resolveInfo.size() != 1) {return null;  }  // Get   component info and create ComponentName  ResolveInfo serviceInfo =   resolveInfo.get(0);  String packageName =   serviceInfo.serviceInfo.packageName;  String className =   serviceInfo.serviceInfo.name;  ComponentName component = new   ComponentName(packageName, className);  // Create a new intent. Use   the old one for extras and such reuse  Intent explicitIntent = new   Intent(implicitIntent);  // Set the component to be explicit  explicitIntent.setComponent(component);  return   explicitIntent; }}
    2、 创建服务端的AIDL
    ?12345678// IRemoteApi.aidl package com.crazymo.remoteserver; // 在对应Module下 右键 New   ——>AIDL——>AIDL File即可自动完成创建AIDL模板文件,我们可以根据具体业务定义详细的接口方法interface IRemoteApi { //可以看到AIDL描述文件也是用interface   来声明的本质上也是一个特殊“接口”,所以也可以按照普通接口的语法来说明将要提供给其他客户端使用的方法,也可以有各种参数和返回值 String getRemoteStr();}
    3、实现服务端对应AIDL的Service
继承Service,重写onBind方法 继承对应的Stub类,实现AIDL中定义的方法的具体的逻辑 在onBind方法中返回我们自定义的Stub子类Binder对象
package   com.crazymo.remoteserver.service; import android.os.IBinder;import   android.os.RemoteException;import android.util.Log;import   com.crazymo.remoteserver.IRemoteApi;import com.crazymo.remoteserver.base.AbstractService; /** *   Auther: Crazy.Mo on 2018/5/7 13:04 * Summary:服务端实现AIDL 接口中方法要做的事,这里的类的名称、路径无特殊要求 */public class RemoteApiService extends AbstractService   {  private final static String   TAG="RemoteService";  @Override protected IBinder   initBinder() {  if(mBinder==null){mBinder=new RemoteBinder();  }  return   mBinder; }  /**  * 继承AIDL文件里的Stub封装IBinder对象,可以理解为AIDL接口的实现  */ private   final class RemoteBinder extends   IRemoteApi.Stub{   @Override  public String   getRemoteStr() throws RemoteException {//在这里实现AIDL里接口方法真正的逻辑Log.e(TAG,"【服务端】 线程"+Thread.currentThread().getName()+"服务端处理客户调用getRemoteStr请求并返回字符串:服务器:宝塔镇河妖");return   "宝塔镇河妖";  } }}
在清单中声明自定义的服务,指定相应的Action和进程
?123456     
    4、客户端调用服务端AIDL
无论是本地服务还是远程服务,本质上都是Service机制,按照匿名启动Service的步骤来就可以了。
    引入服务端的AIDL即把服务端AIDL全部文件复制到客户端的aidl目录下 声明远程服务对象实例(类型名为AIDL的名称) 声明ServiceConnection对象实例,最好通过继承ServiceConnection实现具体的子类的形式(不要通过匿名内部类的形式创建,因为取消绑定unbindService(ServiceConnection conn)也需要传入ServiceConnection) 通过上下文的bindService(@RequiresPermission   Intent service,@NonNull ServiceConnection conn, @BindServiceFlags int flags)匿名启动远程服务 在ServiceConnection的onServiceConnected完成远程服务对象实例的初始化 在ServiceConnection的onServiceDisconnected完成远程服务对象实例的销毁或者重连 通过远程服务对象实例调用远程接口
   
     package   com.crazymo.client; import android.app.Activity;import   android.content.ComponentName;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.widget.Toast;import com.crazymo.remoteserver.IRemoteApi;import   java.util.List; public class MainActivity extends Activity   { private final static String   TAG="RemoteService"; private final static String   ACTION_COMM="com.crazymo.aidl.comm"; private boolean   isUnbindRemoteApiConn=false;  private RemoteApiConn   mRemoteApiConn=new RemoteApiConn();//声明并创建ServiceConnection对象实例,只要是进程通信都需要实现,因为系统是在这个ServiceConnecttion类的相关方法通过回调返回Ibinder对象的  private   IRemoteApi mRemoteApi;//声明远程服务对象实例————>IRemote.aidl   @Override protected   void onCreate(Bundle savedInstanceState)   {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main); }  @Override protected   void onDestroy()   {  super.onDestroy();  disconnectRemoteApiComm(); }  //主动断开 private void   disconnectRemoteApiComm()   {  unbindService(mRemoteApiConn);  isUnbindRemoteApiConn=true; }  public   void connAIDL(View view)   {  connectAIDL(); }  private void connectAIDL() {  boolean   isBind;  isBind=   AIDLUtil.bindAIDLService(this,mRemoteApiConn,ACTION_COMM);  Log.e(TAG,"【客户端】线程"+Thread.currentThread().getName()+"绑定远程接口(成功true,失败false):    "+isBind);  if(!isBind){Toast.makeText(this,"连接远程服务发生异常",Toast.LENGTH_SHORT).show();  } }  public   void useAIDL(View view)   {  if(mRemoteApi==null){connectAIDL();  }  try   {if(mRemoteApi!=null) {//这里有必要判空下,因为假如你还未执行过连接的话,第一次就使用会去先连接这需要些时间,而且连接是异步不阻塞的,所以有可能造成空指针异常 Log.e(TAG, "【客户端】线程"+Thread.currentThread().getName()+"通过AIDL调用远程接口,客户端:小鸡炖蘑菇"); String   result=mRemoteApi.getRemoteStr(); Log.e(TAG, "【客户端】线程"+Thread.currentThread().getName()+"服务端响应请求返回:" +result   );}  } catch (RemoteException pE)   {pE.printStackTrace();  } }   if(mRemoteCallbackApi!=null){try   { Log.e(TAG,"【客户端】线程"+Thread.currentThread().getName()+"调用带有回调的远程接口,发送:今天天气怎么样"); mRemoteCallbackApi.speak("今天天气怎么样");} catch   (RemoteException pE)   { pE.printStackTrace();}  } } // private final   class RemoteApiConn implements   ServiceConnection{   @Override  public void   onServiceConnected(ComponentName name, IBinder service)   {mRemoteApi=IRemoteApi.Stub.asInterface(service);//初始化远端服务对象实例  }   @Override  public void   onServiceDisconnected(ComponentName name)   {if(isUnbindRemoteApiConn){ mRemoteApi=null;//主动断开直接置为null}else{ while(mRemoteApi==null)   {  connectAIDL();//做重连 }}  } }}
    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注移动开发之Android频道!

本文由 @凌雪 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程