IPC 使用AIDL实现跨进程通信

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线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要再服务端方法中开线程去进行异步任务。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343