[android]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,为了:

不让这个DownloadService占用App的UI进程内存资源
DownService奔溃不影响App的UI进程
我们需要在AndroidManifest里面注册DownloadService的时候通过 android:process=":remote"指定这个Service运行在一个
独立的私有进程(进程名为应用包名:remote)中(请百度 android:process=""的用法)

因为DownloadService运行在一个独立进程,App默认的UI进程需要和它通信就是IPC,具体来说就是使用Binder
3.下面一步步演示具体的实践步骤:
我们希望有一个MainActivity代表当前的App的进程,DownloadService运行在另外的一个进程
然后:
MainActivity可以通过bindService 绑定DownloadService
然后Binder实现IPC(进程间通信)调用DownloadService的两个功能:

public List 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"?>

<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>
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 getTasks();
void addTask(in DownloadTask task);//使用in 表明这是一个输入的变量
}
有代码可以看到我们定义了一个接口 IDownload
它声明了两个方法

List

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

可以看到里面就

三个常量
private static final java.lang.String DESCRIPTOR = “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的构造方法
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调用也是可以的。
  1. 生成的IDownload.java 接口的使用:
    4.1 Server端:
    public class DownloadService extends Service {

    private static final String TAG = “DownloadService”;

    private List 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 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());
        }
    }
}

}

转帖 https://www.cnblogs.com/bylijian/p/7372232.html

发布了14 篇原创文章 · 获赞 4 · 访问量 3516

猜你喜欢

转载自blog.csdn.net/lisiwei1994/article/details/86301715