简介

理解Android的跨进程通信原理的关键是Binder。Binder是Android中独有的跨进程通信方式,底层的支持是Binder Driver,需要知道的是Linux 内核中并不包含Binder Driver,也就是说Linux并不对Android中的Binder跨进程通信方式提供支持。

跨进程通信是需要内核空间作为支持的。传统的IPC机制如管道、Socket都是内核的一部分,因此通过内核支持实现进程间通信自然是没问题的。但是Binder并不是Linux系统内核的一部分,那该怎么办呢?幸好,Linux支持动态内核可加载模块的机制,模块是具有独立功能的程序,它可以被单独的编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样Android就可以动态添加一个内核模块运行在内核空间,用户进程通过这个内核模块作为桥梁实现通信。

在Android系统中,这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就是Binder驱动。

品尝前,建议先阅读:内存映射、Binder原理

本文基于Binder分析Android中的跨进程通信方式 - AIDL。

Android中的Messenger、ContentProvider底层都是基于AIDL,因此,掌握了AIDL的工作原理也就间接掌握了其余的跨进程通讯方案了。

本文资料来源“艺术探索”,重读此书,仅作学习笔记,备忘。

Android中的多进程模式

在Android中使用多进程只有一种方法,那就是给四大组件在AndroidManifest中指定android:process属性,除此之外没有其他方法。

关于android:process属性的设置,有两种方式:

        

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

小知识点:Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里需要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以相互访问对方的私有数据,比如data目录,组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像一个应用的两个部分。

多进程模式下注意事项

Android 会为每一个应用分配一个独立的虚拟机,或者说为每一个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间。

public class UserManager {public static int userId = 1;}

如上代码,我们在MainActivity中将userId赋值为2,在SecondActivity(通过android:process运行在单独的进程中)中打印userId,会发现userId的值是1,而不是2。

一般来说,使用多进程会造成如下几个方面的问题

  • 静态成员和单例模式完全失效;
  • 线程的同步机制完全失效;
  • SP的可靠性下降;
  • Application会创建多次;

第一个问题和第二个问题原因类似。

第三个问题是因为SP不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,因为SP底层是通过读/写XML文件来实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题。

第四个问题也是显而易见的,由于系统要在创建新的进程的同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一遍,既然重新启动了,那么自然就会创建新的Application。即运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。

小结:以上问题产生的原因是多进程模式中,不同的进程拥有独立的虚拟机、Application、内存空间

通过AIDL实现Binder

Binder是Android中的一个类,实现了IBinder接口。

  • IPC角度,Binder是Android中的一种跨进程通讯方式;
  • Android FrameWork角度,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等)和相应ManagerService的桥梁;
  • Android 应用层角度,Binder是客户端与服务器端进行通信的媒介;

想要具体了解Binder的工作原理:Binder原理。

示例相关代码:

-  Book.javapublic class Book implements Parcelable {    private String name;    protected Book(Parcel in) {        name = in.readString();    }......}

Book在跨进程通信中使用,因此需要序列化,实现Parcelable接口。Parcelable主要用在内存序列化上。如下两种情况虽然通过Parcelable也是可以的,但是过程会稍微复杂,建议使用Seriallizable。

  • 将对象序列化到存储设备中;
  • 通过网络传输;
-  Book.aidlpackage com.json.ipctest;parcelable Book;

在AIDL文件中,如果用到了自定义的Parcelable对象,必须新建一个和它同名的AIDL文件。

注:定义如上文件的时候,先定义Book.aidl文件然后按照上述格式书写代码即可。否则,如果先定义Book.java,再定义Book.aidl的时候,AS会提示命名冲突。

-  IBookManager.aidlpackage com.json.ipctest;import com.json.ipctest.Book;interface IBookManager {    List getBooks();    void addBook();}

自定义的Parcelable对象和AIDL对象必须显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。

--------------------------------------分割线----------------------------------------

AIDL文件定义完成,Android就为我们生成了一个Binder的抽象子类。

AIDL文件的本质:只是Android提供给我们的快速构建满足业务需求Binder的工具。我们完全可以抛开AIDL,手动完成自定义Binder。自定义的方式可以参照AIDL生成的Binder子类。

如下代码所示:

package com.json.ipctest;public interface IBookManager extends android.os.IInterface {    /**     * Local-side IPC implementation stub class.     * Stub 继承自Binder,因此Stub就是一个Binder     * 自定义Binder需继承android.os.Binder,实现android.os.IInterface及用户自定义接口,比如IBookManager。即可。     */    public static abstract class Stub extends android.os.Binder implements com.json.ipctest.IBookManager {        private static final java.lang.String DESCRIPTOR = "com.json.ipctest.IBookManager";        /**         * Construct the stub at attach it to the interface.         */        public Stub() {            // 将Binder的唯一标识与Binder绑定,可用于queryLocalInterface            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.json.ipctest.IBookManager interface,         * generating a proxy if needed.         *          */        public static com.json.ipctest.IBookManager asInterface(android.os.IBinder obj) {            if ((obj == null)) {                return null;            }            // 查询是否为跨进程Binder            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.json.ipctest.IBookManager))) {                return ((com.json.ipctest.IBookManager) iin);            }            return new com.json.ipctest.IBookManager.Stub.Proxy(obj);        }        @Override        public android.os.IBinder asBinder() {            return this;        }        // 该方法运行在服务器端。用于RPC调用        @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_getBooks: {                    data.enforceInterface(descriptor);                    java.util.List _result = this.getBooks();                    reply.writeNoException();                    reply.writeTypedList(_result);                    return true;                }                case TRANSACTION_addBook: {                    data.enforceInterface(descriptor);                    this.addBook();                    reply.writeNoException();                    return true;                }                default: {                    return super.onTransact(code, data, reply, flags);                }            }        }        // 若为跨进程通讯,则obj.queryLocalInterface(DESCRIPTOR)返回该内部类        private static class Proxy implements com.json.ipctest.IBookManager {            // mRemote 为BinderProxy            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 getBooks() throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                java.util.List _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    // 执行transact,会调用远程服务端的onTransact方法                    mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);                    _reply.readException();                    _result = _reply.createTypedArrayList(com.json.ipctest.Book.CREATOR);                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            @Override            public void addBook() throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);                    _reply.readException();                } finally {                    _reply.recycle();                    _data.recycle();                }            }        }        static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    }    public java.util.List getBooks() throws android.os.RemoteException;    public void addBook() throws android.os.RemoteException;}

IBookManager.java类结构乍看感觉结构清晰,其实IBookManager.java的实现可以分成两个文件,这样你会发现更容易一眼看明白。我们也可以按照这个步骤,不依赖于AIDL,完全自定义我们需要的Binder。

public interface IBookManager extends android.os.IInterface {static final java.lang.String DESCRIPTOR = "com.json.ipctest.IBookManager";static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    public java.util.List getBooks() throws android.os.RemoteException;    public void addBook() throws android.os.RemoteException;}
public abstrace class BookManager extends android.os.Binder implements IBookManager {// 重写如下方法asInterfaceasBinderonTransact// Proxy 内部类private static class Proxy implements IBookManager {// 重写如下方法asBindergetInterfaceDescriptorgetBooksaddBook }}

使用示例:

public class LocalProcessService extends Service {    @Override    public IBinder onBind(Intent intent) {        return new MyBinder();    }    private class MyBinder extends IBookManager.Stub {        @Override        public List getBooks() throws RemoteException {            Log.e("getBooks", "getBooks");            return null;        }        @Override        public void addBook() throws RemoteException {            Log.e("addBook", "addBook");        }    }}

分析AIDL生成的IBookManager.java类,IBookManager.java这个类继承自android.os.IInterface,同时它自己也还是个接口。所有可以在Binder中传输的接口都需要继承IInterface接口。

 只看上述提出的一堆代码,可能头顶还是一堆问号?

                                                         

因此,还是老规矩,上图。通过图一目了然,看清把握整体。

                                                             

第一张:跨进程通信原理

                     

第二张:Binder的工作原理

                

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

详细分析:

  • DESCRIPTOR

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

  • asInterface

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

  • asBinder

此方法用于返回当前的Binder对象。

  • onTransact

这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。该方法参数中code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕之后,就向reply中写入返回值。如果该方法返回值为false,那么客户端的请求就会失败,利用这一点,我们可以做权限验证,我们也不希望随便一个进程都能远程调用我们的服务。

  • linkToDeath与unLinkToDeath

Binder运行在服务器端进程,如果服务器端由于某种原因异常终止了,这会导致Binder连接断裂,即我们的远程调用失败。如果我们不知道Binder的连接断裂了,那么客户端的功能就会受到影响。为了解决这个问题,Binder提供了linkToDeath和unLinkToDeath。我们可以给Binder设置一个死亡代理,当Binder死亡的时候,我们可以收到通知。

---------------------------分割线--------------------------------------

其他知识点:

  • 反序列化得到的对象只是内容上和序列化的对象是一样的,但它们本质上还是两个对象。
  • Messenger使用方法简单,但是它一次处理一个请求,即串行的方式。通过Messenger来传递Message,Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo。Message的另一个字段object在同一个进程中是很实用的,但是在进程间通讯的时候,在Android 2.2以前object字段不支持跨进程传输,即便2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这意味着我们自定义的Parcelable对象是无法通过object字段来传输的。
  • AIDL相关:

1、支持的数据类型:

基本数据类型、String、CharSequence、List、Map、Parcelable、AIDL。

2、除了基本的数据类型,其他数据类型的参数必须标上方向:in - 输入型参数,out - 表示输出型参数、inout - 输入输出型参数

3、AIDL接口只支持方法,不支持声明静态常量。

4、为了方便AIDL开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另外一个应用时,我们可以直接把整个包复制到客户端工程中。{AS目前新建AIDL相关已经是在单独的包中了}

5、需要注意的是,AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法的正常的运行了。

6、AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步问题。

---------------------------分割线--------------------------------------

考虑情形:跨进程通讯中的”观察者模式“,需要解决哪些问题?

  • 我们需要提供一个接口,因为观察者模式需要用到,但是这个接口不是普通的接口,而是AIDL接口。因为AIDL中无法使用普通的接口。
  • 解注册的时候,会发现无法完成解注册,为啥?

在日常的开发中使用的解注册方式是没什么问题的,但是放在多进程中就会产生问题,这是为啥呢?因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然在注册和解注册的时候我们用的是同一个客户端对象,但是通过Binder传递到服务端就会产生两个全新的对象,别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程。

如何解决?

RemoteCallBackList是系统专门提供用于删除跨进程listener的接口。

  • 几种跨进程通讯方式对比:

更多相关文章

  1. 史上最全的Android面试题集锦
  2. Android(安卓)getDimensionPixelSize, 代码中设置字体大小,读xml
  3. Android(安卓)内存管理
  4. Android中String资源文件的format方法
  5. android view 原理 -- measure 分析与应用
  6. Android(安卓)Theme主题样式开发注意点
  7. Android开机流程分析 -- Zygote
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. 彻底解决android读取中文txt,lrc的乱码(自
  2. Rect、RectF矩形类
  3. ArcGIS for Android示例解析之地图基础--
  4. 当Android遇见HTTP缓存代理服务器
  5. Android(安卓)可拖动的进度条:SeekBar之简
  6. Android数据读取之Sqlite数据库操作
  7. 彻底弄懂Activity的启动模式和任务栈
  8. Android源码阅读技巧--查找开发者选项中
  9. Android程序的调试--善用Log
  10. Android(安卓)显示单位dip、px、pt、sp的