天天看点

IPC

一、Linux知识点

  • Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。
  • 进程隔离:是一个进程不能直接操作或访问另一个进程
  • 内核空间(Kernel  Space)是Linux内核运行空间,用户空间(User  Space)是用户程序运行空间;内核空间的数据是共享的,而用户空间则不可以数据共享。
  • 系统调用:用户空间需要访问内核空间,就需要借助系统调用来实现,这是用户空间访问内核空间的唯一方式,提升了系统安全性和稳定性。
  • 用户进程交互:通过系统函数和内核空间进行交互,在内核中开辟一块缓存区,进程1把数据从用户空间拷贝到内核缓存区,进程2再从内核缓存区把数据读取,这就是一次IPC,需要拷贝两次。

        copy_from_user   将用户空间的数据拷贝到内核空间的内核缓存区

        copy_to_user   将内核空间的内核缓存区的数据拷贝到用户空间

IPC

    缺点:接收数据的缓存是要由接收方提供,但接收方不知道需要多大的缓存

    方案:

  1. 开辟尽量大的,浪费空间,
  2. 先获取消息大小,浪费时间
  • Linux提供了多种进程间通信机制,这些都是需要内核空间做支持,主要有管道(pipe),信号(sinal),消息队列(Message),共享内存(Share Memory),套接字(socket)。
IPC
  • 进程A和进程B可以通过系统函数和内核空间进行交互,得copy两次,效率低下,接收数据的缓存是要由接收方提供,但接收方不知道需要多大的缓存(解决方式1:开辟尽量大的,浪费空间,方式2:先获取消息大小,浪费时间)

        copy_from_user   将用户空间的数据拷贝到内核空间的内核缓存区

        copy_to_user   将内核空间的内核缓存区的数据拷贝到用户空间

IPC
  • 内存映射

       内存映射(Memory-mapped)使一个磁盘文件与存储空间中的一个缓存区相映射,当从缓存区中取数据,就相当于读文件中的相应字节,于此类似,将数据存入缓存区,则相应的字节就自动写入文件。

      使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中,这个映射工作通过mmap函数来实现。

IPC

二、Binder

1、Binder驱动

     Binder不是Linux系统内核的一部分,要实现IPC通行,得益于Linux的动态内核可加载模块(LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行,它在运行时被链接到内核作为内核的一部分运行。Android系统通过动态添加一个内核模块运行在内核空间,这个内核模块就是Binder驱动。

2、Binder  IPC底层通信原理

IPC

通信步骤如下:

  1. Binder驱动在内核空间创建一个数据接收缓存区
  2. 建立内核缓存区与数据接收缓存区(在内核中)之间的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系
  3. 发送方进程通过系统调用copy_from_user()函数将数据copy到内核中的内核缓存区中,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,整个过程只用了1次拷贝,就完成了一次单行通行。  

3、Binder通信过程

  1. 一个进程通过Binder驱动将自己注册成为ServiceManager
  2.  servicer通过驱动向ServiceManager中注册Binder(Service的binder实体),驱动为这个Binder创建实体的引用,并在ServiceManager中加入到查找表。
  3. client通过名字,在Binder驱动下从ServiceManager中获取对Binder实体的引用,通过这个引用就能实现和Server进程的通信。
IPC

4、Binder通信中的代理模式

     A进程想获取B进程的object时,驱动并不会真的把object返回给A,而是返回了一个object看起来一模一样的代理对象objectProxy,在A进程中调用objectProxy的方法,会通知Binder驱动,Binder查询自己维护的表单,发现是B进程object的代理对象,于是通知B进程调用该方法,并要求B进程把返回结构发给自己。当驱动拿到B进程返回的结果后会转发给A进程,一次通信就完成了。

三、进程间通信

(1)进程间通信方式对比

名称 优点 缺点 适用场景
intent 简单易用 数据类型为了Bundle支持的 四大组件的进程间通信
文件共享 简单易用 不适合高并发 数据共享
AIDL 功能强大,支持一对多并发实时通信 线程同步 复杂的进程间通信,最常用
Messenger 比aidl简单 串行 简单的进程间通信
ContentProvider 数据共享
RemoteViews
Socket 跨主机,通信范围广 传输原始的字节流 网络通信

(2)开启多进程

         在Androidmenifest中给四大组件指定android:process开启多进程模式

(3)多进程造成的问题(各个进程都是独立的虚拟机)

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降:因为sp不支持两个进程并发进行读写,有一定几率导致数据丢失
  • Application会多次创建

四、序列化

(1)Bundle传递对象为啥需要序列化

         因为Bundle传递数据时只支持基本数据类型,所以在传递对象时需要序列化转换成可存储或可传输的本质状态(字节流)。

(2)序列化的两种方式

Serializable接口 Parcelable接口
平台 java提供的 android提供的
序列化原理 将一个对象装换成可存储或可存储的状态 将一个对象进程分解,分解后的每一部分都是传递可支持的数据类型
优缺点 简单,效率低,开销大,大量I/O操作 高效,使用较麻烦
使用场景 适合将对象序列化到存储设备或将对象系列化通过网络设备传输 主要用在内存的序列化

(3)代码示例

public class Student implements Serializable {
    private static final long serialVersionUID = -4211449776975163975L;
    
    private String name;
}

public class Student implements Parcelable {
  
    private String name;

    protected Student(Parcel in) {
        name = in.readString();
    }

    //序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
    }

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

    //反序列化
    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            return new Student(in);
        }

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

五、Messenger

       Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,它是以串行的方式来处理客户端发来的消息,因此在服务端我们不需要考虑线程同步的问题。

public Messenger(Handler target) {
  mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
  mTarget = IMessenger.Stub.asInterface(target);
}
           

实现事例:

     服务端:onbind方法返回自己封装好的Messenger,msg获取的replyTo属性是客户端封装的messenger

public class MessengerService extends Service {

    private static class Messengerhandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
          switch (msg.what){
            case 1:
                //接受客户端用msg键发送的消息
                msg.getData().getString("msg")

                //给客户端发消息
                Messenger  client=msg.replyTo;
                client.send(message);             

                break;
          }
          super.handleMessage(msg);
       }
   }

private final Messenger mMessenger = new Messenger(new Messengerhandler());

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
   }
}
           

      客户端:onserviceConnected获取服务端返回的messenger,发送消息时给msg.replyTo赋值为自己封装的messenger

private  Messenger  mService;

void bindMyMessengerService(){
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            sendData();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
    Intent intent = new Intent(this,MessengerService.class);
    bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}

private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
private static class ClientMessenger extends Handler{
    @Override
    public void handleMessage(Message msg) {
       switch (msg.what){
          case 1:
           //服务端发来的消息
           msg.getData().getString("reply"));
           break;
}


void sendData(){
    Message msg = Message.obtain(null,1);
     msg.replyTo = mCilentMessenger;//设置客户端接受消息的Messenger
    Bundle data = new Bundle();
    data.putString("msg","客户端的消息");
    msg.setData(data);
    try{
        mService.send(msg);
    }catch (RemoteException e) {
        e.printStackTrace();
    }
}
           

六、AIDL

(1)AIDL的定义

         全称Android  Interface  Definition  Language,Android接口定义语言;如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。

本质是系统提供了一套可快速实现BInder的工具。

(2)AIDL文件支持的数据类型:

  • 基本数据类型(int  long  char boolean  double等)
  • String和CharSequence
  • List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
  • Map:值支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  • Parcelable :所有实现了Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用, 其中自定义的Parcelable对象和AIDL对象必须显示import进来,并且还要创建同名的.aidl文件

(3)aidl运用

  服务端:

(1)新建一个aidl类型的文件(里面定义了一个接口),文件最好在main文件夹下新建一个文件夹aidl,不要在java文件夹下

package aidl;

// Declare any non-default types here with import statements
interface MyAIDLService {
    
    int getNum(int num1,int num2);
}
           

然后build下项目,我们可以在build-->genetated-->aidl-->debug下生一个同名的aidl文件,说明aidl文件便宜成功。如下图:

IPC

(2)新建Service,并创建中间类,中间类要实现上面定义的aidl.Stub,实例代码如下

package shop.lantian.com.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import aidl.MyAIDLService;

public class AidlService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyServiceBind();
    }

    class MyServiceBind extends MyAIDLService.Stub {

        @Override
        public int getNum(int num1, int num2) throws RemoteException {
            return num1 + num2;
        }
    }
}
           

(3)配置AndroidManifest.xml,实例如下,

<service android:name=".AidlService">
      <intent-filter>
        <action android:name="shop.lantian.com.service.AidlService"/>
      </intent-filter>
 </service>
           

客户端:

(1)复制服务端的aidl文件原封不动的复制到客户端(注意,包名路径要一模一样)

IPC

(2)创建ServiceConnection接口的实例,覆写onServiceConnected方法和onServiceDisConnected,重点在OnServiceConnection方法

private MyAIDLService mBinder;

    public class MainServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = MyAIDLService.Stub.asInterface(service);
            if (mBinder != null) {
                try {
                    int end = mBinder.getNum(1, 2);
                    Log.e("aaa", String.valueOf(end));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
           

(3)调用service

MainServiceConnection connection = new MainServiceConnection();
 Intent intent = new Intent();
 intent.setAction("shop.lantian.com.service.AidlService");
 //从 Android 5.0开始 隐式Intent绑定服务的方式已不能使用,所以这里需要设置Service所在服务端的包名
 intent.setPackage("shop.lantian.com.service");
 bindService(intent, connection, BIND_AUTO_CREATE);
           

七、AIDL源码分析

       Binder是Android的一个类,它实现了IBinder的接口,是Android中的一种跨进程通信方式。

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

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

        /**
         * Cast an IBinder object into an com.xsx.ipcdemo.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.xsx.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.xsx.ipcdemo.IBookManager))) {
                return ((com.xsx.ipcdemo.IBookManager) iin);
            }
            return new com.xsx.ipcdemo.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.xsx.ipcdemo.Booker> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.xsx.ipcdemo.Booker _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.xsx.ipcdemo.Booker.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.xsx.ipcdemo.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.xsx.ipcdemo.Booker> 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.xsx.ipcdemo.Booker> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.xsx.ipcdemo.Booker.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.xsx.ipcdemo.Booker booker) 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 ((booker != null)) {
                        _data.writeInt(1);
                        booker.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

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

    public java.util.List<com.xsx.ipcdemo.Booker> getBookList() throws android.os.RemoteException;

    public void addBook(com.xsx.ipcdemo.Booker booker) throws android.os.RemoteException;
}
           
  • 实现AIDL的接口都得实现android.os.IInterface接口
  • 内部类Stub类,BInder的实现类,服务端听过这个类来提供服务。
  • DESCRIPTOR----唯一标识,标识的是哪个Stub,一般用当前Binder的类名的完整路径表示,    
  • asInterface(android.os.IBinder   obj)

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

  • asBinder()    根据当前调用情况返回代理Proxy的Binder对象
  • onTransact()

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

       public  Boolean  onTransact(int code,android.os.parcel  data,android.os.Parcel  reply,int  flags)

       返回值为false标识客户端的请求会失败,可以利用这个特性来做权限验证;code是标识客户端调用的方法,data是目标方法所需要的参数,reply是目标方法执行完毕后,向reply中写入返回值。

  • transact()

   运行在客户端,当客户端发起远程请求的同时将当前线程挂起,之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行

  • Proxy#客户端定义的method

        当客户端调用此方法时,首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象,然后把该方法的参数信息写入data中,接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出返回的结果。

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