《Android开发艺术探索》第2章- IPC 机制读书笔记

目录

1 Android IPC 简介

1.1 简单介绍一下 Android IPC

IPC 是 Inter-Process Communication 的缩写,含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。IPC 并不是 Android 所独有的,任何一个操作系统都需要有相应的 IPC 机制。

在 Android 中,最有特色的进程间通信方式就是 Binder 了,通过 Binder 可以轻松地实现跨进程通信。除了 Binder,Android 还支持 Socket,通过 Socket 也可以实现任意两个终端之间的通信,以及一个设备上的两个进程之间的通信。

1.2 什么情况下需要使用到多进程?

说到 IPC 就必须提到多进程,多进程的情况分为两种:第一种情况是一个应用因为某些原因自身需要采用多进程模式来实现;另一种情况是当前应用需要向其它应用获取数据。

第一种情况的例子有:应用的某些模块由于特殊原因需要运行在单独的进程里,比如把 WebView 模块放在一个单独的进程里;为了加大一个应用可使用的内存,这是因为 Android 对单个应用可使用的最大内存作了限制。

第二种情况的例子有:两个应用通过跨进程的方式去获取所需的数据,通过系统提供的 ContentProvider 去查询数据。

2 Android 中的多进程模式

2.1 如何开启多进程模式

常规方法:在 AndroidManifest.xml 中给四大组件(ActivityServiceReceiverContentProvider)指定 android:process 属性,来开启多进程模式。

非常规方法:通过 JNI 在 native 层去 fork 一个新的进程。

2.2 查看进程信息的 shell 命令

在 Ubuntu 上的命令:

wangzhichao@wangzhichao:~$ adb shell ps | grep com.wzc.chapter_2
u0_a1022     17463  1048 2261012  52116 SyS_epoll_wait      0 S com.wzc.chapter_2
u0_a1022     17516  1048 2261984  49064 SyS_epoll_wait      0 S com.wzc.chapter_2:remote
u0_a1022     17552  1048 2260840  46256 SyS_epoll_wait      0 S com.wzc.chapter_2.remote

在 Windows 上,需要这样写:adb shell 'ps | grep com.wzc.chapter_2'(用单引号包裹一下)。

下面的写法会输出很多进程的信息,但是会有表头信息:

wangzhichao@wangzhichao:~$ adb shell ps
USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME                       
...
u0_a1022     17463  1048 2261012  51844 SyS_epoll_wait      0 S com.wzc.chapter_2
u0_a1022     17516  1048 2261984  48820 SyS_epoll_wait      0 S com.wzc.chapter_2:remote
u0_a1022     17552  1048 2260968  46264 SyS_epoll_wait      0 S com.wzc.chapter_2.remote
...
wangzhichao@wangzhichao:~$ 

2.3 android:process 指定为 :remotecom.wzc.chapter_2.remote 的区别是什么?

在命名方式上不同:android:process=":remote": 的含义是指要在当前进程名(remote)前面附加上当前的包名(com.wzc.chapter_2),这是一种简写的方式,完整的进程名为 com.wzc.chapter_2:remote

在进程类型上不同:进程名以 : 开头的进程属于当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中;而进程名不以 : 开头的进程属于全局进程,其它应用可以通过 ShareUID 方式和它跑在一个进程中。

需要注意的是,两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要这两个应用有相同的 ShareUID 并且签名相同才可以。

自己测试发现,不能自己指定 android:processcom.wzc.chapter_2:remote,会导致安装失败。

2.4 多进程会造成哪些问题以及原因是什么?

Android 会给每个应用或者说每个进程分配一个独立的虚拟机,而不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机上访问同一个类的对象会产生多份副本。因此,所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。

多进程会造成的问题有:

  • 静态成员和单例模式失效:这是因为多进程下,即便是静态成员和单例,也会有多个副本;
  • 线程同步机制完全失效:这是因为多进程下,不同进程锁的不是一个对象;
  • SharedPreferences 的可靠性下降SharedPreferences 不支持两个进程同时去执行写操作,否则会有一定几率的数据丢失(SharedPreferences 底层是通过读写 xml 文件来实现的,并发写是可能出问题的);
  • Application 会多次创建:这是因为系统在创建新的进程同时会分配新的虚拟机,其实就是启动一个应用的过程,而应用启动会创建新的 Application
    D/MyApplication: application start, process name : com.wzc.chapter_2, process id : 15002
    D/MyApplication: application start, process name : com.wzc.chapter_2:remote, process id : 15081
    D/MyApplication: application start, process name : com.wzc.chapter_2.remote, process id : 15233
    

需要注意的一点是,在多进程下抛出的异常信息,只能在本进程下查看。

3 IPC 基础概念介绍

3.1 使用 Serializable 接口需要注意的地方有哪些?

手动指定 serialVersionUIDserialVersionUID 是用来辅助序列化和反序列化过程。它的工作机制是:序列化时会把当前类的 serialVersionUID 写入到文件中(或其他中介),反序列化时会去检测文件中的 serialVersionUID 是否跟当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和反序列化的类的版本是一致的,这个时候可以成功反序列化,否则说明当前类和序列化的类相比发生了某些变换,无法正常反序列化。为什么要手动指定 serialVersionUID 呢?如果不手动指定,那么系统会根据类结构生成一个 serialVersionUID,只要类结构发生了变化,就可能得到一个不同的 serialVersionUID。而手动指定后,程序能够最大限度地恢复数据

静态成员变量属于类不属于对象,不会参与序列化过程;

使用transient 关键字修饰的成员变量不参与序列化过程;

重写系统默认的序列化和反序列化过程,通过实现下面的方法:

private void writeObject(ObjectOutputStream oos) throws IOException {
    oos.defaultWriteObject(); // 注意:这个是默认的,必须要写
    System.out.println("writeObject");
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ois.defaultReadObject(); // 注意:这个是默认的,必须要写
    System.out.println("readObject");
}

3.2 说一说Parcelable 序列化和反序列化过程

从一个实际的实现了 Parcelable 接口的类来看:

public class Person implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;

    public Person(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    @Override
    public String toString() {
        return "Person{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", isMale=" + isMale +
                '}';
    }
	// 从序列化的对象中创建原始对象
    protected Person(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
    }
	// 注意 CREATOR 是一个静态的 final 类型的字段
    public static final Creator<Person> CREATOR = new Creator<Person>() {
    	// 从序列化的对象中创建原始对象
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }
		// 创建指定长度的原始对象的数组
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
	// 返回当前对象的文件描述符
    @Override
    public int describeContents() {
        return 0;
    }
	// 将当前对象写入序列化结构中。
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeInt(isMale ? 1 : 0);
    }

}

Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输。

序列化功能由 writeToParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法来完成的。其中 int flags 只有两种值:0 或者 1 (即Parcelable.PARCELABLE_WRITE_RETURN_VALUE)。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0

反序列化功能由 CREATOR 来完成,其内部标明了如何创建反序列化对象(createFromParcel 方法)和数组(newArray 方法),通过一系列 read 方法来完成反序列化过程。

内容描述符功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回 0,仅当当前对象中存在文件描述符时,此方法返回 1(即 Parcelable.CONTENTS_FILE_DESCRIPTOR)。

3.3 使用 Parcelable 实现序列化到存储设备上

序列化:

Person p = new Person(1, "hello", false);
FileOutputStream fos = null;
try {
    File dir = new File(MyConstants.CHAPTER_2_PATH);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File cacheFile = new File(MyConstants.CACHE_PARCELABLE_FILE_PATH);
    fos = new FileOutputStream(cacheFile, false);
    Parcel parcel = Parcel.obtain();
    parcel.writeParcelable(p, 0);
    // 获取 Parcel 对象的字节数组
    byte[] bytes = parcel.marshall();
    fos.write(bytes);
    fos.flush();
    parcel.recycle();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    MyUtils.closeQuietly(fos);
}

反序列化:

File dir = new File(MyConstants.CHAPTER_2_PATH);
if (!dir.exists()) {
    dir.mkdirs();
}
File cacheFile = new File(MyConstants.CACHE_PARCELABLE_FILE_PATH);
FileInputStream fis = null;
try {
    fis = new FileInputStream(cacheFile);
    byte[] bytes = new byte[fis.available()];
    fis.read(bytes);
    Parcel parcel = Parcel.obtain();
    parcel.unmarshall(bytes, 0, bytes.length);
    parcel.setDataPosition(0);
    Person person = parcel.readParcelable(Thread.currentThread().getContextClassLoader());
    Log.d(TAG, "recoverFromFileByParcelable: person = " + person);
    parcel.recycle();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    MyUtils.closeQuietly(fis);
}

需要说明的是,Android 官方文档里说把 Parcel 数据放在持久化存储上是不合适的:

Parcel is not a general-purpose serialization mechanism. This class (and the corresponding Parcelable API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport. As such, it is not appropriate to place any Parcel data in to persistent storage: changes in the underlying implementation of any of the data in the Parcel can render older data unreadable.

3.4 比较 ParcelableSerializable

相同点:它们都能实现序列化并且都用于 Intent 间的数据传递。
不同点:

  • 所属不同:Serializable 是 Java 中的序列化接口,Parcelable 是 Android 中的序列化接口。
  • 使用难易不同:Serializable 使用简单,而Parcelable 使用起来有些麻烦。
  • 效率不同:Serializable 开销很大,因为序列化和反序列化过程在 Java 层实现,要大量的 I/O 操作;Parcelable 内存开销小, 在 native 层实现,效率很高,是 Android 推荐的序列化方式。
  • 使用场景不同:Serializable 使用广泛,包括内存序列化,序列化到存储设备以及序列化后通过网络传输;而 Parcelable 主要用于内存序列化,对于序列化到存储设备以及序列化后通过网络传输实现有些复杂。

参考:每日一问 Parcelable 为什么效率高于 Serializable ?

3.5 说一说 Binder 是什么?

  • 从代码上看, Binder是 Android 中的一个类,它实现了IBinder接口;

    public class Binder implements IBinder
    
  • 从 IPC 角度来看,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,在 Linux 中没有这种通信方式;

  • 从 Android Framework 角度来看,BinderServiceManager 连接各种 ManagerActivityManagerWindowManager 等)和相应的 ManagerServiceActivityManagerServiceWindowManagerService 等)的桥梁;

  • 从 Android 应用层角度来看,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,客户端通过这个 Binder 对象就可以获取服务端提供的服务或者数据。

3.6 给定一个 IBookManager.aidl,绘制生成的 IBookManager.java 的类结构图

IBookManager.aidl 如下:

package com.wzc.chapter_2_common_lib;

import com.wzc.chapter_2_common_lib.Book;
interface IBookManager {
    // 从远程服务端获取图书列表
    List<Book> getBookList();
    // 往图书列表中添加一本书, in 表示输入型参数
    void addBook(in Book book);
}

类图如下:

«interface» IInterface +asBinder() : IBinder «interface» IBookManager +getBookList() : List<Book> +addBook(Book book) : void «interface» IBinder +getInterfaceDescriptor() : String +pingBinder() : boolean +isBinderAlive() : boolean +queryLocalInterface(String descriptor) : IInterface +transact(int code, Parcel data, Parcel reply, int flags) : boolean +linkToDeath(DeathRecipient recipient, int flags) : void +unlinkToDeath(DeathRecipient recipient, int flags) : boolean Binder +queryLocalInterface(String descriptor) : IInterface +transact(int code, Parcel data, Parcel reply, int flags) : boolean BinderProxy +attachInterface(IInterface owner, String descriptor) : void +onTransact(int code, Parcel data, Parcel reply, int flags) : void «abstract» Stub -String DESCRIPTOR int TRANSACTION_getBookList int TRANSACTION_addBook +asInterface(IBinder obj) : IBookManager +asBinder() : IBinder +onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) : boolean Proxy +asBinder() : IBinder +getInterfaceDescriptor() : String +getBookList() : List<Book> +addBook(Book book) : void Inheritance Realization Realization Inheritance Realization Realization

所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。

  • DESCRIPTOR

    Binder 的唯一标识,一般用当前 Binder 的类名表示。

  • Stub 类的public static IBookManager asInterface(android.os.IBinder obj)

    这是个静态方法,作用是将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象。需要特别注意的是,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.Proxy 对象。

  • Binder asBinder() 方法

    用于返回当前的 Binder 对象,就是在服务端的创建的 Stub 类匿名内部类,会作为 onBind() 方法的返回值返回。

  • Stub 类的boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 方法

    运行在服务端的 Binder 线程池里面,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。

    服务端会通过 code 来确定客户端请求的目标方法是什么,接着从 data 里取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值。

    这个方法返回 false,那么客户端的请求会失败,所以可以利用这个特性来做权限验证。

  • Proxy 类的 Book getBookList() 方法

    这个方法运行在客户端,是给客户端调用的。内部实现是:1, 先创建该方法远程调用所需要的输入型 Parcel 对象 _data,输出型 Parcel 对象 _reply 和返回值对象 List;2, 然后把方法的参数写入 _data 中(如果有参数的话);3, 调用 mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0) 来发起 RPC (远程过程调用)请求,同时当前线程被挂起;4, 然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果;5, 返回从 _reply 中取出的数据。

  • Proxy 类的 void addBook(Book book) 方法

    这个方法运行在客户端,没有返回值,有参数,其余和 getBookList() 方法一致。

3.7 远程方法是耗时方法,客户端和服务端分别如何处理?

客户端:因为客户端发起远程请求时,当前线程会被挂起直至服务端进程返回数据,因此远程方法是耗时方法,客户端就不能在 UI 线程发起远程请求。

服务端:因为服务端的 Binder 方法运行在 Binder 的线程池里,所以 Binder 方法不管是否耗时都应该采用同步的方式实现。

3.8 不借助 aidl,手写出 Binder

采用分离的方式来写,而不是像系统生成的那样存在多层类嵌套。

1,创建继承 IInterface 接口的 IBookManager 接口:

public interface IBookManager extends IInterface {
    String DESCRIPTOR = "com.wzc.chapter_2.manualbinder2.IBookManager";
    int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 0;
    int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 1;

    void addBook(Book book) throws RemoteException;

    List<Book> getBookList() throws RemoteException;
}

2,创建抽象类 Stub,继承于 Binder 类,实现于 IBookManager 接口:

public abstract class Stub extends Binder implements IBookManager {
    private static final String TAG = "Stub";

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    // 这个方法的参数至关重要
    public static IBookManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
        if (iInterface != null && iInterface instanceof IBookManager) {
            return (IBookManager) iInterface;
        }
        return new Proxy(obj);
    }

    // 这个方法运行在服务端中的 Binder 线程池里面
    // 这个方法是在调用了 IBinder 的 transact 方法后调用的
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case TRANSACTION_addBook:
                Log.d(TAG, "onTransact: TRANSACTION_addBook, currThread="
                        + Thread.currentThread().getName());
                data.enforceInterface(DESCRIPTOR);
                Book book;
                if (0 != data.readInt()) {
                    book = Book.CREATOR.createFromParcel(data);
                } else {
                    book = null;
                }
                addBook(book);
                reply.writeNoException();
                return true;
            case TRANSACTION_getBookList:
                Log.d(TAG, "onTransact: TRANSACTION_getBookList, currThread="
                        + Thread.currentThread().getName());
                data.enforceInterface(DESCRIPTOR);
                List<Book> _result = getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }
}

3,创建 Proxy 类,实现了 IBookManager 接口:

public class Proxy implements IBookManager {

    private static final String TAG = "Proxy";
    private IBinder mRemote;

    public Proxy(IBinder remote) {
        this.mRemote = remote;
    }

    // 这个方法运行在客户端
    @Override
    public void addBook(Book book) throws RemoteException {
        Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName());
        // 输入型 Parcel 对象
        Parcel _data = Parcel.obtain();
        // 输出型 Parcel 对象
        Parcel _reply = Parcel.obtain();
        try {
            _data.writeInterfaceToken(IBookManager.DESCRIPTOR);
            if (book != null) {
                _data.writeInt(1);
                book.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName()
                    + ", 远程请求开始");
            mRemote.transact(TRANSACTION_addBook, _data, _reply, 0);
            Log.d(TAG, "addBook: book=" + book + ", currThread=" + Thread.currentThread().getName()
                    + ", 远程请求结束");
            _reply.readException();
        } finally {
            _data.recycle();
            _reply.recycle();
        }

    }

    // 这个方法运行在客户端
    @Override
    public List<Book> getBookList() throws RemoteException {
        Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName());
        // 输入型 Parcel 对象
        Parcel _data = Parcel.obtain();
        // 输出型 Parcel 对象
        Parcel _reply = Parcel.obtain();

        List<Book> _result;
        try {
            _data.writeInterfaceToken(IBookManager.DESCRIPTOR);
            Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName() + ", 远程请求开始");
            mRemote.transact(TRANSACTION_getBookList, _data, _reply, 0);
            Log.d(TAG, "getBookList: currThread=" + Thread.currentThread().getName() + ", 远程请求结束");
            _reply.readException();
            _result = _reply.createTypedArrayList(Book.CREATOR);
        } finally {
            _data.recycle();
            _reply.recycle();
        }
        return _result;
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }
}

手写的好处:方便通过添加日志的方式,来了解哪个方法运行在客户端进程,哪个方法运行在服务端的 Binder 线程池里面;明白了 AIDL 文件的本质是系统为我们提供的一种快速实现 Binder 的工具。

3.9 什么是 Binder 连接断裂(也叫 Binder 死亡)?

Binder 运行在服务端进程,如果服务端进程因为某种原因异常终止,这时候连接到服务端的 Binder 连接断裂(也叫 Binder 死亡),会导致我们的远程调用失败。

如果客户端不知道 Binder 已经死亡,还去执行相关业务,就会影响客户端的功能。所以,我们希望有一种方法,可以在 Binder 死亡时,通知给客户端,客户端拿到通知后,做一些处理:比如提醒用户重新发起服务绑定,或者自动再次进行服务绑定。这就是设置死亡代理的内容了。

3.10 如何给 Binder 设置死亡代理?

1,声明一个 DeathRecipient 对象,DeathRecipient 是一个接口,其内部只有一个 binderDied 方法。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    // 当 Binder 死亡的时候,系统就会回调 binderDied 方法
    @Override
    public void binderDied() {
        Log.d(TAG, "binderDied: currentThread = " + Thread.currentThread().getName()); // Binder:25808_2
        if (mBookManager == null) {
            return;
        }
        // 移除之前设置的死亡代理
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mBookManager = null;
        // 重新绑定远程服务
        Intent intent = new Intent(ManualBookManagerActivity.this, ManualBookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }
};

2,在客户端绑定远程服务成功后,给 Binder 设置死亡代理:

public void onServiceConnected(ComponentName name, IBinder service) {
    mBookManager = Stub.asInterface(service);
    try {
        // 在客户端绑定远程服务成功后,给 binder 设置死亡代理。
        mBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

通过 Binder 类的 isBinderAlive() 方法也可以判断 Binder 是否死亡。

4 Android 中的 IPC 方式

4.1 A 进程正在进行一个计算,计算完成后需要启动 B 进程的一个组件并把计算结果传递给 B 进程,但是计算结果不支持放在 Bundle 里面,该如何处理?

既然计算结果不支持放入 Bundle 里面,就无法通过 Intent 来传输。可以考虑通过 Intent 启动 B 进程的一个 Service 组件,让 Service 在后台执行计算,计算完成后再启动 B 进程中真正要启动的那个目标组件。由于 Service运行在 B 进程中,因此目标组件可以直接获取计算结果。这是采用了避免进程间通信的办法。

4.2 SharedPreferences 在多进程通信中使用有什么弊端,为什么?

从本质上来说,SharedPreferences 属于文件的一种,但是系统对它的读/写有一定的缓存策略,也就是说在内存中会有一份 SharedPreferences 文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问时,SharedPreferences有很大几率丢失数据,因此,不建议在进程间通信中使用 SharedPreferences。可以考虑使用 mmkv。

参考:官方也无力回天?“SharedPreferences 存在什么问题?”

4.3 实现一个 Messenger 的步骤

包括客户端向服务端发送消息以及服务端向客户端回传消息的功能:

1,构建服务端进程

// 1-1, 在服务端创建一个 Service,来处理客户端的连接请求
public class MessengerService extends Service {
    private static final String TAG = MessengerService.class.getSimpleName();

    // 服务端要持有一个 Handler 来处理来自客户端的调用
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                // 处理来自客户端的消息
                case MyConstants.MSG_FROM_CLIENT:
                    Log.d(TAG, "handleMessage: currThread=" + Thread.currentThread().getName());
                    Log.d(TAG, "handleMessage: receive msg from client : " + msg.getData().getString(MyConstants.MSG));
                    // 3-4, 从 msg.replyTo 中取出处理服务端消息的 Messenger 对象
                    Messenger replyMessenger = msg.replyTo;
                    Log.d(TAG, "handleMessage: replyMessenger=" + replyMessenger);
                    try {
                        Field mTargetField = Messenger.class.getDeclaredField("mTarget");
                        mTargetField.setAccessible(true);
                        Object o = mTargetField.get(replyMessenger);
                        Log.d(TAG, "handleMessage: mTarget=" + o);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, "handleMessage: replyMessenger.getBinder()=" + replyMessenger.getBinder());
//                    Book book = (Book) msg.obj;
//                    Log.d(TAG, "handleMessage: book : " + book);
                    Point point = (Point) msg.obj;
                    Log.d(TAG, "handleMessage: point : " + point);
                    Message replyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString(MyConstants.REPLY, "嗯, 你的消息我已经收到, 稍后会回复你.");
                    replyMessage.setData(bundle);
                    // 3-5, 向客户端发送消息
                    try {
                        replyMessenger.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    // 1-2, 使用 Handler 来创建一个 Messenger 对象
    private Messenger mMessenger = new Messenger(new MessengerHandler());

    @Override
    public IBinder onBind(Intent intent) {
        // 1-3, 返回 Messenger 对象所持有的 Binder 对象
        return mMessenger.getBinder();
    }
}

2,构建客户端进程

public class MessengerActivity extends Activity {
    private static final String TAG = MessengerActivity.class.getSimpleName();
    private AtomicBoolean mBound = new AtomicBoolean(false);
    private Messenger mMessenger;

    // 3-1, 创建处理来自服务端消息的Handler
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "handleMessage: currThread=" + Thread.currentThread().getName());
            // 2-6, 处理从服务端返回的消息
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVICE:
                    Log.d(TAG, "handleMessage: receive msg from service : " + msg.getData().getString(MyConstants.REPLY));
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    // 3-2, 创建处理服务端消息的 Messenger
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 2-2, 绑定成功后,使用服务端返回过来的 IBinder 对象来初始化一个 Messenger 对象
            mMessenger = new Messenger(service);
            mBound.set(true);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mMessenger = null;
            mBound.set(false);
        }
    };

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

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message sendMessage = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
                // 非系统的Parcelable对象不能通过obj传输  Caused by: java.lang.ClassNotFoundException: com.wzc.chapter_2_common_lib.Book
//                Book book = new Book(1, "wzc");
//                sendMessage.obj = book;
                // 系统的Parcelable对象能够通过obj传输
                sendMessage.obj =  new Point(4, 6);
                Bundle bundle = new Bundle();
                bundle.putString(MyConstants.MSG, "hello, this is client.");
                sendMessage.setData(bundle);
                // 3-3, 把处理服务端消息的 Messenger ,通过msg.replyTo,带给服务端
                Log.d(TAG, "mGetReplyMessenger=" + mGetReplyMessenger + ",looper="+ Looper.myLooper());
                sendMessage.replyTo = mGetReplyMessenger;
                try {
                    Field mTargetField = Messenger.class.getDeclaredField("mTarget");
                    mTargetField.setAccessible(true);
                    Object o = mTargetField.get(mGetReplyMessenger);
                    Log.d(TAG, "onClick: mTarget=" + o);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Log.d(TAG, "onClick: mGetReplyMessenger.getBinder()=" + mGetReplyMessenger.getBinder());
                try {
                    // 2-3 通过创建好的 Messenger 对象,向服务端发送消息, 发起远程调用
                    mMessenger.send(sendMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 2-1, 在客户端进程中,首先要绑定服务端的 Service
        Intent service = new Intent(this, MessengerService.class);
        bindService(service, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mBound.get()) {
            unbindService(mConnection);
            mBound.set(false);
        }
    }
}

4.4 Message 的 Object obj 字段的使用有什么特殊之处?

  • 在同一个进程中时,object 字段非常实用,也就是说任何对象多可以赋值给 object 对象;
  • 在进程间通信时,在 Android 2.2 之前 object 字段不支持跨进程通信,在 2.2 之后,仅仅是系统提供的实现了 Parcelable 接口的对象才能通过它来传输。因此,自定义的 Parcelable 对象无法通过 object 字段传输。解决办法是,通过 Bundle 来传输:
    public final class Message implements Parcelable {
    	Bundle data;
    	public Bundle getData() {
    	    if (data == null) {
    	        data = new Bundle();
    	    }
    	    
    	    return data;
    	}
    	public void setData(Bundle data) {
        	this.data = data;
    	}
    }
    

4.5 AIDL 文件支持哪些数据类型?

  • 基本数据类型(intlongcharbooleandouble 等);
  • StringCharSequence
  • List:只支持 ArrayList,并且里面的每个元素都必须能够被 AIDL 支持;
  • Map:只支持 HashMap,并且里面的每个元素都必须 能够被 AIDL 支持,包括 key 和 value;
  • Parcelable:所有实现了 Parcelable 接口的对象;
  • AIDL:所有 AIDL 接口本身也可以在 AIDL 文件中使用。

4.6 使用 AIDL 需要注意的地方有哪些?

  1. 当定义 AIDL 文件时,对于文件需要的自定义的 Parcelable 对象和 AIDL 对象必须要显式 import 进来,不管它们是否和定义的 AIDL 文件位于同一个包内。
  2. 当定义 AIDL 文件时,如果用到了自定义的 Parcelable 对象,那么必须新建一个和它同名的 AIDL 文件,并在其中声明它为 Parcelable 类型。
    比如,对于 Book.java 这个自定义的 Parcelable 类,
    package com.wzc.chapter_2_common_lib;
    import android.os.Parcel;
    import android.os.Parcelable;
    public class Book implements Parcelable {
    }
    
    如果要在 AIDL 文件中使用它,就新建一个 Book.aidl 文件:
    // Book.aidl
    package com.wzc.chapter_2_common_lib;
    
    parcelable Book;
    
  3. 对于 AIDL 中方法参数中使用到的数据类型,除了基本数据类型,其他类型必须标上方向:inoutinoutin 表示输入型参数,out 表示输出型参数,inout 表示输入输出型参数。
  4. 当定义 AIDL 文件时,只能声明方法,不能声明静态变量,这一点和传统的接口是不一样的。
  5. 在实际开发中,建议把所有和 AIDL 相关的类和文件全部放在一个包中(对于使用 AndroidStudio 开发,可以放在同一个 module 里面),这样可以方便客户端和服务端依赖使用。
  6. AIDL 文件的包结构在客户端和服务端要保持一致,否则会反序列化失败。
  7. 当需要通过 AIDL 文件中定义的方法设置监听器或取消监听器时,不能使用普通的接口,而要使用 AIDL 接口,这是因为在 AIDL 中不能使用普通接口。
  8. 如果在 AIDL 文件中定义了几个接口,即以 interface 开头的,那么就生成几套对应的 Java 类(包括接口名.java,Stub 类,以及 Proxy 类)。
  9. AIDL 文件中不能使用中文注释,如需注释请使用英文,避免出现 AIDL 生成 java 文件缺失或为空的情况。(参考:Android生成远程服务AIDL对应的java文件失败原因

4.7 当客户端需要监听服务端的一些操作时,会通过注册监听器的方式来实现,那么服务端如何实现解除注册的监听器呢?

如果我们打算在服务端使用普通的 List 来保存注册的监听器对象,而在服务端解除注册时从这个集合里移除注册的监听器,这是不会成功的。但是,在解注册时,传递过来的监听器对象在集合里是不存在的,尽管客户端在注册和解注册时传递的监听器是同一个。这是因为 Binder 会把客户端传递过来的对象重新转化为一个新的对象。记住:对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程。

我们需要使用 RemoteCallbackList,它是系统提供的专门用于管理跨进程监听器。

public class RemoteCallbackList<E extends IInterface>

在它的内部有一个 Map 结构专门用于保存所有的 AIDL 回调,这个 MapkeyIBinder 类型,valueCallback 类型:

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

Callback 中封装了真正的远程 listener

public boolean register(E callback, Object cookie) {
    synchronized (mCallbacks) {
        if (mKilled) {
            return false;
        }
        // 获取 listener 对象底层的 Binder 对象
        IBinder binder = callback.asBinder();
        try {
            // 使用 Callback 类来封装 listener 对象
            Callback cb = new Callback(callback, cookie);
            binder.linkToDeath(cb, 0);
            mCallbacks.put(binder, cb);
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }
}

虽然监听器对象不是同一个了,但是它们底层的 Binder 对象是同一个,这点是非常重要的。

@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
    // IOnNewBookArrivedListener 在注册与解注册是不同的,但是它们底层的 IBinder 是同一个。
    Log.d(TAG, "registerListener: listener = " + listener); // com.wzc.chapter_2_common_lib.IOnNewBookArrivedListener$Stub$Proxy@249af64
    Log.d(TAG, "registerListener: listener.asBinder() = " + listener.asBinder()); // android.os.BinderProxy@82855cd
}
@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
    Log.d(TAG, "unregisterListener: listener = " + listener); // com.wzc.chapter_2_common_lib.IOnNewBookArrivedListener$Stub$Proxy@cdf2e82
    Log.d(TAG, "unregisterListener: listener.asBinder() = " + listener.asBinder()); // android.os.BinderProxy@82855cd
}

解除注册:

public boolean unregister(E callback) {
    synchronized (mCallbacks) {
    	// 删除和要解除注册的 listener 有相同的 Binder 对象的那条记录就可以了。
        Callback cb = mCallbacks.remove(callback.asBinder());
        if (cb != null) {
            cb.mCallback.asBinder().unlinkToDeath(cb, 0);
            return true;
        }
        return false;
    }
}

RemoteCallbackList 正是利用了不同的 listener 底层的 Binder 对象是一样的这一特性,并借助死亡代理(IBinder.DeathRecipient)来实现删除 listener 的。

RemoteCallbackList 通过使用 synchronized 同步代码块方式显示了线程同步的功能。

RemoteCallbackList 还包括了遍历监听器的方法,代码如下:

final int n = mListenerList.beginBroadcast();
for (int i = 0; i < n; i++) {
    IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
    if (listener != null) {
        listener.onNewBookArrived(newBook);
    }
}
mListenerList.finishBroadcast();

需要注意的是 beginBroadcast()finishBroadcast() 必须成对使用。

4.8 说一说客户端和服务端进行通信时,一些方法或回调运行的线程

方法运行线程
ServiceConnectiononServiceConnected()main
ServiceConnectiononServiceDisconnected()main
IBinder.DeathRecipientbinderDied()Binder 线程池
ProxygetBookList()客户端线程
ProxyaddBook()客户端线程
ProxyregisterListener()客户端线程
ProxyunregisterListener()客户端线程
IBookManager.StubonTransact()服务端的 Binder 线程池
IBookManager.StubgetBookList()服务端的 Binder 线程池
IBookManager.StubaddBook()服务端的 Binder 线程池
IBookManager.StubregisterListener()服务端的 Binder 线程池
IBookManager.StubunregisterListener()服务端的 Binder 线程池
IOnNewBookArrivedListener.StubonNewBookArrived()客户端的 Binder 线程池
IOnNewBookArrivedListeneronNewBookArrived()服务端线程

4.9 如何在 AIDL 总使用权限验证功能?

第一种方法,在 onBind 方法中验证,验证不通过就直接返回 null,验证通过才返回 Stub 对象。
第二种方法,在服务端的 onTransact 方法中验证,验证失败就返回 false,这时服务端会终止执行 AIDL 中的方法。

4.10 ContentProvider 设置单独的进程,那么它的方法分别运行在什么线程?

6 个方法都运行在 ContentProvider 的进程里,其中 onCreate 运行在主线程,其他 5 个方法(querygetTypeinsertdeleteupdate)运行在 Binder 线程池里。需要注意的是,queryinsertdeleteupdate 四个方法是存在多线程并发访问的,因此这些方法内部要做好线程同步。

4.11 ContentProvider 如何实现支持自定义调用?

通过 ContentResolvercall 方法和 ContentProvidercall 方法来完成。

5 Binder 连接池

5.1 如何实现 Binder 连接池?

  1. 定义一个 IBinderPool.aidl 文件
    // IBinderPool.aidl
    package com.wzc.chapter_2_common_lib.binderpool;
    
    // Declare any non-default types here with import statements
    
    interface IBinderPool {
    
       IBinder queryBinder(int binderCode);
    }
    
  2. 定义一个 BinderPool.java 文件
    public class BinderPool {
        private static final String TAG = BinderPool.class.getSimpleName();
    
        public static final int BINDER_NONE = -1;
        public static final int BINDER_COMPUTE = 0;
        public static final int BINDER_SECURITY_CENTER = 1;
        private static volatile BinderPool sInstance;
        private Context mContext;
        private IBinderPool mBinderPool;
        private CountDownLatch mConnectBinderPoolCountDownLatch;
    
        private BinderPool(Context context) {
            mContext = context.getApplicationContext();
            connectBinderPoolService();
        }
    
        public static BinderPool getInstance(Context context) {
            if (sInstance == null) {
                synchronized (BinderPool.class) {
                    if (sInstance == null) {
                        sInstance = new BinderPool(context);
                    }
                }
            }
            return sInstance;
        }
    
        private synchronized void connectBinderPoolService() {
            Log.d(TAG, "connectBinderPoolService: current thread = " + Thread.currentThread().getName());
    
            mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
    
            Intent intent = new Intent();
            intent.setComponent(new ComponentName("com.wzc.chapter_2", "com.wzc.chapter_2.binderpool.BinderPoolService"));
            mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    
            try {
                mConnectBinderPoolCountDownLatch.await(); // 等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        // 从 Binder 连接池中,根据 binderCode 获取对应的 Binder 对象
        public IBinder queryBinder(int binderCode) {
            IBinder binder = null;
            if (mBinderPool != null) {
                try {
                    binder = mBinderPool.queryBinder(binderCode);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            return binder;
        }
    
        private final ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.d(TAG, "onServiceConnected: current thread = " + Thread.currentThread().getName());
                mBinderPool = IBinderPool.Stub.asInterface(service);
                try {
                    // 给 Binder 设置一个死亡代理
                    mBinderPool.asBinder().linkToDeath(mDeathRecipient,0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mConnectBinderPoolCountDownLatch.countDown();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                Log.d(TAG, "binderDied: ");
                if (mBinderPool == null) {
                    return;
                }
                mBinderPool.asBinder().unlinkToDeath(mDeathRecipient, 0);
                mBinderPool = null;
                connectBinderPoolService(); // 重新绑定远程服务
            }
        };
    
        public static class BinderPoolImpl extends IBinderPool.Stub {
    
            public BinderPoolImpl() {
                super(); // 这个super不可以去掉
            }
    
            @Override
            public IBinder queryBinder(int binderCode) throws RemoteException {
                IBinder binder = null;
                switch (binderCode) {
                    case BINDER_COMPUTE:
                        binder = new ComputeImpl();
                        break;
                    case BINDER_SECURITY_CENTER:
                        binder = new SecurityCenterImpl();
                        break;
                    default:
                        break;
                }
                return binder;
            }
        }
    }
    
    

5.2 Binder 连接池的作用是什么?

将每个业务模块的 Binder 请求统一转发到远程 Service 中执行,从而避免重复创建 Service 的过程。

6 选用合适的 IPC 方式

6.1 Android 中有哪些 IPC 方式,以及优缺点是什么?

IPC 方式特点优点缺点
使用 Bundle基于序列化的思想四大组件中的三大组件(ActivityServiceReceiver)都支持在 Intent 中传递 Bundle 数据,Bundle 实现了 Parcelable 接口,可以方便地在不同进程间传输。Bundle 不支持的类型无法通过 Bundle 来进行跨进程通信
使用文件共享文件共享使用方便,A 进程把数据写入文件,B 进程通过读取这个文件来获取数据;对文件格式没有具体要求,只要读/写双方约定好数据格式即可。文件共享方式只适合在对数据同步要求不高的进程之间进行通信,并且要处理好并发读/写问题。
使用 Messenger轻量级的 IPC 方案,它的底层实现是 AIDL1,通过 Messenger,可以在不同进程中传递 Message对象,因此只要把要传递的数据放在 Message 中就可以轻松实现数据的进程间传递;2,一次只处理一个请求,不会造成服务端并发执行的情形,因此在服务端不用考虑线程同步的问题。1,传输的数据类型需要是 Bundle 支持的类型,2,Messenger是以串行的方式处理客户端的消息,不适用于有大量并发请求发送到服务端的情形;3,Messenger 只实现了消息的传递,无法做到跨进程调用服务端的方法。
使用 AIDLBinder 机制可以做到跨进程调用服务端的方法实现过程有些复杂
使用 ContentProvider使用过程比 AIDL 要简单在数据源访问方面功能强大,支持一对多并发数据共享,可通过 call 方法扩展其他操作可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作。
Socket网络数据交换类功能强大,可以通过网络传输字节流,支持一对多并发实时通信实现细节稍微有点复杂,不支持直接的 RPC.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值