69

IPC机制

 5 years ago
source link: http://coderlengary.github.io/2018/08/18/IPC/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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来执行的。
VNBru2u.png!web

远程服务端实现,这个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线程池中执行)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK