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

Android进程间通信(1)-AIDL

程序员文章站 2024-03-23 23:29:10
...

为什么学习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文件就写在这个包里面就可以了。
Android进程间通信(1)-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>

运行效果图如下:
Android进程间通信(1)-AIDL

当点击绑定服务按钮的时候,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