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