Android AIDL实现进程间通信

        今天犯二了, 犯了个超级低级的错误, 真的是丢人丢大发了大哭.

        刚好顺道反思下, 也对工作这几年做一些简单的总结. 不知道你们是不是和我一样也总遇到各种奇葩问题, 明明代码是对的可是就是不好使; 我明明声明了权限了但是就是获取不到; 这个bug以前没发生过啊, 我最近代码也没修改没道理出现问题啊, 肯定是其他地方的bug(尤其是多个人联合开发某个功能, 需要对接的时候)等等啦, 我以前遇到这个问题, 我的第一反应应该不是代码问题啊, 是不是哪里弄错了, 我"清楚的"记得代码写的没错, 是不是其他地方出问题导致的?.  "事出反常必有妖啊", 而且往往证明最后的妖都出在我身上尴尬. 啥也不说了, 总结反思吧, 以后再出现这种问题, 

        第一, 先暗地里提醒自己是不是自己的问题, 不要上来就以为自己的代码没问题, 信誓旦旦找别人对问题, 结果发现最终还是自己的问题, 这脸打的太疼大哭,  我有几次就是这样的

        第二, 分析下出现问题的可能性, 找出对应的地方的代码

        第三, 啥也别说, 仔细把出问题的那块的代码仔细看一遍, 我以前在网上看到过一个大神说的读书方法, 就一个字一个字的看, 我觉得说的对我很合适, 我发现我有段时间很浮躁, 读书看博客都是跳着看, 或者一目好几行, 这样是很快, 但是容易漏掉内容, 并且往往也领会不到深意, 我自认没有一目多行的能力,  以后还是老老实实一个字一个字看

        第四, 一边过完, 再一个字一个字过一遍, 我觉得多看一遍不是浪费时间, 在仔细看过后 在去找相关的同事对问题, 这样能减少被打脸的几率么不是(/ □ \), 至少也要对自己写的代码负责么不是

        检查问题一定要仔细, 耐心, 切忌一目多行, 草草了事

        好了言归正传 Android AIDL使用, 我就是把我在使用AIDL时候遇到的问题和遇到的坑给总结一下, 供以后参考

        AIDL(Android Interface Definition Language)即Android 接口定义语言, 主要是用于进程间通信, 方便进程间数据传递, 方法调用, 关于概念性的定义这里就不细讲了, 直接开始讲解使用方法, 关于注意事项, 我边讲边提吧

        既然是进程间通信, 肯定要涉及service端和client端, 这里先说service端怎样对外提供服务

AIDL的实现步骤:
    1.服务端
服务端首先要创建一个Service用来监听客户端的请求, 然后创建一个AIDL文件, 将暴露给客户端的接口在这个AIDL文件中声明, 最后在Service中实现这个AIDL接口即可

    2.客户端
客户端要做的事情就相对简单些, 首先绑定服务端的Service, 绑定成功后, 将服务端返回的Binder对象转成AIDL接口所属的类型, 接着就可以调用AIDL中的方法了

Service端

1.在Android Studio里创建AIDL文件夹

        先说明一下service工程的包名: com.wb.aidl_service

        工程跳到project模式下, 在工程的main上右键->New->Folder->AIDL Folder这样就创建了一个AIDL的专属目录如下图:

                                          

        然后创建我们aidl文件夹下面的包: com.aidl.demo,  就是右键new->package

2.创建我们需要声明的aidl文件及响应的bean类的aidl文件

        这里我的例子是"Android开发艺术探索"书里的基础上改的, 先说下Service里要用到的几个类: 

     Book.java Book对象的bean类, Book.aidl Book对象的aidl文件, IBookManager.aidl 对外提供方法的aidl文件, BookManagerService.java 服务端对外提供的service

        第一个注意事项在进程间通讯的时候关于参数的传递问题: java的基本数据类型都支持, 对于自定义对象类型(例如上面说的Book对象)必须能够可序列化

        众所周知Android里实现序列化的方法有两种implements Parcelable接口或者 implements Serializable接口这两个我们选择哪个呢? 当然是选择Parcelable接口了,  原因如下:

        这里我直接引用别人的总结(李嘉欣-第四维空间 http://blog.csdn.net/ljx19900116/article/details/41699593 点击打开链接) : Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。

        2.1创建实现了Parcelable接口的Book对象

            这个创建对象不需要在aidl里, 就是在工程的src目录下即可
public class Book implements Parcelable {
    private String name;
    private int price;

    public Book() {

    }

    public Book(int price, String name) {
        this.price = price;
        this.name = name;
    }

    protected Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }
    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

       这段代码没什么可说的, 就是定义两个属性, 然后实现set, get方法, 关于需要实现Parcelable的方法系统也会自动生成, 只需要注意一件事情就是包名: com.aidl.demo 

       2.2创建Book.java对应的Book.aidl文件

        为什么需要这个文件呢?  所有实现了Parcelable接口的并且要在AIDL接口中使用到的类都需要新建一个对应类名的.aidl文件., 文件内容就写 parcelable XXX
        在aidl文件夹下的包下右键->new->AIDL->AIDLfile
package com.aidl.demo;

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

parcelable Book;

        代码是不是超简洁, 总共就四行 , 还有两行是系统注释偷笑, 不过有个要注意的事项: 注意到包名了吗, 和上面创建的Book.java的包名是一样的

        2.3创建IBookManager.aidl文件

           还是在aidl文件夹下的包下右键->new->AIDL->AIDL file
// IBookManager.aidl
package com.aidl.demo;

// Declare any non-default types here with import statements
// 一定是全路径的类引用, 即使实在同一个包下
import com.aidl.demo.Book;

interface IBookManager {

    List<Book> getBookList();
    void addBook(in Book book);
}
        这里代码也不多, 但是有个非常重要的地方需要注释, 也是困扰我了很久的一个知识点, 声明了一个interface, 里面两个方法, 一个是获取book列表, 一个是添加一本书
        这里注意第二个方法addBook(in Book book), 这里返回值是void没啥说的, 注意参数列表里的in(这个是表明跨进程间数据的流向共有in, out, inout三种), 这里重点说一下这个, 在对外提供的方法参数有以下几点要注意:
        1.基本类型不需要指明方向例如String, int等
        2. in 表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。(这个参考http://blog.csdn.net/luoyanglizi/article/details/51980630点击打开链接)的,  这里只看文字描述也许有些抽象, 下文会有实例演示就能比较形象的理解各个tag表示的意思了
        3.在使用过程中要根据具体情况来选择对应的tag, 虽说inout肯定没错, 但是不要不管三七二十一直接就用inout, 这个在底层是要花费开销的, 在使用out和inout的时候还有个坑等着我们,  我在第一次使用的时候, 还是费了点劲的, 下文介绍具体是什么坑

3.创建服务器端的Service类

        
package com.wb.aidl_service.service;

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

import com.aidl.demo.Book;
import com.aidl.demo.IBookManager;

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

/**
 * Created by wb on 2018/3/19.
 */

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.d(TAG, "===receive===" + book.getName() + "===price===" + book.getPrice());
            if(book.getName() == null) {
                book.setName("为什么给我传递的是空数据");
                book.setPrice(100000);
            } else {
                book.setPrice(200);
            }
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(100, "Android"));
        mBookList.add(new Book(150, "IOS"));
    }

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

        简单介绍下: 首先维护了一个book列表, 然后创建了一个Binder对象, 我们可以ctrl+左键点击Stub进去看下具体内容, 会跳到一个IBookManager.java的文件里, 这个文件是系统帮我们生成的, 它声明了一个内部类Stub,  这个Stub就是一个Binder类, 当客户端和服务端都位于同一个进程中的时候, 方法调用不会走跨进程的transact过程, 而当二者位于不同进程时, 方法调用需要走transact过程, 这个逻辑是由Stub的内部代理类Proxy来完成的.  而这个Stub对象需要实现两个方法分别是getBookList和addBook, 而这两个方法就是我们上文声明的IBookManager.aidl里声明的, 具体的代码没什么难度, 不多做解释, 至于为什么判断book.getName()是否为null,  下文会做说明. onCreate里就是向list里添加两本书做基础数据,  在onBinder里会把mBinder对象返回供client端使用, 这样服务器端的代码就完成了

        记得要在Manifest.xml文件中把service声明出来, 并且service的exported属性设置为true

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wb.aidl_service">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true">
        <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=".service.BookManagerService"
            android:exported="true"/>
    </application>

</manifest>

  

 Client端

1.把服务端提供的aidl文件及目录原封不动的copy到客户端, 注意目录要求一模一样

                                

        我客户端的包名是: com.wb.aidl_client,   Book.java的目录和两个aidl文件的目录一定要注意要和服务器端的一样

2.通过bindservice绑定服务端的service, 调用服务端对外提供的方法

        绑定服务的代码比较简单, 就是注意服务提供方的包名和service的全路径名不要写错即可:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.wb.aidl_service",
                "com.wb.aidl_service.service.BookManagerService"));
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

        接下来就是调用服务端提供的方法了, 这里主要是在ServiceConnection对象的onServiceConnected方法里进行处理了, 先看代码:

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "===query book list, list type:" + list.getClass().getCanonicalName());
                Log.i(TAG, "==query book list:" + list);

                // 添加一本图书
                Book book = new Book(110, "Android开发艺术探索");
                bookManager.addBook(book);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "==query book list newList:" + newList);
                for (Book temp : newList) {
                    Log.d(TAG, "==temp : name===" + temp.getName());
                    Log.d(TAG, "==temp : price===" + temp.getPrice());
                }

                Log.d(TAG, "===book===name===" + book.getName() + "===price===" + book.getPrice());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
        这里需要注意的其实就是一行代码即
IBookManager bookManager = IBookManager.Stub.asInterface(service);

        这里的asInterface的作用就是将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象, 这种转换过程是区分进程的, 如果客户端和服务端位于统一进程, 那么此方法返回的就是服务端的Stub对象本身, 否则返回的是系统封装后的Stub.proxy对象. 在我们拿到这个对象后, 就可以通过该对象调用服务器端的方法了.

        下面是运行效果

Client的log如下:

                          

服务端的log如下:

                          

        先看client的log, 主要就看红色框内的即可, 首先把通过getBookList()方法获取到的book列表打印出来, 发现多了一本书, 也就是客户端通过addBook方法新添加的那本只不过在service端把价格改为200了,  再看第二个红框, 我打印的是客户端自己new的那个对象可以看到名称和价格和原来的一模一样.

        有意思的事情要来了, 前面说过在定义aidl接口方法的时候, 方法是有参数的, 而参数是有tag(in, out, inout)的, 重点来了, 前面已经简单的说过in/out/inout有什么区别了, 这里将通过实际操作来进行验证, 也让大家有个更清晰的认识

关于in/out/inout的用法及可能遇到的坑

        前面我们使用的就是in, 接下来我们换成out, 修改如下几个地方:

        1.service端的代码

interface IBookManager {

    List<Book> getBookList();
    void addBook(out Book book);
}

        只是把原来的in改为了out

        2.client端的代码, 和service端一样

interface IBookManager {

    List<Book> getBookList();
    void addBook(out Book book);
}

        然后编译运行

        果然还是太年轻,  没有想象的那么容易, 这里遇到了又一个坑 编译后报了如下错误:

                             

        不过不要着急, 仔细看log发现异常是: readFromParcel(Parcel) 这个方法找不到, 而且是和Book类相关的,  静下心去Book.java里看一下,  是不是能发现一个和名字差不多的方法writeToParcel, 再看看writeToParcel是怎么实现的, 

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }

        再想想, 刚刚我们就是把in改成了out, 就这一点区别, 就报错了, 肯定和out有关, 那么in可能就是对应write, 而out根据提示可能就是和read呗,  那我们写个readFromParcel方法不就行了, 具体写法就是从parcel里读出对应数据

    public void readFromParcel(Parcel in){
        name = in.readString();
        price = in.readInt();
    }

        然后重新编译, 哦耶, 编译通过, 运行看下效果:

        client端log:

03-20 20:07:54.680 12520-12520/? I/MainActivity: ===query book list, list type:java.util.ArrayList
03-20 20:07:54.680 12520-12520/? I/MainActivity: ==query book list:[com.aidl.demo.Book@86d462, com.aidl.demo.Book@3ea79f3]
03-20 20:07:54.682 12520-12520/? I/MainActivity: ==query book list newList:[com.aidl.demo.Book@f026b0, com.aidl.demo.Book@e2a4929, com.aidl.demo.Book@901d4ae]
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : name===Android
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : price===100
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : name===IOS
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : price===150
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : name===为什么给我传递的是空数据
03-20 20:07:54.683 12520-12520/? D/MainActivity: ==temp : price===100000
03-20 20:07:54.683 12520-12520/? D/MainActivity: ===book===name===为什么给我传递的是空数据===price===100000

         Service端log:

03-20 20:07:54.681 12201-12334/? D/BookManagerService: ===receive===null===price===0

        看一下log发现, service端收到的book对象居然是一个内容为空的book对象, 也就是虽然有book对象, 但是name是null, 而price是默认值0,  紧接着, service端对新的book进行了赋值

        再看client端的日志, 发现得到的booklist里的最后一本书不是自己添加的了, 是服务器端判断是空对象后修改的了, 更有意思的是客户端本身new的那个book对象的值, 在服务器修改后, 客户端的book对象也被修改了

        这个就是out的作用了:out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动

        好了, 接下来修改为inout还是老样子service和client都是只改一个地方
interface IBookManager {

    List<Book> getBookList();
    void addBook(inout Book book);
}
        然后编译运行看log:
        client的:
03-20 20:25:44.128 13270-13270/? I/MainActivity: ===query book list, list type:java.util.ArrayList
03-20 20:25:44.128 13270-13270/? I/MainActivity: ==query book list:[com.aidl.demo.Book@86d462, com.aidl.demo.Book@3ea79f3]
03-20 20:25:44.131 13270-13270/? I/MainActivity: ==query book list newList:[com.aidl.demo.Book@f026b0, com.aidl.demo.Book@e2a4929, com.aidl.demo.Book@901d4ae]
03-20 20:25:44.131 13270-13270/? D/MainActivity: ==temp : name===Android
03-20 20:25:44.131 13270-13270/? D/MainActivity: ==temp : price===100
03-20 20:25:44.131 13270-13270/? D/MainActivity: ==temp : name===IOS
03-20 20:25:44.132 13270-13270/? D/MainActivity: ==temp : price===150
03-20 20:25:44.132 13270-13270/? D/MainActivity: ==temp : name===Android开发艺术探索
03-20 20:25:44.132 13270-13270/? D/MainActivity: ==temp : price===200
03-20 20:25:44.132 13270-13270/? D/MainActivity: ===book===name===Android开发艺术探索===price===200
        service的:
03-20 20:25:44.129 12957-13077/? D/BookManagerService: ===receive===Android开发艺术探索===price===110        
         发现设置为inout后服务器能收到book对象, 并且服务器对book修改后, 客户端原有的book对象也被修改了 inout 为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
        到此为止我相信大家对in/out/inout能有一个比较清晰的认知了, 也知道什么时候需要使用in, 什么时候需要使用out, 和inout了,  接下来我们就默认使用in了,  不再使用out和inout进行测试了.

        接下来我们考虑另外一个问题:

        假设有一种需求:用户不想时不时的去查询图书列表, 于是他问"能否有新书的时候通知我呢?",  这个就是个典型的观察者模式, 首先,  service要提供一个AIDL的接口, 每个client都需要实现这个接口并向图书馆注册这个接口, 当然用户也可以随时取消注册, 之所以使用AIDL接口而不是普通接口是因为AIDL中无法使用普通接口

在AIDL中如何使用接口以及接口的注册和取消注册

       我们先在servic端的代码里创建一个IOnNewBookArrivedListener.aidl文件, 我们期望的是当有新书到来时, 调用IOnNewBookArrivedListener上的onNewArrived方法, 并把新书通过参数传递给客户端, 代码如下:

// IOnNewBookArrivedListener.aidl
package com.aidl.demo;

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

import com.aidl.demo.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}
        注意这里一定要import Book对象

       位置如下:

                              

        除了新加这个aidl文件以外还要在 原本的IBookManager.aidl文件中增加两个方法用于注册和取消注册, 代码如下:
// IBookManager.aidl
package com.aidl.demo;

// Declare any non-default types here with import statements
// 一定是全路径的类引用, 即使实在同一个包下
import com.aidl.demo.Book;
import com.aidl.demo.IOnNewBookArrivedListener;

interface IBookManager {

    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
      注意这里一定要import IOnNewBookArrivedListener.
     接着修改服务端Service中的代码, 主要是Service中IBookManager.Stub中新添加了两个方法要进行实现, 同时, 在BookManagerService中还开启了一个线程, 每隔5s向图书列表中加入一本新书, 并通知用户, 代码如下

       在Stub里添加如下方法, 代码比较简单不需要太多解释:
        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(!mListenerList.contains(listener)) {
                mListenerList.add(listener);
            } else {
                Log.i(TAG, "==already exists.");
            }
            Log.d(TAG, "==registerListener, size:==" + mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(mListenerList.contains(listener)) {
                mListenerList.remove(listener);
                Log.i(TAG, "==unregister success.");
            } else {
                Log.i(TAG, "==not found, can not unregister");
            }
            Log.d(TAG, "==unregisterListener, current size:==" + mListenerList.size());
        }
       
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(100, "Android"));
        mBookList.add(new Book(150, "IOS"));
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    private class ServiceWorker implements  Runnable {

        @Override
        public void run() {
            while(!mIsServiceDestoryed.get()) {
                try{
                    Thread.sleep(5000);
                } catch (Exception e) {

                }
                int bookId = mBookList.size() + 1;
                Book newBook = new Book(bookId, "new Book" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (Exception e) {

                }
            }
        }
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        Log.i(TAG, "==onNewBookArrived, nofity listener:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.i(TAG, "==onNewBookArrived, notify listener:" + listener);
            listener.onNewBookArrived(book);
        }
    }
        然后再定义一个work线程用于5秒钟添加一本书到booklist中, 添加完之后, 去调用通知客户端的代码onNewBookArrived
方法, 循环遍历ListenerList, 挨个通知客户端, 服务器端的代码到此就修改完毕.

        再来看客户端的代码:

        主要是两方面内容, 首先client要注册IOnNewBookArrivedListener到service端, 这样当有新书过来的时候才能通知到client, 同时我们要在Activity退出的时候取消这个注册, 另一方面, 当有新书过来的时候service会回调客户端的IOnNewBookArrivedListener中的onNewBookArrived方法, 但是这个方法是在客户端的Binder线程池中的, 因此为了便于进行UI操作, 我们需要有一个Handler可以将其切换到UI线程中

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.d(TAG, "===receive new book:" + msg.obj);
                    break;

                default:
                    super.handleMessage(msg);
            }
        }
    };

    private IOnNewBookArrivedListener mOnnewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "===query book list, list type:" + list.getClass().getCanonicalName());
                Log.i(TAG, "==query book list:" + list);

                // 添加一本图书
                Book book = new Book(110, "Android开发艺术探索");
                bookManager.addBook(book);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "==query book list newList:" + newList);
                for (Book temp : newList) {
                    Log.d(TAG, "==temp : name===" + temp.getName());
                    Log.d(TAG, "==temp : price===" + temp.getPrice());
                }

                Log.d(TAG, "===book===name===" + book.getName() + "===price===" + book.getPrice());

                // 注册
                bookManager.registerListener(mOnnewBookArrivedListener);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.wb.aidl_service",
                "com.wb.aidl_service.service.BookManagerService"));
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if(bookManager != null && bookManager.asBinder().isBinderAlive()) {
            try {
                Log.i(TAG, "==unregister listener:==" + mOnnewBookArrivedListener);
                bookManager.unregisterListener(mOnnewBookArrivedListener);
            } catch (RemoteException e) {

            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }

        主要代码如上,  就是创建一个handler, 用于接收数据处理UI, 定义一个IOnNewBookArrivedListener对象, 在onNewBookArrived方法中通过handler发送消息到主线程进行处理, 然后就是在onServiceConnected的最后一行添加了注册监听的代码, 在ondestroy会调用添加了取消监听的逻辑

        编译运行log如下:

service端log

03-20 21:10:20.562 15329-15361/? D/BookManagerService: ===receive===Android开发艺术探索===price===110
03-20 21:10:20.565 15329-15361/? D/BookManagerService: ==registerListener, size:==1
03-20 21:10:25.522 15329-15425/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:10:25.522 15329-15425/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@3b61bc8
03-20 21:10:30.527 15329-15425/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:10:30.528 15329-15425/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@3b61bc8
03-20 21:10:35.533 15329-15425/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:10:35.533 15329-15425/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@3b61bc8
03-20 21:10:38.962 15329-15361/? I/BookManagerService: ==not found, can not unregister
03-20 21:10:38.962 15329-15361/? D/BookManagerService: ==unregisterListener, current size:==1
03-20 21:10:40.536 15329-15425/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:10:40.537 15329-15425/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@3b61bc8
client端log:
03-20 21:10:20.560 15407-15407/? I/MainActivity: ===query book list, list type:java.util.ArrayList
03-20 21:10:20.561 15407-15407/? I/MainActivity: ==query book list:[com.aidl.demo.Book@86d462, com.aidl.demo.Book@3ea79f3]
03-20 21:10:20.563 15407-15407/? I/MainActivity: ==query book list newList:[com.aidl.demo.Book@f026b0, com.aidl.demo.Book@e2a4929, com.aidl.demo.Book@901d4ae]
03-20 21:10:20.563 15407-15407/? D/MainActivity: ==temp : name===Android
03-20 21:10:20.564 15407-15407/? D/MainActivity: ==temp : price===100
03-20 21:10:20.564 15407-15407/? D/MainActivity: ==temp : name===IOS
03-20 21:10:20.564 15407-15407/? D/MainActivity: ==temp : price===150
03-20 21:10:20.564 15407-15407/? D/MainActivity: ==temp : name===Android开发艺术探索
03-20 21:10:20.564 15407-15407/? D/MainActivity: ==temp : price===200
03-20 21:10:20.564 15407-15407/? D/MainActivity: ===book===name===Android开发艺术探索===price===110
03-20 21:10:20.672 1617-1999/? I/KPI-6PA-WMS-6: 6095447 WindowState.performShowLocked() sends empty message FINISHED_STARTING: Token{ae4bc5b ActivityRecord{9607f6a u0 com.wb.aidl_client/.MainActivity t98}}
03-20 21:10:20.676 1617-1857/? I/LaunchCheckinHandler: Displayed com.wb.aidl_client/.MainActivity,cp,ca,420
03-20 21:10:20.676 1617-1857/? I/KPI-6PA-AMS-8: 6095451 windowsDrawn com.wb.aidl_client.MainActivity
03-20 21:10:20.681 1617-1857/? I/ActivityManager: Displayed com.wb.aidl_client/.MainActivity: +416ms
03-20 21:10:25.525 15407-15407/? D/MainActivity: ===receive new book:com.aidl.demo.Book@8a7759d
03-20 21:10:30.531 15407-15407/? D/MainActivity: ===receive new book:com.aidl.demo.Book@9911a12
03-20 21:10:35.535 15407-15407/? D/MainActivity: ===receive new book:com.aidl.demo.Book@4a5c5e3
03-20 21:10:38.668 584-584/? D/SFPerfTracer:        layers: (5:9) (NavigationBar#0 (0xb1513000): 8:1481) (StatusBar#0 (0xb1845000): 0:2421) (RoundedOverlay#0 (0xb183c000): 0:48) (RoundedOverlay#1 (0xb15fb000): 0:50) (com.android.systemui.ImageWallpaper#0 (0xb19e6000): 0:123)* (AssistPreviewPanel#0 (0xb16d8000): 0:241)* (com.zui.launcher/com.zui.launcher.Launcher#0 (0xb1817000): 0:31)- (Splash Screen com.wb.aidl_client#0 (0xb156d000): 0:31)- (com.wb.aidl_client/com.wb.aidl_client.MainActivity#0 (0xb180f000): 1:11) 
03-20 21:10:38.961 15407-15407/? I/MainActivity: ==unregister listener:==com.wb.aidl_client.MainActivity$2@d729be0
03-20 21:10:40.540 15407-15407/? D/MainActivity: ===receive new book:com.aidl.demo.Book@d62c55e
        看着log发现两边运行貌似都很正常, 但是细心的读者能发现服务器端有一条log有问题:

03-20 21:10:38.962 15329-15361/? I/BookManagerService: ==not found, can not unregister
        也就是当我点击退出客户端的时候, 客户端调用了unregister但是服务器端并没有成功unregister并且, 看客户端最后一条log, 虽然上一句有unregisterlog, 但是后面还是收到了新的通知, 这里就遇到又一个坑了

      怎么才能正确的取消注册监听

       对于上面的问题起始也很好理解, 虽然我们在注册和解注册的时候使用的是同一个客户端对象, 但是通过Binder传递到服务端后却会产生两个全新的对象, , 别忘了对象是不能扩进程直接传输的, 对象的跨进程传输是反序列化的过程. 
       如何解决上述问题呢?  虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象, 但是这些新生成的对象

有一个共同点, 那就是他们底层的Binder对象是同一个, 我们可以利用这个特点来进行解绑

        只需要修改service端的unregister方法即可

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            /*if(mListenerList.contains(listener)) {
                mListenerList.remove(listener);
                Log.i(TAG, "==unregister success.");
            } else {
                Log.i(TAG, "==not found, can not unregister");
            }*/

            Log.d(TAG, "==unregisterListener, current size:==" + mListenerList.size());
            for (IOnNewBookArrivedListener l : mListenerList) {
                if(l.asBinder() == listener.asBinder()) {
                    mListenerList.remove(l);
                    Log.i(TAG, "==unregister success.");
                }
            }
            Log.d(TAG, "==unregisterListener, current size:==" + mListenerList.size());
        }
        再次编译运行就能发现log正常了

service端log:

03-20 21:32:29.250 16805-16819/? D/BookManagerService: ===receive===Android开发艺术探索===price===110
03-20 21:32:29.254 16805-16819/? D/BookManagerService: ==registerListener, size:==1
03-20 21:32:34.210 16805-16879/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:32:34.211 16805-16879/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@5b0f461
03-20 21:32:39.216 16805-16879/? I/BookManagerService: ==onNewBookArrived, nofity listener:1
03-20 21:32:39.217 16805-16879/? I/BookManagerService: ==onNewBookArrived, notify listener:com.aidl.demo.IOnNewBookArrivedListener$Stub$Proxy@5b0f461
03-20 21:32:41.485 16805-16819/? D/BookManagerService: ==unregisterListener, current size:==1
03-20 21:32:41.485 16805-16819/? I/BookManagerService: ==unregister success.
03-20 21:32:41.486 16805-16819/? D/BookManagerService: ==unregisterListener, current size:==0
03-20 21:32:44.220 16805-16879/? I/BookManagerService: ==onNewBookArrived, nofity listener:0
client端log:
03-20 21:32:29.249 16862-16862/? I/MainActivity: ===query book list, list type:java.util.ArrayList
03-20 21:32:29.249 16862-16862/? I/MainActivity: ==query book list:[com.aidl.demo.Book@86d462, com.aidl.demo.Book@3ea79f3]
03-20 21:32:29.252 16862-16862/? I/MainActivity: ==query book list newList:[com.aidl.demo.Book@f026b0, com.aidl.demo.Book@e2a4929, com.aidl.demo.Book@901d4ae]
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : name===Android
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : price===100
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : name===IOS
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : price===150
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : name===Android开发艺术探索
03-20 21:32:29.252 16862-16862/? D/MainActivity: ==temp : price===200
03-20 21:32:29.252 16862-16862/? D/MainActivity: ===book===name===Android开发艺术探索===price===110
03-20 21:32:29.361 1617-1999/? I/KPI-6PA-WMS-6: 6633934 WindowState.performShowLocked() sends empty message FINISHED_STARTING: Token{2160a98 ActivityRecord{2bb5c7b u0 com.wb.aidl_client/.MainActivity t105}}
03-20 21:32:29.364 1617-1857/? I/LaunchCheckinHandler: Displayed com.wb.aidl_client/.MainActivity,cp,ca,420
03-20 21:32:29.364 1617-1857/? I/KPI-6PA-AMS-8: 6633938 windowsDrawn com.wb.aidl_client.MainActivity
03-20 21:32:29.370 1617-1857/? I/ActivityManager: Displayed com.wb.aidl_client/.MainActivity: +416ms
03-20 21:32:29.381 584-584/? D/SFPerfTracer:        layers: (7:13) (NavigationBar#0 (0xb1513000): 0:1699) (StatusBar#0 (0xb1845000): 0:2718) (RoundedOverlay#0 (0xb183c000): 0:54) (RoundedOverlay#1 (0xb15fb000): 0:56) (com.android.systemui.ImageWallpaper#0 (0xb19e6000): 0:134) (AssistPreviewPanel#0 (0xb16d8000): 0:241)* (Sprite#0 (0xb1817000): 0:1)* (com.zui.launcher/com.zui.launcher.Launcher#0 (0xb151d000): 0:33)- (Splash Screen com.wb.aidl_service#0 (0xb181a000): 0:34)- (com.wb.aidl_service/com.wb.aidl_service.MainActivity#0 (0xb152b000): 0:29)- (com.zui.launcher/com.zui.launcher.Launcher#0 (0xb181a000): 0:9) (Splash Screen com.wb.aidl_client#0 (0xb152b000): 5:21) (com.wb.aidl_client/com.wb.aidl_client.MainActivity#0 (0xb180d000): 5:5)* 
03-20 21:32:34.214 16862-16862/? D/MainActivity: ===receive new book:com.aidl.demo.Book@8a7759d
03-20 21:32:39.219 16862-16862/? D/MainActivity: ===receive new book:com.aidl.demo.Book@9911a12
03-20 21:32:41.482 16862-16862/? I/MainActivity: ==unregister listener:==com.wb.aidl_client.MainActivity$2@4a5c5e3
03-20 21:32:47.667 584-584/? D/SFPerfTracer:        layers: (6:11) (NavigationBar#0 (0xb1513000): 0:1712) (StatusBar#0 (0xb1845000): 0:2723) (RoundedOverlay#0 (0xb183c000): 0:54) (RoundedOverlay#1 (0xb15fb000): 0:56) (com.android.systemui.ImageWallpaper#0 (0xb19e6000): 0:136) (AssistPreviewPanel#0 (0xb16d8000): 0:241)* (Sprite#0 (0xb1817000): 0:1)* (com.zui.launcher/com.zui.launcher.Launcher#0 (0xb181a000): 0:10)- (Splash Screen com.wb.aidl_client#0 (0xb152b000): 0:32)- (com.wb.aidl_client/com.wb.aidl_client.MainActivity#0 (0xb180d000): 0:29)- (com.zui.launcher/com.zui.launcher.Launcher#0 (0xb152b000): 1:50) 

        服务器端unregister成功之前是size是1, 之后size是0, 客户端在解绑后, 也不会收到onNewBookArrived的通知了, 到此为止对AIDL的使用基本功能完成了, 剩下的就是灵活应用了


          


猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/79615929