Binder or AIDL的最简单实践

1.前言:

在Android开发中多进程的合理使用+进程间通信的IPC是一个比较难的点。特别是Android特有的Binder机制,非常复杂,对于应用层开发的初级开发工程师强求深入理解Binder机制是不现实的。
其实Android 的开发人员已经为我们考虑到,提供了方便我们使用Binder的方法。
这就是AIDL: Android Interface definition language Android接口定义语言
我们按照要求简单书写一个AIDL文件,当前的IDE,比如Android Sudio 会根据这个自动生成一个用于Binder通信的Java类
通过这个类,我们就不需要关心如何通过Java代码实现Binder通信,我们只要会使用编写的AIDL文件自动生成的类即可
很难理解?

原理是这样的:如果我们要通过Java写Binder的IPC通信代码,对于菜鸟的我们很难写出来,又因为其实Java代码进行Binder通信过程都是类似的,所以谷歌就创造了AIDL文件,我们写好AIDL文件,就帮我们生成对应的Java代码,省去我们理解Binder的抽象过程(电脑帮我们写代码,哈哈)

2.从一个最简单的AIDL实例出发:

在应用层开发中,使用到AIDL的常见场景通常如下:


我们有一个Service,举例:下载文件的DownloadService,为了:

  1. 不让这个DownloadService占用App的UI进程内存资源
  2. DownService奔溃不影响App的UI进程
    我们需要在AndroidManifest里面注册DownloadService的时候通过 android:process=":remote"指定这个Service运行在一个
    独立的私有进程(进程名为应用包名:remote)中(请百度 android:process=""的用法)
<service
         android:name=".DownloadService"
         android:process=":remote" />
  1. 因为DownloadService运行在一个独立进程,App默认的UI进程需要和它通信就是IPC,具体来说就是使用Binder

3.下面一步步演示具体的实践步骤:

我们希望有一个MainActivity代表当前的App的进程,DownloadService运行在另外的一个进程
然后:
MainActivity可以通过bindService 绑定DownloadService
然后Binder实现IPC(进程间通信)调用DownloadService的两个功能:

public List<DownloadTask> getDownloadTasks();//获取当前所有的下载任务
public void addDownloadTask(DownloadTask task)//新增一个下载任务


3.1新建两个组件:MainActivity和DownloadService

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}

DownloadService.java

public class DownloadService extends Service {

    private static final String TAG = "DownloadService";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;//注意:这里我们后面需要返回一个IBinder
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lijian.binderdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".DownloadService"
            android:process=":remote" />

    </application>

</manifest>

3.2创建代表下载任务的JavaBean DownloadTask

/**
* 下载任务的JavaBean
* 为了Binder传输,必须实现Parcelable序列化
* Created by lijian on 2017/3/23.
*/

public class DownloadTask implements Parcelable {
    public int taskId;
    public String fileName;
    public String downloadUrl;

    public DownloadTask(int taksId, String fileName, String downloadUrl) {
        this.taskId = taksId;
        this.fileName = fileName;
        this.downloadUrl = downloadUrl;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.taskId);
        dest.writeString(this.fileName);
        dest.writeString(this.downloadUrl);
    }

    protected DownloadTask(Parcel in) {
        this.taskId = in.readInt();
        this.fileName = in.readString();
        this.downloadUrl = in.readString();
    }

    public static final Parcelable.Creator<DownloadTask> CREATOR = new Parcelable.Creator<DownloadTask>() {
        @Override
        public DownloadTask createFromParcel(Parcel source) {
            return new DownloadTask(source);
        }

        @Override
        public DownloadTask[] newArray(int size) {
            return new DownloadTask[size];
        }
    };

    @Override
    public String toString() {
        return "DownloadTask{" +
                "taskId=" + taskId +
                ", fileName='" + fileName + '\'' +
                ", downloadUrl='" + downloadUrl + '\'' +
                "}\n";
    }
}

观察DownloadTask代码,可以看到DownloadTask实现了序列化接口,
因为DownloadTask是需要IPC,在两个进程之间传输的,经过
对象-》序列化-》反序列化-》重新生成一个对象的过程
注意:由于这个DownloadTask需要在AIDL中使用,所以我们需要在AIDL声明它:

也就是DownloadTask.aidl

// DownloadTask.aidl
package com.lijian.binderdemo;//AIDL需要声明包名

// Declare any non-default types here with import statements
parcelable DownloadTask;

3.3创建IDownload.aidl

aidl名称是Android interface defined language,顾名思义,aidl是用于定义接口的,然后,编译器自动帮助我们生成用于Binder IPC的代码

// IDownload.aidl
package com.lijian.binderdemo;
// Declare any non-default types here with import statements
import com.lijian.binderdemo.DownloadTask;
interface IDownload {
    List<DownloadTask> getTasks();
    void addTask(in DownloadTask task);//使用in 表明这是一个输入的变量
}

有代码可以看到我们定义了一个接口 IDownload
它声明了两个方法

  • List getTasks();

  • void addTask(in DownloadTask task);
    build一下,奇迹发生了:

    这里生成了一个IDownload的Java接口,它是根据我们编写的IDownload.aidl自动生成的,它的作用是方便Binder进行IPC通信

/*
 * This file is auto-generated.  DO NOT MODIFY.
* Original file: G:\\Android_demo\\BinderDemo\\app\\src\\main\\aidl\\com\\lijian\\binderdemo\\IDownload.aidl
*/
package com.lijian.binderdemo;
public interface IDownload extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.lijian.binderdemo.IDownload
{
private static final java.lang.String DESCRIPTOR = "com.lijian.binderdemo.IDownload";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.lijian.binderdemo.IDownload interface,
* generating a proxy if needed.
*/
public static com.lijian.binderdemo.IDownload asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lijian.binderdemo.IDownload))) {
return ((com.lijian.binderdemo.IDownload)iin);
}
return new com.lijian.binderdemo.IDownload.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_getTasks:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.lijian.binderdemo.DownloadTask> _result = this.getTasks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addTask:
{
data.enforceInterface(DESCRIPTOR);
com.lijian.binderdemo.DownloadTask _arg0;
if ((0!=data.readInt())) {
_arg0 = com.lijian.binderdemo.DownloadTask.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addTask(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.lijian.binderdemo.IDownload
{
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;
}
@Override public java.util.List<com.lijian.binderdemo.DownloadTask> getTasks() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.lijian.binderdemo.DownloadTask> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getTasks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.lijian.binderdemo.DownloadTask.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addTask(com.lijian.binderdemo.DownloadTask task) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((task!=null)) {
_data.writeInt(1);
task.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addTask, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getTasks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.lijian.binderdemo.DownloadTask> getTasks() throws android.os.RemoteException;
public void addTask(com.lijian.binderdemo.DownloadTask task) throws android.os.RemoteException;
}

代码看似很困惑,其实不然,层次还是很分明的,我们一层一层剥离代码分析

3.3.1 IDownload Interface

public interface IDownload extends android.os.IInterface{
    //省略代码
    public java.util.List<com.lijian.binderdemo.DownloadTask> getTasks() throws android.os.RemoteException;
    public void addTask(com.lijian.binderdemo.DownloadTask task) throws android.os.RemoteException;
}

根据我们定义的IDownload.aidl文件,里面声明了两个方法,所以生成了一个Java代码
IDownload Interface ,extends android.os.IInterface,

android.os.IInterface Binder接口的基础class,在定义一个新的Binder Interface,必须继承这个接口 IInterface

/**
 * Base class for Binder interfaces.  When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

IDownload java声明了我们定义在对应的AIDL的方法,唯一不同的是声明了这些方法可能会抛出 Remote调用的的Exception异常

public java.util.List<com.lijian.binderdemo.DownloadTask> getTasks() throws android.os.RemoteException;
public void addTask(com.lijian.binderdemo.DownloadTask task) throws android.os.RemoteException;

3.3.2 public static abstract class Stub

在上面我们曾经说过Client调用的其实是影子,真身在Server进程。
IDownload 有静态内部类辅助我们的在Client操作影子,在Server创建真身:

它就是public static abstract class Stub

/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.lijian.binderdemo.IDownload
{
private static final java.lang.String DESCRIPTOR = "com.lijian.binderdemo.IDownload";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.lijian.binderdemo.IDownload interface,
* generating a proxy if needed.
*/
public static com.lijian.binderdemo.IDownload asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lijian.binderdemo.IDownload))) {
return ((com.lijian.binderdemo.IDownload)iin);
}
return new com.lijian.binderdemo.IDownload.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_getTasks:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.lijian.binderdemo.DownloadTask> _result = this.getTasks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addTask:
{
data.enforceInterface(DESCRIPTOR);
com.lijian.binderdemo.DownloadTask _arg0;
if ((0!=data.readInt())) {
_arg0 = com.lijian.binderdemo.DownloadTask.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addTask(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.lijian.binderdemo.IDownload
{
    //省略代码
}
static final int TRANSACTION_getTasks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

为了更清晰

Stub.png

可以看到里面就

三个常量

  1. private static final java.lang.String DESCRIPTOR = "com.lijian.binderdemo.IDownload";
  2. static final int TRANSACTION_getTasks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  3. static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

Stub的构造方法

public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}

一个静态方法:public static com.lijian.binderdemo.IDownload asInterface(android.os.IBinder obj)

这个方法可以把一个IBinder转换为 com.lijian.binderdemo.IDownload interface

/**
* Cast an IBinder object into an com.lijian.binderdemo.IDownload interface,
* generating a proxy if needed.
*/
public static com.lijian.binderdemo.IDownload asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lijian.binderdemo.IDownload))) {
return ((com.lijian.binderdemo.IDownload)iin);//说明这是一个本地调用而不是IPC调用,直接返回真身即可,真身直接调用,不需要binder,效率更高
}
return new com.lijian.binderdemo.IDownload.Stub.Proxy(obj);//说明这是是IPC调用,返回一个Proxy,代理类,通过他进行Binder 调用
}

两个成员方法:

  • public android.os.IBinder asBinder()
@Override
 public android.os.IBinder asBinder(){
    return this;
}
  • public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    这个方法运行在Server端的Binder线程池,观察它的参数:
    int code:用于标识不同的方法
    android.os.Parcel data:用于传递Client的参数
    android.os.Parcel reply,用于写入Server的返回值
@Override 
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
switch (code)//通过code Client通过Binder调用的是哪一个方法
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getTasks:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.lijian.binderdemo.DownloadTask> _result = this.getTasks();//这个是接口的具体实现
reply.writeNoException();//写入Binder remote调用没有异常
reply.writeTypedList(_result);//写入返回值
return true;
}
case TRANSACTION_addTask:
{
data.enforceInterface(DESCRIPTOR);
com.lijian.binderdemo.DownloadTask _arg0;
if ((0!=data.readInt())) {
_arg0 = com.lijian.binderdemo.DownloadTask.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addTask(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

总结:

分析了IDownload.java 后,我们可以看到通过IDownload.aidl生成的代码,其实最终我们需要的也只是java代码,也就是说,如果我们熟悉了自动生成代码的套路,其实,不需要aidl,手写Java代码实现AIDL调用也是可以的。

4. 生成的IDownload.java 接口的使用:

4.1 Server端:

public class DownloadService extends Service {

    private static final String TAG = "DownloadService";

    private List<DownloadTask> mTasks = new CopyOnWriteArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

//注意这个方法,这就是定义的接口具体实现,理所当然的具体功能应该有Server实现
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IDownload.Stub() {
            @Override
            public List<DownloadTask> getTasks() throws RemoteException {
                return mTasks;
            }

            @Override
            public void addTask(DownloadTask task) throws RemoteException {
                if (mTasks != null) {
                    mTasks.add(task);
                }
            }
        };
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
}

4.2 Client端:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";

    private IDownload download;//注意这里

    private Button addTaskBtn;
    private TextView addTaskInfo;
    private Button getTasksBtn;
    private TextView getTasksInfo;

    private int index = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bindDownloadService();
        setContentView(R.layout.activity_main);
        setupView();
    }

    private void setupView() {
        addTaskBtn = (Button) findViewById(R.id.btn_add_task);
        getTasksBtn = (Button) findViewById(R.id.btn_get_tasks);
        addTaskInfo = (TextView) findViewById(R.id.tv_add_task_info);
        getTasksInfo = (TextView) findViewById(R.id.tv_tasks_info);
        addTaskBtn.setOnClickListener(this);
        getTasksBtn.setOnClickListener(this);
    }

    private void bindDownloadService() {
        Intent intent = new Intent(this, DownloadService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                download = IDownload.Stub.asInterface(service);//我们把bindService返回的 IBinder Service转换为一个接口实例
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        }, Context.BIND_AUTO_CREATE);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_get_tasks:
                getTasks();
                break;
            case R.id.btn_add_task:
                addTask();
                break;
            default:
                break;
        }
    }

    private void addTask() {
        if (download != null) {
            DownloadTask task = new DownloadTask(index++, "下载文件" + index, "www.baidu.com/" + index + ".txt");
            try {
                download.addTask(task);//这里就可以调用接口实例,看到没有,Binder的IPC操作对于Client端来说就是简化为调用一个对象了
                addTaskInfo.setText(task.toString());
            } catch (RemoteException e) {
                Log.w(TAG, e.toString());//Binder调用可能尝试RemoteException,需要捕获异常
            }
        }
    }

    private void getTasks() {
        if (download != null) {
            try {
                List<DownloadTask> tasks = download.getTasks();/这里就可以调用接口实例
                getTasksInfo.setText(tasks == null ? "no task" : tasks.toString());
            } catch (RemoteException e) {
                Log.w(TAG, e.toString());
            }
        }
    }
}

总结:

通过文字来阐述解析是比较困难的,具体可以参考Github上的代码:
https://github.com/bylijian/BinderDemo
注意,请查看git记录,这个是比较早的提交,和最新的代码不一样,具体参考截图高亮部分


另外,这个Demo还有更多的的演示代码,都值得看一看。

posted @ 2017-08-16 10:56  bylijian  阅读(688)  评论(0编辑  收藏  举报