Binder or AIDL的最简单实践

it2024-12-16  52

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=""的用法) <service android:name=".DownloadService" android:process=":remote" /> 因为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); }

为了更清晰

可以看到里面就

三个常量

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调用也是可以的。

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还有更多的的演示代码,都值得看一看。

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

最新回复(0)