美文网首页android
IPC 使用AIDL实现跨进程通信

IPC 使用AIDL实现跨进程通信

作者: 静享时光 | 来源:发表于2020-05-05 23:44 被阅读0次

AIDL使用注意事项

第一、AIDL支持的文件类型

1、基本类型
2、String和CharSequence
3、List:只支持ArrayList
4、Map:只支持HashMap
5、Parcelable:所有实现Parcelable的对象
6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

第二、自定义的Parcelable对象和ADIL对象的时候,必须通过import引入
第三、自定义的Parcelable对象,必须新建一个同名的AIDL文件,并在其中声明他为parcelable类型。
第四、处理基本数据类型,其他类型的参数必须表明方向,in(输入型), out(输出型),inout(输入输出型)
第五、AIDL接口只支持方法,不支持静态常量
第六、AIDLde 包结构在服务端和客户端要保持一致

AIDL实现客户端和服务端通信

注册


        <service
            android:name=".ipc_demo3.BookManagerService"
            android:process="com.book.manager.remote" />

        <activity android:name=".ipc_demo3.ClientAndServiceDemoActivity" />

Activity类

public class ClientAndServiceDemoActivity extends Activity implements View.OnClickListener {
    private ServiceConnection serviceConnection;
    public IBookManager iBookManager;

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

        findViewById(R.id.binder_service_tv).setOnClickListener(this);
        findViewById(R.id.add_book).setOnClickListener(this);
        findViewById(R.id.get_book_list).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.binder_service_tv:
                bindMyService();
                break;
            case R.id.add_book:
                addBook();
                break;
            case R.id.get_book_list:
                getBookList();
                break;
            default:
                break;
        }
    }

    /**
     * 绑定服务
     */
    private void bindMyService() {
        Intent serviceIntent = new Intent(this, BookManagerService.class);
        serviceConnection = new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iBookManager = IBookManager.Stub.asInterface(service);

                //获取书单列表
                try {
                    List<Book> booKList = iBookManager.getBooKList();
                    printBookList(booKList);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
        bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);

    }

    /**
     * 添加书单
     */
    private void addBook() {
        if (iBookManager == null) {
            return;
        }
        try {
            iBookManager.addBook(new Book(14, "Android进阶"));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取书单列表
     */
    private void getBookList() {
        if (iBookManager == null) {
            return;
        }
        try {
            List<Book> booKList = iBookManager.getBooKList();
            printBookList(booKList);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void printBookList(List<Book> booKList) {
        if (EmptyUtils.isEmpty(booKList)) {
            return;
        }

        for (Book book : booKList) {
            Log.e("IPC 通信", "书单:" + book);
        }
    }

    @Override
    protected void onDestroy() {
        iBookManager = null;
        if (serviceConnection != null) {
            unbindService(serviceConnection);
            serviceConnection = null;
        }
        super.onDestroy();
    }
}

Service类

public class BookManagerService extends Service {
    /**
     * 使用CopyOnWriteArrayList进行自动的线程同步
     * AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
     * 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
     * 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
     * 所以在服务端采用CopyOnWriteArrayList时完全可以的。
     */
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    //创建AIDL的Binder对象
    private Binder mBinder = new IBookManager.Stub() {

        /**
         * 获取书单列表
         * @return
         * @throws RemoteException
         */
        @Override
        public List<Book> getBooKList() throws RemoteException {
            return mBookList;
        }

        /**
         * 添加书单
         * @param book
         * @throws RemoteException
         */
        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);

        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //添加两个测试书籍
        mBookList.add(new Book(12, "android开发艺术探索"));
        mBookList.add(new Book(13, "第一行代码"));

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

实现新书提醒功能

创建一个监听器
// IOnNewBookArrvedListener.aidl
package com.example.jinghuang.demo2020.ipc_demo3;
 import com.example.jinghuang.demo2020.ipc_demo3.Book;

// Declare any non-default types here with import statements

interface IOnNewBookArrvedListener {

   void onNewBookArived(in Book newBook);
}

添加注册和注销监听的方法
 package com.example.jinghuang.demo2020.ipc_demo3;
 import com.example.jinghuang.demo2020.ipc_demo3.Book;
 import com.example.jinghuang.demo2020.ipc_demo3.IOnNewBookArrvedListener;

 // Declare any non-default types here with import statements

 interface IBookManager {

  List<Book>getBooKList();
  void addBook(in Book book);

  void registerListener(IOnNewBookArrvedListener listener);
  void unRegisterListener(IOnNewBookArrvedListener listener);
 }

Service类

package com.example.jinghuang.demo2020.ipc_demo3;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by jing.huang on 2020/5/5.
 */
public class BookManagerService extends Service {
    /**
     * 使用CopyOnWriteArrayList进行自动的线程同步
     * AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
     * 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
     * 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
     * 所以在服务端采用CopyOnWriteArrayList时完全可以的。
     */
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    /**
     * 定义一个变量记录服务是否停止
     */
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    /**
     * 定义一个集合存放监听器,因为可能不止一个客户端
     */
    private CopyOnWriteArrayList<IOnNewBookArrvedListener> mListenerList = new CopyOnWriteArrayList<>();


    //创建AIDL的Binder对象
    private Binder mBinder = new IBookManager.Stub() {

        /**
         * 获取书单列表
         * @return
         * @throws RemoteException
         */
        @Override
        public List<Book> getBooKList() throws RemoteException {
            return mBookList;
        }

        /**
         * 添加书单
         * @param book
         * @throws RemoteException
         */
        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrvedListener listener) throws RemoteException {
     
            if (mListenerList.contains(listener)) {
                Log.e("IPC 通信", "already exists,can not register ");
            } else {
                mListenerList.add(listener);
            }
            Log.e("IPC 通信", "添加了监听器  监听器个数: " + mListenerList.size());
        }

        @Override
        public void unRegisterListener(IOnNewBookArrvedListener listener) throws RemoteException {
            if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);
            } else {
                Log.e("IPC 通信", "not found,can not unRegister ");
            }
            Log.e("IPC 通信", "移除了监听器  监听器个数: " + mListenerList.size());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //添加两个测试书籍
        mBookList.add(new Book(12, "android开发艺术探索"));
        mBookList.add(new Book(13, "第一行代码"));

        //开启线程添加新书
        new Thread(new ServiceWork()).start();

    }

    @Override
    public void onDestroy() {
        //修改标志
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 开启一个线程,每个3秒就添加一本新书
     */
    private class ServiceWork implements Runnable {

        @Override
        public void run() {
            //如果服务器没有关闭,每个一段时间添加一本新书,知道服务器关闭
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int count = mBookList.size() + 1;

                Book book = new Book(count, "android_" + count);
                //添加新书
                onNewBookArrived(book);
            }
        }
    }

    /**
     * 添加新书,并且通知客户端
     *
     * @param book
     */
    private void onNewBookArrived(Book book) {
        mBookList.add(book);
        for (IOnNewBookArrvedListener listener : mListenerList) {
            Log.e("IPC 通信", "notify listener: " + listener);
            try {
                listener.onNewBookArived(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

Activity

public class ClientAndServiceDemoActivity extends Activity implements View.OnClickListener {
    private ServiceConnection serviceConnection;
    public IBookManager iBookManager;
    private static final int CLIENT_MESSAGE_WHAT = 111;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CLIENT_MESSAGE_WHAT:
                    Log.e("IPC 通信", "客户端 新书:" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    };

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

        findViewById(R.id.binder_service_tv).setOnClickListener(this);
        findViewById(R.id.add_book).setOnClickListener(this);
        findViewById(R.id.get_book_list).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.binder_service_tv:
                bindMyService();
                break;
            case R.id.add_book:
                addBook();
                break;
            case R.id.get_book_list:
                getBookList();
                break;
            default:
                break;
        }
    }

    /**
     * 绑定服务
     */
    private void bindMyService() {
        Intent serviceIntent = new Intent(this, BookManagerService.class);
        serviceConnection = new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                iBookManager = IBookManager.Stub.asInterface(service);

                //获取书单列表
                try {
                    //注册监听
                    iBookManager.registerListener(iOnNewBookArrvedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                iBookManager = null;
            }
        };
        bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);

    }

    /**
     * 添加书单
     */
    private void addBook() {
        if (iBookManager == null) {
            return;
        }
        try {
            iBookManager.addBook(new Book(14, "Android进阶"));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取书单列表
     */
    private void getBookList() {
        if (iBookManager == null) {
            return;
        }
        try {
            List<Book> booKList = iBookManager.getBooKList();
            printBookList(booKList);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void printBookList(List<Book> booKList) {
        if (EmptyUtils.isEmpty(booKList)) {
            return;
        }

        for (Book book : booKList) {
            Log.e("IPC 通信", "书单:" + book);
        }
    }

    @Override
    protected void onDestroy() {
        //注销监听
        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
            try {
                iBookManager.unRegisterListener(iOnNewBookArrvedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        iBookManager = null;
        if (serviceConnection != null) {
            unbindService(serviceConnection);
            serviceConnection = null;
        }

        super.onDestroy();
    }

    private IOnNewBookArrvedListener iOnNewBookArrvedListener = new IOnNewBookArrvedListener.Stub() {

        @Override
        public void onNewBookArived(Book newBook) throws RemoteException {
            //发送消息到Handler
            mHandler.obtainMessage(CLIENT_MESSAGE_WHAT, newBook).sendToTarget();

        }
    };
}

上面的功能就实现了新书提醒功能。但是当我们按返回键时,会发现监听器注销失败


日志.png

无法注销监听的原因:

这种注销的处理方式在日常开发时常使用到,但是放到多线程中就无法凑效,因为Binder会把客户端传递过去的对象重新转换并生成一个新的对象。虽然我们在注册和注销过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后,会产生两个全新的对象。对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。

怎样实现注销监听

我们用RemoteCallbackList代替CopyOnWriteArrayList。RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。

我们修改Service的代码

public class BookManagerService extends Service {
    /**
     * 使用CopyOnWriteArrayList进行自动的线程同步
     * AIDL支持的List数据只支持ArrayList类型,这里为什么可以使用CopyOnWriteArrayList呢?
     * 因为AIDL中所支持的是抽象的List,而List只是一个接口,因为虽然服务端返回的是CopyOnWriteArrayList
     * 但是是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端。
     * 所以在服务端采用CopyOnWriteArrayList时完全可以的。
     */
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    /**
     * 定义一个变量记录服务是否停止
     */
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    /**
     * 定义一个集合存放监听器,因为可能不止一个客户端
     */
    private RemoteCallbackList<IOnNewBookArrvedListener> mListenerList = new RemoteCallbackList<>();


    //创建AIDL的Binder对象
    private Binder mBinder = new IBookManager.Stub() {

        /**
         * 获取书单列表
         * @return
         * @throws RemoteException
         */
        @Override
        public List<Book> getBooKList() throws RemoteException {
            return mBookList;
        }

        /**
         * 添加书单
         * @param book
         * @throws RemoteException
         */
        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrvedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArrvedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        //添加两个测试书籍
        mBookList.add(new Book(12, "android开发艺术探索"));
        mBookList.add(new Book(13, "第一行代码"));

        //开启线程添加新书
        new Thread(new ServiceWork()).start();

    }

    @Override
    public void onDestroy() {
        //修改标志
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 开启一个线程,每个3秒就添加一本新书
     */
    private class ServiceWork implements Runnable {

        @Override
        public void run() {
            //如果服务器没有关闭,每个一段时间添加一本新书,知道服务器关闭
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(3000);

                    int count = mBookList.size() + 1;

                    Book book = new Book(count, "android_" + count);
                    //添加新书
                    onNewBookArrived(book);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }


    /**
     * 添加新书,并且通知客户端
     *
     * @param book
     */
    private void onNewBookArrived(Book book) {
        mBookList.add(book);
        int count = mListenerList.beginBroadcast();
        for (int i = 0; i < count; i++) {
            IOnNewBookArrvedListener listener = mListenerList.getBroadcastItem(i);
            Log.e("IPC 通信", "notify listener: " + listener);
        }
    }
}

执行之后发现,程序会报如下异常:
java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast
为什么会报异常呢?
是因为RemoteCallbackLsitener的beginBroadcast方法必须和finishBroadcast()方法配对使用。
修改后的代码:


   /**
    * 添加新书,并且通知客户端
    *
    * @param book
    */
   private void onNewBookArrived(Book book) {
       mBookList.add(book);
       int count = mListenerList.beginBroadcast();
       for (int i = 0; i < count; i++) {
           IOnNewBookArrvedListener listener = mListenerList.getBroadcastItem(i);
           Log.e("IPC 通信", "notify listener: " + listener);
       }
       mListenerList.finishBroadcast();
   }

另外,需要注意的是,我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法运行在服务端方法执行比较耗时,就会导致客户端线程长时间的足晒,如果客户端是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,客户端调用服务端方法时,要开启子线程。另外,由于客户端的onServiceConnected()方法和onServiceDisconnected()方法都是运行在UI线程,所以如果需要在他们的方法内调用服务端的方法,也需要开启子线程。
再次,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要再服务端方法中开线程去进行异步任务。

相关文章

网友评论

    本文标题:IPC 使用AIDL实现跨进程通信

    本文链接:https://www.haomeiwen.com/subject/txnmghtx.html