安卓IPC机制

IPC机制的简介

IPC是Inter-Process Communication的缩写,含义是进程间通信,用于两个进程之间进行数据交互的过程,任何操作系统都有IPC机制,但不同的操作系统有着不同的通信方式,Android系统是一种基于Linux内核的移动操作系统,Linux的跨进程通信主要通过管道、共享内存、内存映射等实现的,但Android有自己的进程间通信机制,最具代表性的就是Binder。

线程:CPU调度的最小单元,是一种有限的资源。
进程:是指一个执行单元,PC和移动设备上指一个应用或程序,一个进程可以包含多个线程。
在安卓里主线程也叫UI线程,在UI线程里才能操作界面元素。

Android中的多线程模式

指的是指定应用内部开启多进程模式,只有一中方法,给四大组件在AndroidMenifest中指定android:process属性。如下图方式:
在这里插入图片描述
process有两种写法

第一种以“:”开头的,“:”的含义是要在当前的进程的前面附加上当前包名的一个缩写,是当前应用的私有进程,其他应用不能和他跑在同步一个进程。

第二种 包含“.”的,是完整的命名方式,属于全局进程,其他应用通过ShareUID方式可以和他跑在同一个进程种。

UID:是Android系统为每一个应用程序分配的用户身份证明来区分不同的应用。
具有相同UID的应用能共享数据(可以互相访问对方的私有数据data目录、组件信息等)
跑在同一进程需要相同的UID+相同的签名(还可以访问共享内存数据)
相同UID的应用实现资源共享: 首先需要在两个应用的AndroidManifest.xml中都定义相同的sharedUserId,如:android:sharedUserId=“com.test”。

同应用开启不同的线程后就运行在不同的虚拟机里面,不同的虚拟机在内存分配上有不同的地址空间,造成的问题:

1.静态成员和单例模式失效
2.SharePreferences的可靠性下降(底层是通过XML文件实现的,不支持两个进程同时读写数据,会丢失数据)
3.线程的同步机制失效
4.Application会多次创建。因为系统要为新建的线程分配独立的虚拟机,这个过程就是启动新的应用的过程,相当于系统又要把这个应用重新启动一边导致重建新的Application。运行在同一个进程的组件属于同一个虚拟机和Application。

利用跨进程通信解决上述问题

简单来说就是:通过使用数据持久化存储在设备上来传输数据。
需要通过Serializable接口/Parcelable接口来完成对象的序列化,在通过Intent和Binder传输数据或者通过Serializable将对象持久化到存储设备上或者网络传输给其他应用。

Serializable:是JAVA的序列化接口,使用起来简单开销很大
Parcelable:Android中的序列化方式,使用麻烦但是效率很高。

不参加序列化的
1.静态变量,2.transient相关标记的成员变量。

Serialization接口,基于JVM上的持久化数据

Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化。
Serializable使用IO读写存储在硬盘上。序列化过程使用了反射技术,并且期间产生临时对象。Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。优点代码少。
使用方法:在需要序列化的类里面实现Serializable接口并声明一个标识serialVersionUID 如

public class User implements Serializable {
    //可以不写,系统根据结构计算当前类的hash值自动分配,增加或删除结构从新计算会hash值会不一致,导致反序列化失败程序crash。
    //手动指定,最大程度反序列化成功,建议用插件自动生成。
    private static final long serialVersionUID =463442362345654634L;
    ... ...
}

剩下的工作系统会自动完成,进行序列化和反序列化的方法:采用ObjectOutputStream和ObjectInputStream。

//序列化过程
User user = new User("chou",27,man);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.tex"));
out.writeObject(user);
out.close;

//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.tex"));
User newUser = (User)in.readObject();
in.close;
Parcelable接口,Android专用,基于DVM上的持久化数据,Parcelable 的读取顺序必须一致,不然有的机型会出错闪退(遇到过这种bug)

Parcelable是直接在内存中读写,我们知道内存的读写速度肯定优于硬盘读写速度,所以Parcelable序列化方式性能上要优于Serializable方式很多。但是代码写起来相比Serializable方式麻烦一些。
代码示例:User(Parcel source) 参数的顺序一定需要与 writeToParcel(Parcel dest, int flags)参数顺序

public class User implements Parcelable {
      private String name;
      private int age;
      private String sex;
  
      public User(String name, int age, String sex ) {
          this.name = name;
          this.age = age;
          this.sex = sex;
      }

      //返回当前对象的内容描述
      @Override
      public int describeContents() {
          return 0;
      }

      //序列化操作,将对象写入序列化结构中
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeString(name);
          dest.writeInt(age);
          dest.writeString(sex);
      }

      //反序列化操作
      public static final Creator<User> CREATOR = new Creator<User>() {
          //从序列化后的对象中创建原始对象
          @Override
          public User createFromParcel(Parcel in) {
              return new User(in);
          }
          //创建指定长度的原始对象数组
          @Override
          public User[] newArray(int size) {
              return new User[size];
          }
      };
      //从序列化后的对象中创建原始对象
      public User(Parcel source) {
          this.name = source.readString();
          this.age = source.readInt();
          this.sex = source.readString();
      }
  }

这里先说一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。从上述代码中可以看出,在序列化过程中需要实现的功能有序列化、反序列化和内容描述。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的;反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程;内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。详细的方法说明请参看下面
1、describeContents()

返回当前对象的内容描述。返回值为0或者CONTENTS_FILE_DESCRIPTOR(即1),如果writeToParcel(Parcel dest, int flags)的输出包含文件描述符,则返回1,否则返回0,几乎所有的情况之下都是返回0.

2、writeToParcel (Parcel dest, int flags)

将当前的对象写入序列化结构中,dest表示需要写入序列化的对象。flags有两种值0和PARCELABLE_WRITE_RETURN_VALUE(即1),几乎所有的情况下都是0.

3、createFromParcel(Parcel source)

返回Parcelable类的新实例。同Parcel的readXXX()方法来完成反序列化的过程。

4、newArray(int size)

创建Parcelable类的新数组。其中每个条目都初始化为空。供外部类反序列化本类数组使用。

将这个数据放到Intent或者Bundle中完成传递

 Intent intent = new Intent(MainActivity.this, ServerService.class);

  intent.setExtrasClassLoader(User.class.getClassLoader());

  Bundle bundle = new Bundle();
  bundle.putParcelable("User", new User("chou",27,"sex"));
  intent.putExtras(bundle); 

  bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

在需要接收目标组件中完成接收操作

  Bundle bundle = intent.getExtras();
  User user = bundle.getParcelable("User");

Binder

Binder是Andriod中的一种跨进程通信方式,是Android的一种类,它实现了IBinder接口。
1.从IPC角度讲:也可以把它当成一种虚拟的物理设备,它的驱动是/dev/binder,该通讯方式在Linux中没有,所以是Android特有的跨进程通信方式。
2.从Android Framework层面上讲:Binder是连接ServiceManager(用来管理系统的service)连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService的桥梁。
3.从应用层讲:Binder是客户端和服务端进行通信的媒介,当bindService时,服务端会返回一个包含服务端业务调用的Binder对象,客户端通过这个对象来获取服务端提供的服务或数据。

Android是基于Linux系统上的,我们先了解一下Linux中的IPC通信原理

在Linux中,进程之间是隔离的,内存也是不共享的,想要进程之间通信必须先了解一下进程空间,进程空间分为用户空间和内核空间。用户空间是用户程序运行的空间,内核空间则是内核运行的空间,为了防止用户空间随便干扰,用户空间是独立的,内核空间
是共享的,但是为了安全性考虑,内核空间和用户空间也是隔离的,他们之间仅可以通过系统调用来通信,至此我们知道IPC的大致方案是A进程的数据通过系统调用把数据传递到内核空间,内核空间再利用系统调用把数据传递到B空间,其中会有两次数据的拷贝如下图:
在这里插入图片描述
缺点:据传递通过内存缓存—>内核缓存—>内存缓存2次拷贝性能低,其次是传递数据后,接收方不知道用多大内存存放,所以尽可能大的开辟内存空间导致内存空间浪费。

再来看一下Android中的IPC通信-Binder

字面意思,粘合剂、胶水的意思,顾名思义就是粘合不同的进程,使之实现通信。
你肯定有疑问,既然基于Linux系统,为什么Android不沿用Linux的IPC而要使用Binder呢?因为综合考虑其性能、稳定性和安全性。
性能 :数据拷贝次数,共享内存0次、Binder 1次、Socket/管道/消息队列2次。
稳定性:Binder基于C/S架构,客户端有需求丢给服务端完成,架构清晰,职责明确独立,共享内存需要控制负责、难以使用,Binder机制更优。
安全性:Android开放性平台,不免会有很多恶意APP、流氓软件等,传统IPC没有任何安全防护,无法获取对方进程ID,Android为每个进程分配UID,传统的IPC机制只能在数据包中添加,可靠的身份标识只能由IPC机制在内核添加才安全,其次传统的IPC访问接入点是开放的,恶意软件通过猜测接入点可以获得连接,所以不安全。Binder支持实名和匿名Binder,安全性更高。

了解了这些,我们再来看Binder是怎样不利用传统的IPC来实现通信的

IPC基于内核空间,但Binder不是Linux系统内核的一部分,但Linux有动态内核可加载模块(LKM)的机制,模块是具有独立功能的程序,可以被单独编译但不能独立运行,运行时被了链接到内核座位内核的一部分运行,至此Android系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信,我们把这个内核模块叫做Binder驱动(Binder Driver)。它是怎样实现进程间通信的呢?通过内存映射!,Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

Binder IPC 原理

Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。举个栗子:进程中的用户区域不能直接访问物理设备,如果想访问磁盘数据,需要通过磁盘—>内核空间—>用户空间这两次拷贝,我们通过mmap()在两者之间建立映射,减少数据的拷贝,用内存读取来取代I/O读写提高效率。而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。

过程如下图文:
1.首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2.接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
3.发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
在这里插入图片描述

Client/Server/ServiceManager/驱动

Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。

binder 驱动 -> 路由器
ServiceManager -> DNS
Binder Client -> 客户端
Binder Server -> 服务器
在这里插入图片描述
在这里插入图片描述
Binder通讯整个下来的过程
1.首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
2.Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
3.Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。
在这里插入图片描述

Binder通信中的代理模式

我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。
前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。
当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。
请添加图片描述

各 Java 类职责描述

在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

IBinderIBinder :是一个接口,代表了一种跨进程通信的能力。只要实现了这个接口,这个对象就能跨进程传输。

IInterface: IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)

Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

Stub:Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现

实现过程

一次跨进程通信必然会涉及到两个进程,在这个例子中 RemoteService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 RemoteService 提供的服务。如下图:

那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口,BookManager 继承自 IIterface,表明服务端具备什么样的能力。

/**
 * 这个类用来定义服务端 RemoteService 具备什么样的能力
 */
public interface BookManager extends IInterface {

    void addBook(Book book) throws RemoteException;
}

只定义服务端具备什么要的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

public abstract class Stub extends Binder implements BookManager {

    ...

    public static BookManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof BookManager)
            return (BookManager) iin;
        return new Proxy(binder);
    }

    ...

    @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 TRANSAVTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0 = null;
                if (data.readInt() != 0) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;

        }
        return super.onTransact(code, data, reply, flags);
    }

    ...
}

Stub 类中我们重点介绍下 asInterface 和 onTransact。

先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。

接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口。

public class Proxy implements BookManager {

    ...

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

    @Override
    public void addBook(Book book) throws RemoteException {

        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (book != null) {
                data.writeInt(1);
                book.writeToParcel(data, 0);
            } else {
                data.writeInt(0);
            }
            remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
            replay.readException();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    ...
}

我们看看 addBook() 的实现;在 Stub 类中,addBook(Book book) 是一个抽象方法,Client 端需要继承并实现它。

如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。

最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 addBook() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。
这样一次跨进程调用就完成了。

多种多样的跨进程方式

上面介绍了Binder、序列化,我们再来了解一下其它的方式,比如通过Intent附加extra来传输信息,文件共享,PrivideContent,Socket。

1.使用Bundle
四大组件都支持Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以当我们在一个进程中启动另一个进程的Activity、Service等,Bindle传递的数据必须能序列化,比如基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象。

2.使用文件共享
两个进程通过读写同一个文件来交换数据。
适合对数据同步要求不高的进程之间通信,并且妥善处理并发读写问题。
SharePreference是个特例,他是Android的轻量级存储方案,通过键值对存储数据,本质上也是文件的一种。

3.使用Messenger
Messenger顾名思义,信使的意思,通过它我们可以在不同的进程中传递Message对象,Message中存放着需要传递的对象。它是一种轻量级的IPC方案,底层实现是AIDL,它对AIDL做了封装,使我们使用起来更简单,由于它一次处理一个请求,所以我们不需要考虑线程同步问题,因为服务端不存在并发执行的情形。缺点是有大量消息的时候就不合适,因为是串行发送的一次只能处理一个请求。主要用来传递信息不能用来跨进程调度服务端。

使用方法:
1.服务端进程:创建Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind方法中返回这个messenger对象底层的Binder。
2.客户端进程:首先需要绑定服务端的Service,绑定成功后在服务端返回的IBinder对象中创建一个Messenger,通过它向服务端发Message类型的消息。如果服务端回应客户端,和服务端,我们还需要创建一个Handler并创建一个新的Messenger并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过replyTo参数就可以回应客户端。
代码示例:

构建一个运行在独立进程中的服务端Service:

public class MessengerService extends Service {
    private static final String TAG = "MessagerService";

    /**
     * 处理来自客户端的消息,并用于构建Messenger
     */
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MESSAGE_FROM_CLIENT:
                    Log.e(TAG, "receive message from client:" + message.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(message);
                    break;
            }
        }
    }

    /**
     * 构建Messenger对象
     */
    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //将Messenger对象的Binder返回给客户端
        return mMessenger.getBinder();
    }
}

注册Service,在不同进程

<service
    android:name="com.xxq2dream.service.MessengerService"
    android:process=":remote" />

然后客户端是通过绑定服务端返回的binder来创建Messenger对象,并通过这个Messenger对象来向服务端发送消息

public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";

    private Messenger mService;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "ServiceConnection-->" + System.currentTimeMillis());
            //通过服务端返回的Binder创建Messenger
            mService = new Messenger(iBinder);
            //创建消息,通过Bundle传递数据
            Message message = Message.obtain(null, MESSAGE_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "hello service,this is client");
            message.setData(bundle);
            try {
                //向服务端发送消息
                mService.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e(TAG, "onServiceDisconnected-->binder died");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        //绑定服务
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //解绑服务
        unbindService(mConnection);
        super.onDestroy();
    }
}

服务端如果要回复消息给客户端,那就要用到Message的replyTo参数了

private static class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case Constant.MESSAGE_FROM_CLIENT:
                Log.e(TAG, "receive message from client:" + message.getData().getString("msg"));
                //获取客户端传递过来的Messenger,通过这个Messenger回传消息给客户端
                Messenger client = message.replyTo;
                //当然,回传消息还是要通过message
                Message msg = Message.obtain(null, Constant.MESSAGE_FROM_SERVICE);
                Bundle bundle = new Bundle();
                bundle.putString("msg", "hello client, I have received your message!");
                msg.setData(bundle);
                try {
                    client.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                super.handleMessage(message);
                break;
        }
    }
}

客户端改造:

/**
 * 用于构建客户端的Messenger对象,并处理服务端的消息
 */
private static class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case Constant.MESSAGE_FROM_SERVICE:
                Log.e(TAG, "receive message from service:" + message.getData().getString("msg"));
                break;
            default:
                super.handleMessage(message);
                break;
        }
    }
}

/**
 * 客户端Messenger对象
 */
private Messenger mClientMessenger = new Messenger(new MessengerHandler());

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Log.e(TAG, "ServiceConnection-->" + System.currentTimeMillis());
        mService = new Messenger(iBinder);
        Message message = Message.obtain(null, MESSAGE_FROM_CLIENT);
        Bundle bundle = new Bundle();
        bundle.putString("msg", "hello service,this is client");
        message.setData(bundle);
        //将客户端的Messenger对象传递给服务端
        message.replyTo = mClientMessenger;
        try {
            mService.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        Log.e(TAG, "onServiceDisconnected-->binder died");
    }
};

在这里插入图片描述

4.使用AIDL:

Messenger是串行处理消息的,服务端需要一个个来处理,不适合大量的消息同时发送给服务端。其次Messenger作用是传递消息,有时候我们还需要调用服务端方法,这种情景AIDL更适合。步骤如下:
1.服务端:创建Service用来监听客户端连接请求
2.客户端:绑定服务端的Service,将服务端返回的Binder对象转换成AIDL接口所属的类型后就可以调用AIDL中的方法了。
3.AIDL接口创建,声明接口、方法、同步项目,具体使用如下图
在这里插入图片描述

// IMyAidlInterface.aidl
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     *
     * 此方法作用是告诉我们aidl中可以使用的基本类型,可以删除无视。
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

在AIDL中定义提供给其他app使用的方法

interface IMyAidlInterface {
   String getName();
}

sycn project同步后,编译工具会自动帮我们生成一个Stub类,创建一个service并在里面创建一个继承刚才接口的Stub类内部类,实现接口方法,并在onBind方法中返回内部类的实例:

public class MyService extends Service
{

    public MyService()
    {
      ...
    }

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

    class MyBinder extends IMyAidlInterface.Stub
    {

        @Override
        public String getName() throws RemoteException
        {
            return "test";
        }
    }
}

接下来我们将AIDL文件拷贝到客户端(包名,文件名必须完全一致),然后在Activity中绑定服务。

public class MainActivity extends AppCompatActivity
{


    private IMyAidlInterface iMyAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService(new Intent("cc.abto.server"), new ServiceConnection()
        {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service)
            {

                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name)
            {

            }
        }, BIND_AUTO_CREATE);
    }

    public void onClick(View view)
    {
        try
        {
            Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_SHORT).show();
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
    }
}

这边我们通过隐式意图来绑定service,在onServiceConnected方法中通过IMyAidlInterface.Stub.asInterface(service)获取iMyAidlInterface对象,然后在onClick中调用iMyAidlInterface.getName()。
使用自定义数据类型是需要把使用的对象序列化实现Parcelable接口,在aidl中导入该类型再使用,注意包名文件名。

5.使用ContentProvider

ContentProvider是Android提供的专门用于应用间进行数据共享的方式,天生适合进程间通信,和Messenger一样底层同样实现了Binder(可以理解几乎所有跨进程都是基于Binder的封装。来实现的)。
在这里插入图片描述
ContentProvider是一个抽象类,如果我们需要开发自己的内容提供者我们就需要继承这个类并复写其方法,需要实现的主要方法如下:
public boolean onCreate()
在创建ContentProvider时使用
public Cursor query()
用于查询指定uri的数据返回一个Cursor
public Uri insert()
用于向指定uri的ContentProvider中添加数据
public int delete()
用于删除指定uri的数据
public int update()
用户更新指定uri的数据
public String getType()
用于返回指定的Uri中的数据MIME类型
数据访问的方法insert,delete和update可能被多个线程同时调用,此时必须是线程安全
其它应用可以通过ContentResolver来访问ContentProvider提供的数据,而ContentResolver通过uri来定位自己要访问的数据。

为什么要通过再加一层ContentResolver而不是直接访问ContentProvider?

原因是:一台手机中可不是只有一个Provider内容,它可能安装了很多含有Provider的应用,比如联系人应用,日历应用,字典应用等等。有如此多的Provider,如果你开发一款应用要使用其中多个,如果让你去了解每个ContentProvider的不同实现,岂不是要头都大了。所以Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作。怎样区别不同的Provider则是通过URI!
在这里插入图片描述
扩展:URI(Universal Resource Identifier)统一资源定位符
格式:[scheme:][//host:port][path][?query]
URI:http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
scheme:根据格式我们很容易看出来scheme为http
host:www.baidu.com
port:就是主机名后面path前面的部分为8080
path:在port后面?的前面为wenku/jiatiao.html
query:?之后的都是query部分为 id=123456$name=jack
uri的各个部分在安卓中都是可以通过代码获取的,下面我们就以上面这个uri为例来说下获取各个部分的方法:
getScheme() :获取Uri中的scheme字符串部分,在这里是http
getHost():获取Authority中的Host字符串,即 www.baidu.com
getPost():获取Authority中的Port字符串,即 8080
getPath():获取Uri中path部分,即 wenku/jiatiao.html
getQuery():获取Uri中的query部分,即 id=15&name=du

接下来我们看一下如何使用,创建了两个工程,进程一自定义了contentprovider,进程二通过ContentResolver来访问进程一中的contentprovider的数据(对进程一中自定义的contentprovider的数据库进行增删改查操作)
先写个数据库的工具类用来创建数据库(进程二就是跨进程操作此数据库的)

public class DBHelper extends SQLiteOpenHelper {

    // 数据库名
    private static final String DATABASE_NAME = "finch.db";

    // 表名
    public static final String USER_TABLE_NAME = "user";
    public static final String JOB_TABLE_NAME = "job";

    private static final int DATABASE_VERSION = 1;
    //数据库版本号

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        // 创建两个表格:用户表 和职业表
        db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");

        db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)   {

    }
}

再自定义一个自己的contentprovider。。DBHelper中有两张表, UriMatcher是用来根据进程二的ContentResolver调用的uri判断进程二到底是需要操作哪张表的数据的。

public class MyProvider extends ContentProvider {

    private Context mContext;
    DBHelper mDbHelper = null;
    SQLiteDatabase db = null;
    public static final String AUTOHORITY = "com.example.zhaoziliang";
    // 设置ContentProvider的唯一标识

    public static final int User_Code = 1;
    public static final int Job_Code = 2;

    // UriMatcher类使用:在ContentProvider 中注册URI
    private static final UriMatcher mMatcher;
    static{
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 初始化
        mMatcher.addURI(AUTOHORITY,"user", User_Code);
        mMatcher.addURI(AUTOHORITY, "job", Job_Code);
        // 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
        // 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
    }

    // 以下是ContentProvider的6个方法

    /**
     * 初始化ContentProvider
     */
    @Override
    public boolean onCreate() {

        mContext = getContext();
        // 在ContentProvider创建时对数据库进行初始化
        // 运行在主线程,故不能做耗时操作,此处仅作展示
        mDbHelper = new DBHelper(getContext());
        db = mDbHelper.getWritableDatabase();

        // 初始化两个表的数据(先清空两个表,再各加入一个记录)
        db.execSQL("delete from user");
        db.execSQL("insert into user values(1,'Carson');");
        db.execSQL("insert into user values(2,'Kobe');");

        db.execSQL("delete from job");
        db.execSQL("insert into job values(1,'Android');");
        db.execSQL("insert into job values(2,'iOS');");

        return true;
    }

    /**
     * 添加数据
     */

    @Override
    public Uri insert(Uri uri, ContentValues values) {

        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);

        // 向该表添加数据
        db.insert(table, null, values);

        // 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
        mContext.getContentResolver().notifyChange(uri, null);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);

        return uri;
        }

    /**
     * 查询数据
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);

        // 查询数据
        return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    /**
     * 更新数据
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    /**
     * 删除数据
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    @Override
    public String getType(Uri uri) {

        // 由于不展示,此处不作展开
        return null;
    }

    /**
     * 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
     */
    private String getTableName(Uri uri){
        String tableName = null;
        switch (mMatcher.match(uri)) {
            case User_Code:
                tableName = DBHelper.USER_TABLE_NAME;
                break;
            case Job_Code:
                tableName = DBHelper.JOB_TABLE_NAME;
                break;
        }
        return tableName;
        }
    }

进程二:
ContentResolver通过对应匹配的uri去调用对应的进程一的contentprovider的不同的表进行增删改查操作


public class MainActivity extends AppCompatActivity {

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

        /**
         * 对user表进行操作
         */

        // 设置URI
        Uri uri_user = Uri.parse("content://com.example.zhaoziliang/user");

        // 插入表中数据
        ContentValues values = new ContentValues();
        values.put("_id", 4);
        values.put("name", "zzl");


        // 获取ContentResolver
        ContentResolver resolver =  getContentResolver();
        // 通过ContentResolver 根据URI 向ContentProvider中插入数据
        resolver.insert(uri_user,values);

        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
        while (cursor.moveToNext()){
            System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
            // 将表中数据全部输出
        }
        cursor.close();
        // 关闭游标

        /**
         * 对job表进行操作
         */
        // 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
        Uri uri_job = Uri.parse("content://com.example.zhaoziliang/job");

        // 插入表中数据
        ContentValues values2 = new ContentValues();
        values2.put("_id", 4);
        values2.put("job", "LOL Player");

        // 获取ContentResolver
        ContentResolver resolver2 =  getContentResolver();
        // 通过ContentResolver 根据URI 向ContentProvider中插入数据
        resolver2.insert(uri_job,values2);

        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
        while (cursor2.moveToNext()){
            System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
            // 将表中数据全部输出
        }
        cursor2.close();
        // 关闭游标
    }
}
6.使用Socket(套接字)

socket实现进程间通信,分为:

TCP协议(传输控制/流式套接字):面向连接的协议,提供稳定的双向通信功能,三次握手四次挥手,这个次数是保证安全又高效。
UDP协议(用户数据报套接字):面向无连接,不稳定,不安全,不保证数据一定能传输到,但效率高。
具体就是我们平常使用的网络请求。

Binder连接池

随着项目越来越大,很多业务模块都需要使用AIDL来通信,我们还需要了解一下Binder连接池的原理。Binder连接池的主要作用是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。

首先,说明一下Binder连接池出现的原因以及使用目的。为什么会有Binder连接池这个概念,这需要从AIDL说起,正常来说,服务端新建一个AIDL接口后,都会同时创建一个Service,这个Service对象用于返回AIDL接口的底层调用对象Binder给客户端,试想一下,如果有10个不同的AIDL接口,那是不是要创建10个不同的Service,这肯定是不太合理的,为了避免这种情况的出现,就有了Binder连接池的概念。Binder连接池的最大目的就是减少Service的数量,实现只要通过一个Service就可以管理所有不同的AIDL。
在这里插入图片描述
如何使用好Binder连接池,下面通过代码来介绍一下,我会分别创建服务端和客户端的代码,通过这个简单直接的demo,让读者能够直接抓住binder连接池的使用要领。

   服务端代码:
   对服务端来说,最重要的是提供一个能够返回相应Binder对象的queryBinder接口,这个接口可以根据不同的标志返回不同AIDL接口所对应的Binder对象。这个queryBinder接口是Binder连接池的精髓所在,客户端在绑定远程Service后,可以先通过不同的标志获取服务端中相应AIDL接口所对应的Binder对象,再通过这个Binder就可以获取到服务端的AIDL接口,进而使用服务端AIDL接口中的方法了。
  在服务端新建两个简单的AIDL接口,ICalculate提供加减运算,IStringAppend用于字符串的拼接。
interface ICalculate {
  int add(int a,int b);//加法
  int sub(int a,int b);//减法
}  
interface IStringAppend {
    String append(String str1,String str2);//字符串拼接
}
为了能够返回上面两个接口对应的binder对象,服务端还需要新建一个AIDL接口提供根据不同标志返回相应binder对象的功能。
interface IBinderPool {
 IBinder queryBinder(int binderCode);
}

下面创建两个类分别继承ICalculate接口和IStringAppend接口中的Stub类并实现Stub中的抽象方法:

public class CalculateImpl extends ICalculate.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a+b;
    }
 
    @Override
    public int sub(int a, int b) throws RemoteException {
        return a-b;
    }
}
public class StringAppendImpl extends IStringAppend.Stub {
    @Override
    public String append(String str1, String str2) throws RemoteException {
        return str1+str2;
    }
}

创建一个Service并在Service的onBind方法中返回相应的binder对象,这里可以根据业务需求进行约定,比如当标志为0时返回ICalculate接口对应的binder对象,当标志为1时返回IStringAppend对应的Binder对象:

public class BinderPoolService extends Service {

   private static final int BINDER_CALCULATE=0;
   private static final int BINDER_STRING_APPEND=1;

   private Binder binder= new IBinderPool.Stub() {
       @Override
       public IBinder queryBinder(int binderCode) throws RemoteException {
           IBinder iBinder=null;
           switch (binderCode){
               case BINDER_CALCULATE:
                   iBinder=new CalculateImpl();
                   break;
               case BINDER_STRING_APPEND:
                   iBinder=new StringAppendImpl();
                   break;
           }
           return iBinder;
       }
   };

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

不要忘了在AndroidManefest中service配置Service,这里说明一下,由于我的aidl文件所在包名为com.aidl.binderpool,故action为com.aidl.binderpool.IBinderPool,即包名+类名,其实只要客户端能够正确绑定到服务端Service就OK了。

<service android:name=".BinderPoolService">
     <intent-filter>
          <action android:name="com.aidl.binderpool.IBinderPool"/>
     </intent-filter>
</service>

至此服务端的代码就结束了。
服务端代码在AS中的目录如下:

在这里插入图片描述
客户端代码:

   首先需要先将服务端中aidl包下所有的aidl文件连同目录拷贝至客户端代码,之后客户端做的工作就比较简单了,首先绑定服务端Service,然后通过Service返回的IBinder对象获取IBinderPool接口,通过queryBinder方法获取所需要的AIDL接口所对应的Binder对象,最后获取相应的AIDL接口并调用相应的方法即可,客户端的MianActivity如下:
public class MainActivity extends AppCompatActivity {

   private static final String TAG="BinderActivity";
   private static final int BINDER_CALCULATE=0;
   private static final int BINDER_STRING_APPEND=1;

   IBinderPool mBinderPool;
   ICalculate mCalculate;
   IStringAppend mStringAppend;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       Intent intent=new Intent(IBinderPool.class.getName());
       bindService(intent,conn,BIND_AUTO_CREATE);
   }

   private ServiceConnection conn=new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
           mBinderPool=IBinderPool.Stub.asInterface(iBinder);
           try {
               IBinder calculateBinder=mBinderPool.queryBinder(BINDER_CALCULATE);
               mCalculate=ICalculate.Stub.asInterface(calculateBinder);
               Log.i(TAG,"1+2="+mCalculate.add(1,2));
               Log.i(TAG,"10-6="+mCalculate.sub(10,6));
               IBinder stringAppendBinder=mBinderPool.queryBinder(BINDER_STRING_APPEND);
               mStringAppend=IStringAppend.Stub.asInterface(stringAppendBinder);
               String result=mStringAppend.append("hello","world");
               Log.i(TAG,"\"hello\"与\"world\"拼接=="+result);
           } catch (RemoteException e) {
               e.printStackTrace();
           }
       }

       @Override
       public void onServiceDisconnected(ComponentName componentName) {
           Intent intent=new Intent(IBinderPool.class.getName());
           bindService(intent,conn,BIND_AUTO_CREATE);
       }
   };

   @Override
   public void onDestroy(){
       unbindService(conn);
       super.onDestroy();
   }
}
查看一下控制台输出,如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/4f42b7d70df54b239cb44f805adf781c.png)
客户端代码目录如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/e337af2071c74918b80cebce12a91acb.png)
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android IPC(Inter-Process Communication)机制是指在Android系统中,不同进程之间进行通信的方式。Android中的应用程序通常都运行在自己的进程中,如果不同应用程序之间需要进行通信,或者同一个应用程序的不同进程之间需要进行通信,就需要使用IPC机制Android系统提供了多种IPC机制,包括: 1. Intent:Intent是一种轻量级的IPC方式,可以用来实现不同应用程序之间的通信。通过发送Intent,可以启动其他应用程序的Activity或Service,或者在不同应用程序之间传递数据。 2. Binder:Binder是一种基于进程间通信(IPC)的机制,它是Android系统中进程间通信的基础。通过Binder,可以在不同的进程之间传递对象、调用远程方法等。 3. ContentProvider:ContentProvider是Android中一种特殊的组件,用于在不同的应用程序之间共享数据。通过ContentProvider,可以将数据存储在一个应用程序中,然后在其他应用程序中访问这些数据。 4. Messenger:Messenger是一种轻量级的IPC方式,它基于Binder实现。通过Messenger,可以在不同进程之间传递Message对象。 5. AIDL(Android Interface Definition Language):AIDL是一种专门用于Android的IDL语言,它可以定义跨进程通信(IPC)接口。通过AIDL,可以在不同进程之间传递复杂的数据结构。 6. Socket:Socket是一种基于网络的IPC方式,可以在不同的设备之间进行通信。通过Socket,可以在不同的进程或设备之间传递数据。 以上是Android中常用的IPC机制,不同的IPC机制适用于不同的场景。开发者需要根据具体的需求选择合适的IPC机制

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值