Android进程间通信(1)-AIDL
为什么学习Binder?接触过Android的同学,不管是刚接触还是开发很久的都或多或少的听说过进程间通信,一提到进程间通信又不得不说Binder机制,为什么Binder机制这么重要呢?因为再Android系统中,Binder是连接不同进程通信的信使,学习好Binder机制也是走向高级Android开发的必经之路。
Android Binder框架分为服务器接口、Binder驱动、以及客户端接口。简单想一下,需要提供一个全局服务,那么提供服务的即是服务器接口,调用服务端逻辑的即为客户端接口,由于Android是基于Linux的,每个应用都是运行在自己独立的进程内,不能共享数据,要想实现客户端和服务端的通信,就必须提供一个中间层来实现数据的分发,这个中间层也就是我们所说的Binder驱动。
- 服务器端接口:实际上是Binder类的对象,该对象一旦创建,内部则会启动一个隐藏线程,会接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务器端代码。
- Binder驱动:该对象也为Binder类的实例,客户端通过该对象访问远程服务。
- 客户端接口:获得Binder驱动,调用其transact()发送消息至服务器
通过上面的描叙,大家可能还不明白,不过没关系,下面我将用例子来说明一切。
AIDL
我们刚开始学习Android的时候或多或少的接触了aidl通信,aidl也是实现进程通信的主要手段,其实际上是对Binder进一步的封装,方便了我们开发者使用。
1,服务端
新建一个项目,项目名叫AndroidServer,包名为com.wms.github.aidl.server。由于是服务端,不需要显示界面,所以不必提供Activity。这里我使用的工具是Android Studio(AS),首先我在AS中创建一个aidl文件,在AS中创建很简单,项目->New->Aidl->Aidl文件,这样AS就会自动帮我们创建一个aidl的目录,以后所有的aidl文件就写在这个包里面就可以了。
我们服务端要实现一个简单的接收客户端传递的字符串,并对字符串大写处理,然后返回给客户端。首先创建String2UpperCase.aidl,代码如下:
package com.wms.github.aidl.server;
interface String2UpperCase {
//将字符串str转成大写然后返回
String toUpperCase(String str);
}
编写好String2UpperCase.aidl后编译一下,这时候AS将会给我们自动生成一个String2UpperCase.java文件(注意,这里必须手动编译一下,否则不会自动生成,和Eclipse不一样)
Aidl文件我们已经简单的完成那个了,接下来我们开始实现服务端真正的处理逻辑,这里要想实现通信,服务端必须是一个Service,所以新建一个MyService.java,代码如下:
package com.wms.github.aidl.server;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
/**
* Created by 王梦思 on 2017/5/25.
*/
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind...");
//这里不能返回null,必须要返回我们创建的Binder对象
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind...");
return super.onUnbind(intent);
}
@Override
public void onStart(Intent intent, int startId) {
Log.e(TAG, "onStart...");
super.onStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy...");
super.onDestroy();
}
/**
* 这里一定要继承自String2UpperCase.Stub
*/
class MyBinder extends String2UpperCase.Stub {
@Override
public String toUpperCase(String str) throws RemoteException {
Log.e(TAG, "接收到客户端传递的字符串 str = " + str);
return str.toUpperCase();
}
}
}
在MyService.java中我们定义了一个MyBinder对象继承自String2UpperCase.Stub,(很多人有疑问这个类是哪里来的呢?其实就是我们上面创建的那个Aidl文件自动生成的,这里不用担心,后面我会详细介绍。),然后再onBind方法里面返回我们刚刚创建的MyBinder对象,这样服务端就完成了。对了,不要忘记啊了在AndroidManifest.xml中添加下面代码注册Service:
<service
android:name="com.wms.github.aidl.server.MyService"
android:exported="true">
<intent-filter>
<action android:name="com.wms.github.aidl.server.MyService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
注意:Android5.0以后由于android增强了安全性,要想在其他应用开启另外一个应用的Service,必须要在注册的时候增加一句android:exported=”true”
这里我给MyService.java指定了一个IntentFilter,目的是为了方便我们的客户端来绑定服务。没有界面,只有一个Service,当客户端绑定服务的时候,在后台悄悄运行,不动声色。哈哈。到此我们服务端就完成了,下面我们来写客户端程序。
2,客户端
新建一个项目,项目名AndroidClient,包名为com.wms.github.aidl.client,首先在client项目里面新建一个和AndroidService中一模一样的String2UpperCase.aidl文件,包名也要相同,可以把服务端的拷贝过来。然后提供一个简单的Activity,里面只有一个EditText和3个Button,一个是绑定服务,一个是解除绑定服务,点击转换Button后将EditText中的文本传输到服务端,返回大写的字符串,然后重新显示到EditText中。下面是MainActivity.java的代码:
package com.wms.github.aidl.client;
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.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import com.wms.github.aidl.server.String2UpperCase;
/**
* Created by 王梦思 on 2017/5/25.
*/
public class MainActivity extends AppCompatActivity {
private EditText mEditText;
private String2UpperCase mString2UpperCase;
private ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当绑定成功后调用
mString2UpperCase = String2UpperCase.Stub.asInterface(service);
Log.e("MainActivity","onServiceConnected...");
}
@Override
public void onServiceDisconnected(ComponentName name) {
mString2UpperCase = null;
Log.e("MainActivity","onServiceDisconnected...");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText = (EditText) findViewById(R.id.id_edittext);
findViewById(R.id.bind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService();
}
});
findViewById(R.id.unbind).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
unBindService();
}
});
findViewById(R.id.invokeServer).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
invokeServer();
}
});
}
/**
* 绑定服务
*/
public void bindService() {
Intent intent = new Intent();
intent.setAction("com.wms.github.aidl.server.MyService");
//Android 5.0以上必须要加这句代码,不然报错
intent.setPackage("com.wms.github.aidl.server");
bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
}
public void unBindService() {
unbindService(mServiceConn);
}
public void invokeServer() {
String inputStr = mEditText.getText().toString().trim();
try {
String result = mString2UpperCase.toUpperCase(inputStr);
//重新显示到Edittext
mEditText.setText(result);
} catch (RemoteException e) {
//这里会抛出远程异常
e.printStackTrace();
}
}
}
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:layout_margin="10dp"
android:orientation="vertical">
<EditText
android:id="@+id/id_edittext"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#eee"
android:hint="请输入你要转换的字符串"
android:textColor="#000"
android:textSize="16sp"/>
<Button
android:id="@+id/bind"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="绑定服务"/>
<Button
android:id="@+id/unbind"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="unBindService"
android:text="解绑服务"/>
<Button
android:id="@+id/invokeServer"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="invokeServer"
android:text="调用服务端转换"/>
</LinearLayout>
运行效果图如下:
当点击绑定服务按钮的时候,logcat日志如下:
05-25 12:57:32.538 27037-27037/com.wms.github.aidl.server E/MyService: onCreate...
05-25 12:57:32.538 27037-27037/com.wms.github.aidl.server E/MyService: onBind...
05-25 12:57:32.541 27011-27011/com.wms.github.aidl.client E/MainActivity: onServiceConnected...
首先会执行MyService.java中的onCreate方法,然后执行onBind方法,服务绑定成功后会执行到客户端的MainActivity中的onServiceConnected中,当这一步执行完成后,也就说明客户端和服务端建立了连接,可以进行进程间通信了。
当点击解绑服务按钮时候,logcat日志如下:
05-25 13:11:07.711 31121-31121/com.wms.github.aidl.server E/MyService: onUnbind...
05-25 13:11:07.712 31121-31121/com.wms.github.aidl.server E/MyService: onDestroy...
经过上面的代码,可以看出完成进程间通信其实很简单,需要一个服务端,一个客户端,这样就能跨进程之间的传输了。可是问题来了,很多人肯定有疑问,为什么这样操作可以进行进程间通信,下面我将从源码的角度出发,来解释说明一下。
分析下AIDL生成的代码(服务端)
服务端完成客户端的处理逻辑代码主要是:
class MyBinder extends String2UpperCase.Stub {
@Override
public String toUpperCase(String str) throws RemoteException {
Log.e(TAG, "接收到客户端传递的字符串 str = " + str);
return str.toUpperCase();
}
}
MyBinder继承自String2UpperCase.Stub,那这个Stub又是什么鬼东西呢?我们查看下源代码(AS自动生成)。一下是String2UpperCase.aidl生成的java文件代码,如下:
package com.wms.github.aidl.server;
public interface String2UpperCase extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wms.github.aidl.server.String2UpperCase {
private static final java.lang.String DESCRIPTOR = "com.wms.github.aidl.server.String2UpperCase";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.wms.github.aidl.server.String2UpperCase interface,
* generating a proxy if needed.
*/
public static com.wms.github.aidl.server.String2UpperCase asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.wms.github.aidl.server.String2UpperCase))) {
return ((com.wms.github.aidl.server.String2UpperCase) iin);
}
return new com.wms.github.aidl.server.String2UpperCase.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_toUpperCase: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _result = this.toUpperCase(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.wms.github.aidl.server.String2UpperCase {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
//将字符串str转成大写然后返回
@Override
public java.lang.String toUpperCase(java.lang.String str) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(str);
mRemote.transact(Stub.TRANSACTION_toUpperCase, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_toUpperCase = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
//将字符串str转成大写然后返回
public java.lang.String toUpperCase(java.lang.String str) throws android.os.RemoteException;
}
简单分析下上面的代码,Stub是String2UpperCase的静态抽象内部类,它是继承自Binder的,里面有几个重要的方法,也是进程通信的核心方法。其中包含:asInterface(),asBinder(),onTransact()。asInterface这个方法我们在客户端MainActivity中已经用到了,目的是为了获取到String2UpperCase对象。asBinder很简单就返回了一个self,没啥可说的,onTransact方法可是重中之重,在这个方法里面有四个参数,分别是code , data ,replay , flags
- code 是一个整形的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法,如果服务端方法很多,该code是用来区分调用哪个方法的
- data客户端传递过来的参数
- replay服务器返回回去的值
- flags标明是否有返回值,0为有(双向),1为没有(单向)
接下来我们分析下onTransact中的代码,首先执行的是
data.enforceInterface(DESCRIPTOR);
这句代码的目的是与客户端的writeInterfaceToken对用,标识远程服务的名称,这样才能建立连接
接下来就是数据的传输了
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _result = this.toUpperCase(_arg0);
reply.writeNoException();
reply.writeString(_result);
这里readString()是读取客户端传递过来的值,然后调用toUpperCase方法执行服务端逻辑,这个toUpperCase在String2UpperCase.java中没有实现,但是在MyService.java中MyBinder是继承自这个Stub类的,所以当调用this.toUpperCase(_arg0)后就会执行到MyBinder里面的toUpperCase方法,最后调用reply.writeString(_result)把服务端返回的值返回到客户端。
这里读取客户端和写入客户端的值是不同数据类型有不同的read和write方法,比如如果客户端传递过来的是int类型,则为readInt()方法,写入客户端就是writeInt(10),以此类推。具体请查看android.os.Parcel这个类的源码。
分析下AIDL生成的代码(客户端)
当客户端点击绑定服务的时候,如果服务开启成功然后就会回调到如下代码:
private ServiceConnection mServiceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当绑定成功后调用
mString2UpperCase = String2UpperCase.Stub.asInterface(service);
Log.e("MainActivity","onServiceConnected...");
}
@Override
public void onServiceDisconnected(ComponentName name) {
mString2UpperCase = null;
Log.e("MainActivity","onServiceDisconnected...");
}
};
连接成功,则调用onServiceConnected,在这里传递进来了一个binder对象,这个binder驱动其实就是我们文章开头所说的binder驱动,然后我么调用了String2UpperCase.Stub.asInterface()方法,生成了我们的String2UpperCase对象,其实正真生成的是com.wms.github.aidl.server.String2UpperCase.Stub.Proxy的一个代理对象。
客户端传输数据给服务端主要代码如下:
public java.lang.String toUpperCase(java.lang.String str) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(str);
mRemote.transact(Stub.TRANSACTION_toUpperCase, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
和服务端很像,首先是
_data.writeInterfaceToken(DESCRIPTOR);
目的和服务端的下面代码是对应的,这个DESCRIPTOR是一样的。
data.enforceInterface(DESCRIPTOR);
然后就是调用
_data.writeString(str);
mRemote.transact(Stub.TRANSACTION_toUpperCase, _data, _reply, 0);
客户端能够发送数据的关键就在调用了transact方法,这个方法吧客户端的数据传递给了服务端,那在这个方法里面做了什么事情呢?我们看下这个transact方法内部实现:
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
其实这个方法里面很简单,关键一点就是在这个方法里面调用了onTransact()方法,调用了这个方法,其实也就是回调到了服务端的onTransact中,这样服务端就能接收到客户端传递过来的数据了,也就建立了进程间的通信,在这中间Binder驱动可以说是起到了非常大的作用。
经过上面简单的分析,我相信大家已经明白了Aidl实现进程通信的大致原理了。那是不是我们进程间通信只能通过aidl来实现呢?我相信经过上面的分析,大家应该有了一个大致的了解了,如果想知道更多的实现进程间通信的方法,请听下回分解。哈哈。
代码传送门 : http:github.com/wms1993/blog_aidl_demo
推荐阅读
-
Android进程间通信(1)-AIDL
-
这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用
-
Linux中进程间通信——消息队列
-
Linux中进程间通信之管道
-
Linux编程基础之进程间通信之四:共享内存
-
Web Server 与 子进程间同步(通信) WebJMSJavaActiveMQ应用服务器
-
进程间通信- - -管道
-
进程间通信方式 博客分类: 计算机与 Internet SocketLinuxJMSWindowsWeb
-
进程间通信方式 博客分类: 计算机与 Internet SocketLinuxJMSWindowsWeb
-
Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路