IPC机制

 · 6 mins read

IPC就是跨进程通信。什么是进程呢?它一般指的是一个程序或应用。一个进程可以有多个线程,所以进程和线程是包括与被包括的关系。在Android里主线程就是UI线程,如果有很多任务都放在主线程里,就容易造成ANR,解决办法就是多开线程。

ANR: Activity 5s Service 前台20s 后台200s

Android中的多进程模式

开启多线程

在Android中使用多线程只有一种方法

在Mainfest里面给四大组件(Activity、Service、Receiver、ContentProvider)中给它指定android:process

//默认进程名字就是包名,例如com.example.xiaoming,要开多进程,指定一个process就好
android:process=":remote"
android:process="com.example.xiaoming:remote"

这样我们就开启了另一个线程,这两个线程名字一样,都是com.example.xiaoming:remote,但是第一个与第二个是不一样的,直接以:为开头的进程是当前应用的私有进程,而不以:为开头的则是共享进程。其它应用可以通过ShareUID与它跑在同一进程中。

ShareUID可以让两个应用共享对方的私有数据,如data目录、组件信息等。如果它们还跑在同一进程中,那么他们还可以共享内存。其实这样的他们看起来就是一个应用的两部分了。

多进程问题

如果有一个对象

public class User{
    public static String name = "XiaoMing";
}

Activity A运行在A1进程里,Activity B运行在B1进程里。A把name改成了”XiaoHong”,然后B再去访问name,这时候B获取的名字是”XiaoMing”。原因就是Android给每一个进程都配备了一个独立的虚拟机,这就导致不同进程访问同一个类会拿到不同副本。所以A即使改变了name,也无法影响到B。 一般来说,Android多线程使用不当会造成以下问题

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

第一个就不解释了,第二个也跟第一个差不多。第三个则是因为SharedPreference不支持并发写,否则有机率丢失数据。第四个则是因为一个进程会对应一个Application,所以多开进程自然会多创建Application。

IPC基础概念介绍

这一节会介绍Serializable接口、Parcelable接口和Binder。Serializable接口、Parcelable接口内容都是序列化的内容,它们是为Binder的内容做铺垫的。

Serializable接口

Serializable是Java提供的一个接口,使用Serializable来实现序列化很简单,只要在类的声明里加入一个UID标识就可以实现默认的序列化过程。

private static final long serialVersionUID = 一串数字;

一个类要实现Serializable接口,就比如下面的User,在指定一个serialVersionUID即可。

public class User implements Serializable{
  private static final long serialVersionUID = 437437473233L;

  public int userId;
  public String userName;
}

实现对象的序列化过程和非序列化过程只需要采用ObjectOutputStream和ObjectInputStream就可以轻松实现。

//序列化过程
User user = new User(0, "John");
ObjectOutputStream out = new ObjectOutputStream(
  new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化过程
ObjectInputStream in = new ObjectInputStream(
  new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();

其实User类也可以不写serialVersionUID,直接继承Serializable的,但是这样很有可能会出现问题。原则上序列化后的数据中的serialVersionUID和当前类的serialVersionUID相同才会成功反序列化。这个工作机制是这样的

  1. 序列化的时候系统会把当前类的serialVersionUID写入序列化的文件
  2. 反序列的时候会去检测文件中的serialVersionUID,看是否与当前类的serialVersionUID一致,如果一致说明当前类和序列化的类相比没有什么不同,这个时候就可以反序列化。否则说明当前类和序列化的类相比有了某些变化,例如增减某些变量,这个时候就无法正常反序列化。

一般来说我们应手动指定serialVersionUID,如果不指定的话,系统会自动根据当前类的hash值去生成serialVersionUID,一旦类有改变,例如增减某些变量,hash值会改变,当前类的serialVersionUID就改变了,序列化数据中的serialVersionUID就与它不一致,反序列化就失败了。如果我们有手动指定serialVersionUID的话,即使类发生改变,还能最大程度恢复数据。当然,如果这个类结构发生了毁灭性改变,如类名改变,成员变量的类型改变,反序列化依旧会失败。

需要注意的有两点:

  1. 静态成员变量属于类而不属于对象,所以不会参与序列化过程
  2. 用transient关键字标记的成员变量不参与序列化过程

Parcelable接口

一个类只要实现了Parcelable接口,它就可以通过Intent和Binder传递,下面是一个典型的实现

public class User implements Parcelable{
  public int userId;
  public String userName;

  public Book book;

  public User(int userId, String userName, Book book){
    this.userId = userId;
    this.userName = userName;
    this.book = book;
  }

  public int describeContents(){
    return 0;
  }

  public void writeToParcel(Parcel out, int flags){
    out.writeInt(userId);
    out.writeString(userName);
  }

  public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
    public User createFromParcel(Parcel in){
      return new User(in);
    }
    public User[] newArray(int size){
      return new User[size];
    }
  };

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

}

可以看到,序列化功能是由writeToParcel方法来完成的,反序列化则是由CREATOR完成的。需要注意的是,在User(Parcel in)中,由于book是另一个可序列化对象,所以它的反序列化需要传递当前线程的上下文类加载器。

Serializable接口和Parcelable接口的优缺点

  • Serializable是Java提供的一个接口,其使用简单但是开销很大,序列化和反序列化需要大量的I/O操作。
  • Parcelable是Android的序列化方式,虽然使用起来有点麻烦,但是效率很高,因此我们要首选Parcelable。
  • Parcelable主要用在内存序列上,通过Parcelable序列化到存储设备和网络传输都是可以的,但这个过程有点复杂,所以在序列化到存储设备和网络传输上建议使用Serializable。

Binder

直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android一种跨进程通信方式。从Android应用层来说,Binder是客户端和服务端的他那个新媒介,当bindService的时候,服务端就会返回一个可以调用服务端业务的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或数据。

Android开发中,Binder主要用在特殊的Service中,比如AIDL和Message,普通的Service是不涉及进程调用的,所以无法触及Binder的核心。而Message的底层其实是AIDL,所以这里使用AIDL来阐述Binder的运行机制。 我们可以新建AIDL实例

//Book.java
package com.example.lengary_l.aidltest;

public class Book implements Parcelable{

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName){
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

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

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




    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
}


// Book.aidl
package com.example.lengary_l.aidltest;

// Declare any non-default types here with import statements

parcelable Book;


// IBookManager.aidl
package com.example.lengary_l.aidltest;

import com.example.lengary_l.aidltest.Book;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);
}

在上面的三个文件中,Book.java是图书类,Book.aidl是Book类在AIDL类中的声明,IBookManager是我们定义的接口,getBookList是从远程客户端获得图书列表,addBook是向远程客户端添加一本书。我们看下系统生成的Binder类。

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

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

        /**
         * Cast an IBinder object into an com.example.lengary_l.aidltest.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.example.lengary_l.aidltest.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.lengary_l.aidltest.IBookManager))) {
                return ((com.example.lengary_l.aidltest.IBookManager) iin);
            }
            return new com.example.lengary_l.aidltest.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 {
            //通过code判断是要执行哪一个方法
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.example.lengary_l.aidltest.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.lengary_l.aidltest.Book _arg0;
                    //读取执行方法所需要的参数,例如书籍
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.lengary_l.aidltest.Book.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.example.lengary_l.aidltest.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.example.lengary_l.aidltest.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.example.lengary_l.aidltest.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.lengary_l.aidltest.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.example.lengary_l.aidltest.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);
                    }
                    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.example.lengary_l.aidltest.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.example.lengary_l.aidltest.Book book) throws android.os.RemoteException;
}

我们来介绍一下上一段代码里面的一些变量:

  • DESCRIPTOR:Binder的唯一标识,你可以就把它认为是Binder的身份证
  • onTransact:这个方法运行在服务端的Binder线程池中。当客户端发起跨进程请求的时候, onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服务端通过code判断是哪个方法被执行,接着从data中取出目标方法的参数,然后执行目标方法。执行完毕后就向_reply写入返回值。
  • Proxy#addBook:这个方法运行在客户端,当客户端调用这个方法的时候(意味着客户端服务端不在同一进程),它会调用transact方法,与此同时onTransact方法被调用,它会传入_data和_reply给onTransact,onTransact处理_data,然后写入_reply,这时候Proxy就获得_reply了,它会返回_reply中的数据
  • 内部类Stub:它就是一个Binder类,当客户端和服务端都位于同一个进程的时候,方法调用不会走跨进程的transact过程,当不同进程的时候,就会走transact,这个逻辑是由代理类Proxy来执行的。

Binder

远程服务端实现,这个Service是另外一个进程的

public class BookManagerService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "IOS"));
    }

    public BookManagerService() {
    }

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

在代码里,我们创建了一个Binder对象继承自IBookManager.Stub,并实现了它内部的AIDL方法。需要注意到AIDL方法是在服务端的Binder线程池中执行的,因此当有多个客户端同时连接的时候要处理线程同步。

客户端实现,我们需要先绑定好服务端,将服务端返回的Binder对象转化成AIDL接口(IBookManager.aidl),然后就可以通过这个接口访问服务端的方法了。

public class BookManagerActivity extends AppCompatActivity {
    private Button register;
    private Button sendBook;
    private Button getBookList;
    private IBookManager bookManager;

    private static final String TAG = "BookManagerActivity";
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            bookManager = IBookManager.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        register = findViewById(R.id.register);
        sendBook = findViewById(R.id.send_book);
        getBookList = findViewById(R.id.get_book_list);

        register.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);

                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });

        getBookList.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    List<Book> bookList = bookManager.getBookList();
                    for (int i = 0; i < bookList.size(); i++) {
                        Log.i(TAG, "book list is: " + bookList.get(i).getBookName());
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

需要注意的是,调用bookManager的方法可能会导致ANR,因为服务端的方法可能需要很久要执行完毕,所以在日常开发中要注意这个情况。

从上面我们可以看出大致的流程,Service实现IBookManager的内部虚拟类IBookManager.Stub(),也就是实现addBook(),getBookList(),并把它作为binder返回给客户端,客户端拿到binder后,直接IBookManager.Stub.asInterface(binder)拿到代理Proxy,就通过代理去执行服务端的方法了。(代理模式的作用就是拦截请求,让其在binder线程池中执行)。