Android开发的艺术探索第二章

百般思念 提交于 2020-01-04 14:21:08

IPC机制

2.1 Android IPC简介

IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指 两个进程之间进行数据交换的过程。
IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制,比如 Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信;Linux上可以通过命名管 道、共享内容、信号量等来进行进程间通信。可以看到不同的操作系统平台有着不同的进 程间通信方式,对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间 通信方式并不能完全继承自Linux,相反,它有自己的进程间通信方式。在Android中最有 特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。除了 Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然同 一个设备上的两个进程通过Socket通信自然也是可以的。

2.2 Android中的多进程模式

通过给四 大组件指定android:process属性,我们可以轻易地开启多进程模式,这看起来很简单,但 是实际使用过程中却暗藏杀机,多进程远远没有我们想的那么简单,有时候我们通过多进 程得到的好处甚至都不足以弥补使用多进程所带来的代码层面的负面影响。

2.2.1 开启多进程模式

开启进程,可以通过给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定 android:process属性,或者还可以通过JNI在 native层去fork一个新的进程,但是这种方法属于特殊情况,也不是常用的创建多进程的 方式,因此我们暂时不考虑这种方式。运行多进程App,我们可以通过DDMS视图中查看进程信息,还可以用shell来查看,命令为:adb shell ps或者adb shell ps | grep 包名,通过ps命令也可以查看一个包名中当前所存在的进程信息。
Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共 享数据。这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要 这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问 对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。当然如 果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据, 或者说它们看起来就像是一个应用的两个部分。

2.2.2 多进程模式的运行机制

我们知道Android为 每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同 的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对 象会产生多份副本。
一般来说,使用多进程会造成如下几方面的问题:
(1)静态成员和单例模式完全失效。
(2)线程同步机制完全失效。
(3)SharedPreferences的可靠性下降。
(4)Application会多次创建。

为了解决多进程的问题,系统提供了很多跨进程通信方法,虽然说不能直接地共享内 存,但是通过跨进程通信我们还是可以实现数据交互。实现跨进程通信的方式很多,比如 通过Intent来传递数据,共享文件和SharedPreferences,基于Binder的Messenger和AIDL以 及Socket等。

2.3 IPC基础概念介绍

Serializable接口、 Parcelable接口以及Binder。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过 Intent和Binder传输数据时就需要使用Parcelable或者Serializable。

2.3.1 Serializable接口

内容代码比较简单,这里不阐述了;特别要注意serialVersionUID 类似版本号,用于反序列化;当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否 和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是 相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变 换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。

2.3.2 Parcelable接口

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可 以实现序列化并可以通过Intent和Binder传递。

public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;

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

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(userId);
        out.writeString(userName);
        out.writeInt(isMale ? 1 : 0);
        out.writeParcelable(book, 0);
    }

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

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

    private User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }
}

Parcel内部包装了可序列化的数据,可以在Binder中自由传输。 从上述代码中可以看出,在序列化过程中需要实现的功能有序列化、反序列化和内容描 述。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完 成的;反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并 通过Parcel的一系列read方法来完成反序列化过程;内容描述功能由describeContents方法 来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时, 此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一个可序列化对 象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的 错误。
系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化 的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每 个元素都是可序列化的。
既然Parcelable和Serializable都能实现序列化并且都可用于Intent间的数据传递,那么 二者该如何选取呢?**Serializable是Java中的序列化接口,其使用起来简单但是开销很大, 序列化和反序列化过程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更 适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,这是Android推荐的序列化方式,**因此我们要首选Parcelable。Parcelable主要用在内存序列化 上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是 可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable。以上 就是Parcelable和Serializable和区别。

2.3.3 Binder

Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说, Binder是Android中的一种跨进程通信方式;从Android Framework角度来说, Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和 相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的 媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通 过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通 服务和基于AIDL的服务。

再次我们分析下自动生成的IBookManager.java类

public interface IBookManager extends android.os.IInterface {
    /**
     * Default implementation for IBookManager.
     */
    public static class Default implements com.rk.myservices.IBookManager {
        @Override
        public java.util.List<com.rk.myservices.Book> getBookList() throws android.os.RemoteException {
            return null;
        }

        @Override
        public void addBookInOut(com.rk.myservices.Book book) throws android.os.RemoteException {
        }

        @Override
        public android.os.IBinder asBinder() {
            return null;
        }
    }

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.rk.myservices.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.rk.myservices.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.rk.myservices.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.rk.myservices.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.rk.myservices.IBookManager))) {
                return ((com.rk.myservices.IBookManager) iin);
            }
            return new com.rk.myservices.IBookManager.Stub.Proxy(obj);
        }

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

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.rk.myservices.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBookInOut: {
                    data.enforceInterface(descriptor);
                    com.rk.myservices.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.rk.myservices.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBookInOut(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.rk.myservices.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

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

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.rk.myservices.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.rk.myservices.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getBookList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.rk.myservices.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBookInOut(com.rk.myservices.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInOut, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addBookInOut(book);
                        return;
                    }
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            public static com.rk.myservices.IBookManager sDefaultImpl;
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

        public static boolean setDefaultImpl(com.rk.myservices.IBookManager impl) {
            if (Stub.Proxy.sDefaultImpl == null && impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.rk.myservices.IBookManager getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public java.util.List<com.rk.myservices.Book> getBookList() throws android.os.RemoteException;

    public void addBookInOut(com.rk.myservices.Book book) throws android.os.RemoteException;
}

可以看到根据IBookManager.aidl系统为我们生成了IBookManager.java这个类,它继承 了IInterface这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要 继承IInterface接口。
首先,它声明了两个 方法getBookList和addBook,它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客 户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个 Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过 程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理 类Proxy来完成。这么来看,IBookManager这个接口的确很简单,但是我们也应该认识 到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍 针对这两个类的每个方法的含义。
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示,
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过 程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub对象本身,否则返回的是系统封装后的Stub.proxy对象

当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回 数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其 次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都 应该采用同步的方式去实现,因为它已经运行在一个线程中了。下面给出一个Binder的工作机制图如图所示。

在这里插入图片描述
Binder这里我们还需要注意Binder的两个很重要的方法linkToDeath和unlinkToDeath。

2.4 安卓中的IPC方式

2.4.1 使用Bundle

四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent 中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程 间传输。

2.4.2 使用文件共享

两个进程通过读/写同一个文件来交换数据,由于Android系统基于Linux,使得其并发读/写文件可以没有限制地进行,甚至两个 线程同时对同一个文件进行写操作都是允许的。

2.4.3 使用Messenger

Messenger,通过它可以在不同进程中传递Message对象, 在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。 Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。
特点:由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题, 这是因为服务端中不存在并发执行的情形。

2.4.4 使用AIDL

Messenger是 以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。
此时使用AIDL,例子 2.3.3 Binder,在此基础上,现在我们考虑一种情况,假设有一种需求:用户不想时不时地去查询图书列表了,太 累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。大家应该明白 了,这就是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户,这种模式在实际开发中用得很多,下面我们就 来模拟这种情形。首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并 且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL 接口而不是普通接口,是因为AIDL中无法使用普通接口。这里我们创建一个 IOnNewBookArrivedListener.aidl文件,我们所期望的情况是:当服务端有新书到来时,就 会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNew BookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端。
此处就贴代码了,主要关注在客户端取消监听,如下是错误的
在这里插入图片描述在解注册的过程中,服 务端竟然无法找到我们之前注册的那个listener,在客户端我们注册和解注册时明明传递的是同一个listener啊!最终,服务端由于无法找到要解除的listener而宣告解注册失败!这当 然不是我们想要的结果,但是仔细想想,好像这种方式的确无法完成解注册。其实,这是 必然的,这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法 奏效,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在 注册和解注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后,却会 产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都 是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原 因。那么到底我们该怎么做才能实现解注册功能呢?答案是使用RemoteCallbackList,这 看起来很抽象,不过没关系,请看接下来的详细分析。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,这点从它的声明就可以看出,因为 所有的AIDL接口都继承自IInterface接口,读者还有印象吗?
它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调, 这个Map的key是IBinder类型,value是Callback类型,如下所示。
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
其中Callback中封装了真正的远程listener。当客户端注册listener的时候,它会把这个 listener的信息存入mCallbacks中,使用方式如下服务端:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
客户端注册和注销监听,由于客户端的IOnNewBookArrivedListener中的onNewBookArrived方法运行在客户端的Binder线程池中,使用Handler切换到UI线程。
在这里插入图片描述
Binder的两个很重要的方法linkToDeath和unlinkToDeath,我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到 服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。为了解决 这个问题,Binder中提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath 我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方 法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方 法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务。
通过Binder的方法isBinderAlive也可以判断Binder是否死亡

2.4.5 使用ContentProvider

系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这 些信息,只需要通过ContentResolver的query、update、insert和delete方法即可

2.4.6 使用Socket

2.5 Binder连接池

为了解决项目中如果需要使用多个AIDL,多个服务的问题,我们需要减少Service的数量,将所有的AIDL放在同一 个Service中去管理。
在这种模式下,整个工作机制是这样的:每个业务模块创建自己的AIDL接口并实现 此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然 后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个 Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来 返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程 方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一 转发到远程Service中去执行,从而避免了重复创建Service的过程可以参考Binder连接池

2.6 选用合适的IPC方式

不同的IPC特点如下:
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!