-- 做者 谢恩铭 转载请注明出处javascript
为使应用程序之间可以彼此通讯,Android提供了IPC (Inter Process Communication,进程间通讯)的一种独特实现: AIDL (Android Interface Definition Language, Android接口定义语言)。html
网上有很多关于AIDL的文章,写得都很不错。不过例子构造大多略微复杂: 创建两个Android项目,一个是client(客户端),一个是server(服务端,提供service(服务))。java
这篇文章将首先介绍AIDL的原理,再经过一个Android项目来介绍AIDL用法。服务端和客户端包含在这同一个项目中,原理和分别在两个项目中是同样的,不太轻省许多。android
源码在个人Github上,文末有放出。git
在Android中,默认每一个应用(application)执行在它本身的进程中,没法直接调用到其余应用的资源,这也符合“沙箱”(SandBox)的理念。所谓沙箱原理,通常来讲用在移动电话业务中,简单地说旨在部分地或所有地隔离应用程序。程序员
Android沙箱技术:
Android“沙箱”的本质是为了实现不一样应用程序和进程之间的互相隔离,即在默认状况 下,应用程序没有权限访问系统资源或其它应用程序的资源。
每一个APP和系统进程都被分配惟一而且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。
每一个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。
运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,所以Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序若是想要访问系统资源或者其它应用程序的资源必须在本身的manifest文件中进行声明权限或者共享uid。
本段关于沙箱的解释转载自:Android的权限机制之—— “沙箱”机制sharedUserId和签名github
所以,在Android中,当一个应用被执行时,有一些操做是被限制的,好比访问内存,访问传感器,等等。这样作能够最大化地保护系统,省得应用程序“随心所欲”。web
那咱们有时须要在应用间交互,怎么办呢?因而,Android须要实现IPC协议。编程
关于IPC协议,能够参看下面摘自维基百科的内容:浏览器
进程间通讯(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。
进程是计算机系统分配资源的最小单位(严格说来是线程)。每一个进程都有本身的一部分独立的系统资源,彼此是隔离的。
为了能使不一样的进程互相访问资源并进行协调工做,才有了进程间通讯。举一个典型的例子,使用进程间通讯的两个应用能够被分类为客户端和服务器(主从式架构),客户端进程请求数据,服务端回复客户端的数据请求。有一些应用自己既是服务器又是客户端,这在分布式计算中,时常能够见到。这些进程能够运行在同一计算机上或网络链接的不一样计算机上。
进程间通讯技术包括消息传递、同步、共享内存和远程过程调用(Remote Procedure Call,缩写是RPC)。IPC是一种标准的Unix通讯机制。使用IPC 的理由:
- 信息共享:Web服务器,经过网页浏览器使用进程间通讯来共享web文件(网页等)和多媒体。
- 加速:维基百科使用经过进程间通讯进行交流的多服务器来知足用户的请求。
- 模块化。
- 私有权分离。
与直接共享内存地址空间的多线程编程相比,IPC的缺点:
- 采用了某种形式的内核开销,下降了性能;
- 几乎大部分IPC都不是程序设计的天然扩展,每每会大大地增长程序的复杂度。
对于进程和线程的联系和区别,能够参看阮一峰老师的这篇图文:进程与线程的一个简单解释,很是形象生动。
关于Android中的进程和线程,能够参看官方开发文档:
developer.android.com/guide/compo…
(国内的朋友也能够去这里:developer.android.google.cn/guide/compo… )
咱们知道Android中要实现IPC,有好多种方式:
然而,若是咱们要在Android中本身来实现IPC这个协议,仍是有点复杂的,主要由于须要实现数据管理系统(在进程或线程间传递数据)。为了暂时减缓这个“会呼吸的痛”,Android为咱们实现了一种定制的IPC,也就是梁静茹,oh,sorry,是AIDL。
不要把AIDL和JNI及NDK混淆起来,这几个的功用是这样的:
AIDL:是Android中IPC(进程间通讯)的一种方式, 由于Android中不一样应用通常是位于不一样进程中的,而即便同一个应用中的组件(component。参看Android四大组件:Activity,Service,ContentProvider,BroadcastReceiver)也能够位于不一样进程(经过在AndroidManifest.xml中为组件设置android:process属性来实现)。例如,同一个应用中,若是Activity和Service二者处于不一样进程,但Activity 须要给Service传递一些信息,就能够用到AIDL这种机制。
JNI:Java Native Interface的缩写,表示“Java原生接口”。为了方便Java调用Native(原生)代码(好比C和C++,等等)所封装的一层接口。JNI是Java语言的东西,并不专属于Android。
NDK:Native Development Kit的缩写,表示“原生开发工具集”。NDK是Google为Android开发的工具集,专属于Android。利用NDK,咱们能够在Android中更加方便地经过JNI来调用原生代码(好比C和C++,等等)。NDK还提供了交叉编译器,咱们只须要简单修改.mk文件就能够生成指定CPU平台的动态库。NDK还有其余一些优点。
在Android官方开发文档中有这么一段话,是关于IPC的:
Android offers a mechanism for interprocess communication (IPC) using remote procedure calls (RPCs), in which a method is called by an activity or other application component, but executed remotely (in another process), with any result returned back to the caller. This entails decomposing a method call and its data to a level the operating system can understand, transmitting it from the local process and address space to the remote process and address space, then reassembling and reenacting the call there. Return values are then transmitted in the opposite direction. Android provides all the code to perform these IPC transactions, so you can focus on defining and implementing the RPC programming interface.
To perform IPC, your application must bind to a service, using bindService(). For more information, see the Services developer guide.
翻译以下:
Android利用远程过程调用(Remote Procedure Call,简称RPC)提供了一种进程间通讯(Inter-Process Communication,简称IPC)机制,经过这种机制,被Activity或其余应用程序组件调用的方法将(在其余进程中)被远程执行,而全部的结果将被返回给调用者。这就要求把方法调用及其数据分解到操做系统能够理解的程度,并将其从本地的进程和地址空间传输至远程的进程和地址空间,而后在远程进程中从新组装并执行这个调用。执行后的返回值将被反向传输回来。Android提供了执行IPC事务所需的所有代码,所以只要把注意力放在定义和实现RPC编程接口上便可。要执行IPC,应用程序必须用bindService()绑定到服务上。详情请参阅服务Services开发指南。
AIDL是IPC的一个轻量级实现,用到了Java开发者很熟悉的语法。Android也提供了一个工具,能够自动建立Stub。
问:"Stub又是什么呢?"
答:"Stub在英语中是“树桩”的意思,这个stub的概念并非Android专有的,其余编程开发中也会用到,根据维基百科的解释:
Stub(桩)指用来替换一部分功能的程序段。桩程序能够用来模拟已有程序的行为(好比一个远端机器的过程)或是对将要开发的代码的一种临时替代。所以,打桩技术在程序移植、分布式计算、通用软件开发和测试中用处很大。
所以,简单的说,Android中的Stub是一个类,实现了远程服务的接口,以便你能使用它,就好像此服务是在本地同样。比如在本地打了一个远程服务的“桩”,你就能够用来造房子什么的。"
当咱们要在应用间用AIDL来通讯时,咱们须要按如下几步走:
AIDL的语法很相似Java的接口(Interface),只须要定义方法的签名。
AIDL支持的数据类型与Java接口支持的数据类型有些不一样:
为了更好地展现AIDL的用法,咱们来看一个很简单的例子: 两数相加。
事不宜迟,咱们就用Android Studio建立一个Android项目。
如下是项目的基本信息(不必定要同样):
点击Next(下一步):
默认配置便可,点击Next(下一步):
点击Next(下一步):
点击Finish(完成),Android Studio就会开始帮你建立新项目。稍等片刻,便可看到以下图所示的项目:
此时的项目视图是默认的Android。
鼠标左键选中 HelloSumAIDL/app/src/main/java这个路径,以下图所示:
点击鼠标右键,新建一个AIDL文件(依次选择New->AIDL->AIDL File),取名为 IAdditionService。
点击Finish。
Android Studio就会为你新建一个IAdditionService.aidl文件,位于新建的路径 HelloSumAIDL/app/src/main/aidl 中,它的包名也是 com.android.hellosumaidl,由于包名在咱们建立项目时已经定了,能够在AndroidManifest.xml文件中能够看到
在新建的这个IAdditionService.aidl文件中将已有代码替换为以下代码:
package com.android.hellosumaidl;
// Interface declaration (接口声明)
interface IAdditionService {
// You can pass the value of in, out or inout
// The primitive types (int, boolean, etc) are only passed by in
int add(in int value1, in int value2);
}复制代码
add是英语“加”的动词。addition是“加”的名词。
AIDL也有一些格式规范,主要是in和out关键字,in表明传入的参数,out表明输出的参数,inout表明传入和输出的参数。Java语言内置的类型(好比int,boolean,等等)只能经过in来传入。
一旦文件被保存,Android Studio会自动在 HelloSumAIDL/app/build/generated/source/aidl/debug/com/android/hellosumaidl 这个路径(若是你的Favorites是release,那么debug会是release)里自动生成对应的IAdditionService.java这个文件。
为了能看到app/build/generated/中的文件,须要把项目视图从默认的Android改选为Project Files。
而后,你就能找到IAdditionService.java这个文件了,以下图所示:
在这个文件里,咱们能够看到add方法也被自动添加了:
由于IAdditionService.java这个文件是自动生成的,因此无需改动。这个文件里就包含了Stub,能够看到就是
public static abstract class Stub extends android.os.Binder implements com.android.hellosumaidl.IAdditionService复制代码
那一行。
咱们接下来要为咱们的远程服务实现这个Stub。
首先咱们来理清一下思路,如今咱们的项目有两个主要的文件:
HelloSumAIDL/app/src/main/java/com/android/hellosumaidl/HelloSumAidlActivity.java :这个HelloSumAidlActivity.java是咱们的客户端(client)。
HelloSumAIDL/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl :这个是AIDL。客户端经过AIDL实现与服务端的通讯。
注意:使用AIDL进行客户端和服务端的通讯有一个条件须要知足,那就是服务器端的各个AIDL文件(由于aidl目录下也许不止一个文件,咱们项目中只建立了一个而已)需要被拷贝到客户端的相同包名下,否则会不成功。例如:
- HelloSumAIDLServer/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl :假如HelloSumAIDLServer是一个表示AIDL服务端的Android项目。
- HelloSumAIDLClient/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl :假如HelloSumAIDLClient是一个表示AIDL客户端的Android项目。
那么,HelloSumAIDLClient/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl 就要和 HelloSumAIDLServer/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl同样。
咱们这篇文章中,由于客户端和服务端是在同一项目中,所以存在一份AIDL文件就够了,就是 HelloSumAIDL/app/src/main/aidl/com/android/hellosumaidl/IAdditionService.aidl。
咱们尚未写远程服务端的代码,所以咱们来实现之:
在HelloSumAIDL/app/src/main/java/com/android/hellosumaidl 这个路径中新建一个Service,取名叫AdditionService.java。这个就是咱们的服务端了。
为了实现咱们的服务,咱们须要让这个类中的onBind方法返回一个IBinder类的对象。这个IBinder类的对象就表明了远程服务的实现。
咱们要用到自动生成的子类IAdditionService.Stub。在其中,咱们也必须实现咱们以前在AIDL文件中定义的add()函数。下面是咱们远程服务的代码:
package com.android.hellosumaidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
/* * This class exposes the service to client * 服务端,将服务(service)"暴露"给客户端(client) */
public class AdditionService extends Service {
public AdditionService() {
}
@Override
public IBinder onBind(Intent intent) {
return new IAdditionService.Stub() {
/* * Implement com.android.hellosumaidl.IAdditionService.add(int, int) * 实现了add方法 */
@Override
public int add(int value1, int value2) throws RemoteException {
return value1 + value2;
}
};
}
}复制代码
AdditionService.java(服务端)和HelloSumAidlActivity.java(客户端)被放在同一个路径下:
一旦实现了服务中的onBind方法,咱们就能够把客户端程序(在咱们的项目里是HelloSumAidlActivity.java)与服务链接起来了。
为了创建这样的一个连接,咱们须要实现ServiceConnection类。
咱们在HelloSumAidlActivity.java中建立一个内部类 AdditionServiceConnection,这个类继承ServiceConnection类,而且重写了它的两个方法:onServiceConnected和onServiceDisconnected。
下面给出内部类的代码:
/* * This inner class is used to connect to the service * 这个内部类用于链接到服务(service) */
class AdditionServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder boundService) {
service = IAdditionService.Stub.asInterface(boundService);
Toast.makeText(HelloSumAidlActivity.this, "Service connected", Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
service = null;
Toast.makeText(HelloSumAidlActivity.this, "Service disconnected", Toast.LENGTH_LONG).show();
}
}复制代码
为了完成咱们的测试项目,咱们须要首先改写activity_hello_sum_aidl.xml(主界面的布局文件)和string.xml (字符串定义文件):
布局文件 activity_hello_sum_aidl.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"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textSize="22sp" />
<EditText
android:id="@+id/value1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/hint1" >
</EditText>
<TextView
android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/plus"
android:textSize="36sp" />
<EditText
android:id="@+id/value2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/hint2" >
</EditText>
<Button
android:id="@+id/buttonCalc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/equal" >
</Button>
<TextView
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/result"
android:textSize="36sp" />
</LinearLayout>复制代码
string.xml
<resources>
<string name="app_name">HelloSumAIDL</string>
<string name="hello">Hello Sum AIDL</string>
<string name="result">Result</string>
<string name="plus">+</string>
<string name="equal">=</string>
<string name="hint1">Value 1</string>
<string name="hint2">Value 2</string>
</resources>复制代码
最后,咱们的HelloSumAidlActivity.java以下:
package com.android.hellosumaidl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class HelloSumAidlActivity extends AppCompatActivity {
IAdditionService service;
AdditionServiceConnection connection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello_sum_aidl);
initService();
Button buttonCalc = (Button)findViewById(R.id.buttonCalc);
buttonCalc.setOnClickListener(new View.OnClickListener() {
EditText value1 = (EditText)findViewById(R.id.value1);
EditText value2= (EditText)findViewById(R.id.value2);
TextView result = (TextView)findViewById(R.id.result);
@Override
public void onClick(View v) {
int v1, v2, res = -1;
v1 = Integer.parseInt(value1.getText().toString());
v2 = Integer.parseInt(value2.getText().toString());
try {
res = service.add(v1, v2);
} catch (RemoteException e) {
e.printStackTrace();
}
result.setText(Integer.valueOf(res).toString());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseService();
}
/* * This inner class is used to connect to the service * 这个内部类用于链接到服务(service) */
class AdditionServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder boundService) {
service = IAdditionService.Stub.asInterface(boundService);
Toast.makeText(HelloSumAidlActivity.this, "Service connected", Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
service = null;
Toast.makeText(HelloSumAidlActivity.this, "Service disconnected", Toast.LENGTH_LONG).show();
}
}
/* * This method connects the Activity to the service * 这个方法使Activity(客户端)链接到服务(service) */
private void initService() {
connection = new AdditionServiceConnection();
Intent i = new Intent();
i.setClassName("com.android.hellosumaidl", com.android.hellosumaidl.AdditionService.class.getName());
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
/* * This method disconnects the Activity from the service * 这个方法使Activity(客户端)从服务(service)断开 */
private void releaseService() {
unbindService(connection);
connection = null;
}
}复制代码
将此项目运行起来,获得的两个截图以下:
光是一个AIDL,就涉及到不少Android知识点。因此说:Android是一个“庞然大物”,要学习的东西不少。“少年,路漫漫其修远兮”,要成为Android大牛必须付出努力!
能够看到AIDL的原理仍是著名的客户端和服务端原理。其底层实现用到了Android的Binder。关于Binder的实现原理,能够去看《Android开发艺术探索》一书。
网上通常的AIDL实例是将服务端(Server)和客户端(Client)分开放到两个Android项目中,咱们的这个项目,将服务端和客户端放在同一个项目中,原理是相似的。
以上项目的源码我放到本身的Github上了,欢迎查看、fork、下载。github.com/frogoscar/H…
欢迎留言补充、指正,谢谢。
人世间,
万千情感皆有温度,
千万代码似有性格。
这里有原创教程,IT丛林......
和你一块儿探索程序人生。
微信公众号「程序员联盟」ProgrammerLeague
我是谢恩铭,在巴黎奋斗的嵌入式软件工程师。
我的简介热爱生活,喜欢游泳,略懂烹饪。人生格言:“向着标杆直跑”