Android 开发艺术探索笔记(四) 之 Binder 及AIDL

1.Binder 介绍

从 Android 层面来说,Binder 就是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务。Binder 主要用于 Service,包括 AIDL 和 Messenger,其实 Messenger 的底层是 AIDL

2.使用 AIDL

这里主要介绍一下思路,具体参考一下 Android:学习AIDL,这一篇文章就够了(上) 进行项目搭建。

  • 一、新建服务端工程

    服务端代码:https://github.com/innovatorCL/IPCServer

  • 1.新建 aidl 包,客户端和服务器的包名必须一样。

  • 2.新建非基本数据类型的数据文件,包括 xx.java 和 xx.aidl 文件,同时在 Module 的 gradle 文件中声明源码路径包含 aidl,不然会报找不到文件的错误。

    sourceSets {
         main {
             java.srcDirs = ['src/main/java', 'src/main/aidl']
         }
     }
    
    Android 开发艺术探索笔记(四) 之 Binder 及AIDL_第1张图片
    AIDL包
  • 3.新建接口方法 xxx.aidl 文件。所有需要被客户端调用的方法在这个文件中声明。

    Android 开发艺术探索笔记(四) 之 Binder 及AIDL_第2张图片
    AIDL 定义接口
  • 4.新建一个 Service 作为服务端,在里面是实现接口 xxx.aidl 声明的方法,并实例化成为一个 XXX 对象,在客户端 bind 服务端的时候返回给客户端(其实返回给客户端的是 Android 生成的 XXX.java 里面的内部代理类对象,XXX.Stub.Proxy,客户端通过这个对象调用服务端 Service 的方法。)

 /**
 *
 * 服务端的AIDLService.java
 *
 * 在服务端实现AIDL中定义的方法接口的具体逻辑,
 * 然后在客户端调用这些方法接口,从而达到跨进程通信的目的。
 * Created by innovator on 2018/1/18.
 */

public class AIDLService extends Service{


    //包含Book的List
    private List mBooks = new ArrayList<>();


    //由AIDL生成的BookManager类的代理类
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
        @Override
        public List getBooks() throws RemoteException {
            synchronized (this){
                Log.i("TAG","服务端发送数据给客户端");
                if(mBooks != null){
                    return mBooks;
                }

                return new ArrayList<>();
            }
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.i("TAG","服务端接收客户端的数据");
            synchronized (this) {
                if (mBooks == null) {
                    mBooks = new ArrayList<>();
                }
                if (book == null) {
                    Log.e("TAG", "客户端传了一个空的Book对象");
                    book = new Book();
                }
                //因为 getBook 的参数的 tag 是 inout,所以服务端修改了book的参数,客户端的应该也会修改
                book.setPrice(6666);
                if (!mBooks.contains(book)) {
                    mBooks.add(book);
                }
                //打印mBooks列表,观察客户端传过来的值
                Log.e("TAG", "服务端的 addBooks() 方法被调用 , 打印服务端收到的数据 : " + mBooks.toString());
            }
        }
    };

    @Override
    public void onCreate() {
        Book book = new Book();
        book.setName("在复杂的世界做个明白人");
        book.setPrice(2222);
        mBooks.add(book);
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG", String.format("服务端连接上了客户端,连接的 ntent 是 %s", intent.toString()));
        return mBookManager;
    }
}

在 Manifest.xml 中声明该 Service:

    
        
            
            
        
    

  • 二、新建客户端工程

    客户端代码:https://github.com/innovatorCL/IPCClient

    • 1.新建和服务端同名的 aidl 包,将服务端的 aidl 文件全部 copy 过去。

      Android 开发艺术探索笔记(四) 之 Binder 及AIDL_第3张图片
      AIDL 定义接口
    • 2.在 MainActivity 中连接服务端 Service ,并获取服务端返回的代理对象,调用服务端的方法传入或者读取服务端的数据。

/**
 *
 * IPC 客户端的 MainActivity.java
 */
public class MainActivity extends Activity {

    //由AIDL文件生成的Java类
    private BookManager mBookManager;

    //标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
    private boolean mBound;

    //包含Book对象的list
    private List mBooks;

    private Book book;

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


    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("TAG","连接上了服务端");
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            //获取代理对象,调用服务端的方法
            if(mBookManager != null){
                try {
                    mBooks = mBookManager.getBooks();
                    Log.i("TAG","获取到了服务端数据:"+mBooks.toString());
                }catch (RemoteException e){
                    e.printStackTrace();
                    Log.i("TAG","调用服务端方法出现异常:"+e.getMessage());
                }

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
            Log.i("TAG","断开服务端连接");
        }
    };

    /**
     * 按钮的点击事件,点击之后调用服务端的addBookIn方法
     *
     * @param v
     */
    public void addBook(View v){
        if(!mBound){
            attemptBindService();
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
            return;
        }

        if(mBookManager == null){
            return;
        }

        book = new Book();
        book.setName("认知突围");
        book.setPrice(88);
        //调用服务器端的方法,传入一个Book对象
        try{
            mBookManager.addBook(book);
            Log.i("TAG","调用服务端方法传入一个Book");
        }catch (RemoteException e){
            e.printStackTrace();
            Log.i("TAG","调用服务端方法出现异常:"+e.getMessage());
        }
    }

    /**
     * 点击了 addBook 之后看看客户端的数据有没有同步被改
     * 验证 inout tag 的功能
     *
     * 确实是同步更改了 inout 传入的那个对象的值
     * @param v
     */
    public void getBooks(View v) {

        Log.i("TAG","打印 addBooks 之后的数据 :"+book.toString());
    }

    /**
     * 尝试连接上服务器
     */
    private void attemptBindService(){
        Intent i = new Intent();
        i.setAction("com.innovator.aidl");
        i.setPackage("com.innovator.ipcserver");
        bindService(i,mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if(!mBound){
            attemptBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(mBound){
            unbindService(mServiceConnection);
            mBound = false;
        }
    }
}

3. AIDL 工作流程分析

首先介绍几个类:

  • Stub : 生成的 AIDL 的内部类,继承 Binder, 实现了 IInterface 接口;

  • Proxy : Stub 的内部类,实现了我们自定义的 AIDL 接口;

  • BinderProxy : Binder 的内部类,跨进程通信时传递的对象

  • 流程一:进程间通信时,Client 端开启服务 bindService, Server 端 onBind 方法返回 Stub 对象,通过 Binder 封装成 BinderProxy 对象 返回,到 Client 端的 onServiceConnected 方法接收到的就是 BinderProxy 对象,再调用 BookManager.Stub.asInterface(service) 封装成 Proxy, BinderProxy 是它的成员变量。

    到这里,Client 端已经拿到了 Service 端传递过来的 Binder 对象。

  • 流程二:Client 端调用方法(例:getBookList)时,会先调用 Proxy.getBookList, 这里会调用 mRemote.transact(这个是在客户端进程做的事), mRemote 也就是 BinderProxy 对象,实现跨进程的操作, BinderProxy 里面封装了 Stub 对象,所以会再跨进程调用到 Server 端的 Stub.onTransact(这个是在服务端的 Binder 线程池做的事), Stub.onTransact 内部调用 Service 里自己实现的 getBookList 方法,执行完后跨进程返回到 BinderProxy 中,BinderProxy 再跨进程返回到 Client 端,到此一次方法调用结束。

补充一张进程间方法调用的图。

Android 开发艺术探索笔记(四) 之 Binder 及AIDL_第4张图片
image.png
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/innovator/AndroidStudioProjects/IPCServer/app/src/main/aidl/com/innovator/ipcclient/BookManager.aidl
 */
package com.innovator.ipcclient;
//定义方法接口

public interface BookManager extends android.os.IInterface {

/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.innovator.ipcclient.BookManager {

    private static final java.lang.String DESCRIPTOR = "com.innovator.ipcclient.BookManager";
    /** Construct the stub at attach it to the interface. */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.innovator.ipcclient.BookManager interface,
     * generating a proxy if needed.
     */
    public static com.innovator.ipcclient.BookManager asInterface(android.os.IBinder obj) {
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.innovator.ipcclient.BookManager))) {
            return ((com.innovator.ipcclient.BookManager)iin);
        }
        return new com.innovator.ipcclient.BookManager.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_getBooks: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List _result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                com.innovator.ipcclient.Book _arg0;
                if ((0!=data.readInt())) {
                    _arg0 = com.innovator.ipcclient.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                if ((_arg0!=null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }


    private static class Proxy implements com.innovator.ipcclient.BookManager {
        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 getBooks() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.innovator.ipcclient.Book.CREATOR);
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }


        //传参时除了Java基本类型以及String,CharSequence之外的类型
        //都需要在前面加上定向tag,具体加什么量需而定
        @Override
        public void addBook(com.innovator.ipcclient.Book book) 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 ((book!=null)) {
                    _data.writeInt(1);
                    book.writeToParcel(_data, 0);
                }
                else {
                    _data.writeInt(0);
                }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                if ((0!=_reply.readInt())) {
                    book.readFromParcel(_reply);
                }
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }



    }

    static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}



    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    public java.util.List getBooks() throws android.os.RemoteException;


    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    public void addBook(com.innovator.ipcclient.Book book) throws android.os.RemoteException;
}

详细分析参考:

  • Android 艺术开发探索,Android 根据 AIDL 文件自动生成对应的 java 文件 (P55 —— P60)
  • Android:学习AIDL,这一篇文章就够了(下)

4. IPC 应用场景

  • 腾讯系、阿里系等大厂的 APP 之间的通信
  • 系统 APP 和系统服务之间的通信
  • 推送、IM
  • 双进程守护

你可能感兴趣的:(Android 开发艺术探索笔记(四) 之 Binder 及AIDL)