二、IPC机制(IPC介绍)

来源:互联网 发布:mysql数据库热迁移 编辑:程序博客网 时间:2024/05/02 00:21

IPC机制

1.介绍

IPC是Inter-Progress Communication的缩写,含义为进程间通信或者跨进程通信。是指两个进程之间进行数据交换的过程。

Android里面最有特色的进程间的通信方式就是Binder,通过它可以轻松的实现进程间的通信,Android也支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然一个设备上的两个进程通过Socket通信自然也是可以的。

2.Android中的多进程模式

正常情况下Android中多进程指一个应用中存在多个进程的情况。在Android中使用多进程只有一种方式,那就是给四大组件在Manifest中指定android:progress属性。
就是说不能为一个线程,一个实体类指定其运行时候所在的进程。(其实还有一种方式,就是使用JNI在Native层去fork一个新的进程,这中方法属于特殊情况,也不是常用的创建多进程的方式)。

        <activity            android:name= ".MainActivity"            android:configChanges= "orientation|screenSize"            android:label= "@string/app_name"            android:launchMode= "standard" >            <intent-filter >                <action android:name ="android.intent.action.MAIN" />            </intent-filter >        </activity >        <activity            android:name= ".SecondActivity"            android:configChanges= "screenLayout"            android:label= "@string/app_name"            android:process= ":remote" />        <activity            android:name= ".ThirdActivity"            android:configChanges= "screenLayout"            android:label= "@string/app_name"            android:process= "com.ryg.chapter_2.remote" />

上面的例子中假如当前的应用的包名为com.ryg.chapter_2,那么当SecondActivity启动的时候系统会为它创建一个单独的进程,进程名为com.ryg.chapter_2:remote,当ThirdActivity启动的时候系统也会为它创建一个单独的进程com.ryg.chapter_2.remote,由于MainActivity没有指定Progress属性,运行在默认的进程中,默认的进程是包名。

使用 : 和直接命名的区别:
1. : 的含义是在当前进程名的前面加上当前的包名,这是一种简单的写法,而另外一种则是完整的命名方式,不会附加包名信息。
2.使用 : 的进程表示它属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,不使用 : 的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

注意
如果两个进程有相同的ShareUID并且签名相同,它们可以互相访问对方的私有数据,比如data目录,组件信息等。
如果跑在同一个进程中(前提是有相同的ShareUID并且前面相同),除了能共享data目录,组件信息,还可以共享内存数据或者说它们看起来就像一个应用的两个部分。

Android为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个类的对象会产生多份副本。

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

在MainActivity的onCreate中把这个值赋值为2,启动SecondActivity,发现值任然为1,可以看到多进程绝非仅仅指定一个android:progress属性那么简单。

比如,在进程com.ryg.chapter_2和进程com.ryg.chapter_2:remote中都存在一个UserManager类,并且这两个类是互不干扰的,在同一个进程中修改sUserId的值,只会影响当前的进程。对其他进程不会造成任何影响。

所有运行在不同进程中的四大组件,只要他们之间需要通过内存来共享数据,都会共享失败,这也是多进程带
来的主要影响。正常情况下,四大组件中间不可能不通过一些中间层来共享数据,那么简单的指定进程名来开启多进程都会无法正确的运行。

当然,特殊情况下,某些组件之间不需要共享数据,这个时候可以指定android:progress来开启多进程,但是这种场景是不常见的,几乎所有的情况都需要共享数据。

一般来说,使用多进程会造成如下几方面的问题:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效
(3)SharePreferences的可靠性下降
(4)Application多次创建

3.IPC基础概念介绍

(1)Serializable接口
Serializable是java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。
通过Serializable方式实现对象的序列化,实现起来非常简单,几乎所有的工作都被系统自动完成了。
反序列化也非常简单,需要采用ObjectOutputStream和ObjectInputStream即可。

// 序列化User user = new User(0,"jake",true);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.dat"));out.write(user);out.close();
// 反序列化ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.dat"));User newUser = (User) in.readObject();in.close();

serialVersionUID用来辅助序列化和反序列化的,在反序列化的时候会去检测文件中serialVersionUID和当前对象中的serialVersionUID是否相同,相同才可以反序列化。
所以我们应该手动的指定serialVersionUID,这样即使删除了一些变量或者增加了一些变量任然可以成功的反序列化。

(2)Parcelable接口

Book类:package com.ryg.chapter_2.aidl;import android.os.Parcel;import android.os.Parcelable;public class Book implements Parcelable {    public int bookId;    public String bookName;    public Book() {    }    public Book(int bookId, String bookName) {        this.bookId = bookId;        this.bookName = bookName;    }    public int describeContents() {        return 0;    }    public void writeToParcel(Parcel out, int flags) {        out.writeInt(bookId);        out.writeString(bookName);    }    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {        public Book createFromParcel(Parcel in) {            return new Book(in);        }        public Book[] newArray(int size) {            return new Book[size];        }    };    private Book(Parcel in) {        bookId = in.readInt();        bookName = in.readString();    }    @Override    public String toString() {        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);    }}
User类:package com.ryg.chapter_2.model;import java.io.Serializable;import com.ryg.chapter_2.aidl.Book;import android.os.Parcel;import android.os.Parcelable;public class User implements Parcelable, Serializable {    private static final long serialVersionUID = 519067123721295773L;    public int userId;    public String userName;    public boolean isMale;    public Book book;    public User() {    }    public User(int userId, String userName, boolean isMale) {        this.userId = userId;        this.userName = userName;        this.isMale = isMale;    }    public int describeContents() {        return 0;    }    public void writeToParcel(Parcel out, int flags) {        out.writeInt(userId);        out.writeString(userName);        out.writeInt(isMale ? 1 : 0);        out.writeParcelable(book, 0);    }    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();        isMale = in.readInt() == 1;        book = in                .readParcelable(Thread.currentThread().getContextClassLoader());    }    @Override    public String toString() {        return String.format(                "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",                userId, userName, isMale, book);    }}

Parcel内部包装了可序列化的数据,可以在Binder中自由传输。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法
来完成的。反序列化功能由CREATOR来完成。其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列的read方法来完成反序列化过程。

内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅仅当当前的对象中存在文件描述符时,此方法返回1。

Parcelable的方法说明:
createFromParcel(Parcel in) 从序列化的对象中创建原始对象
newArray(int size)创建指定长度的原始对象数组
User(Parcel in)从序列化后的对象中创建原始对象
writeToParcel(Parcel out, int flags)将当前对象写入序列化结构中
describeContents返回当前对象的内容描述

区别:
Serializable是java中的序列化接口,使用起来简单但是开销很大,序列化和反序列化过程中需要大量的I/O操作。
Parcelable是Android中的序列化方式,因此更适合用在Android平台上,缺点就是使用起来稍微麻烦点。但是它的效率很高,这是Android推荐的序列换方式。
因此我们要首选Parcelable。

Parcelable主要用在内存序列化上,通过Parcelable将对象序列化在存储设备中或者将对象序列化以后通过网络传输也都是可以的,但是这个过程会稍显复杂。
因此在这两种情况下建议使用Serializable。

(3)Binder
直观来说Binder是Android中的一个类,它继承Ibinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信的方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有。
从Android Framework的角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁。
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信。Messenger的核心其实是AIDL。

IBookManager.aidl类:package com.ryg.chapter_2.aidl;import com.ryg.chapter_2.aidl.Book;import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener;interface IBookManager {     List<Book> getBookList();     void addBook(in Book book);}
在功gen里面自动生成的java类:/** This file is auto-generated.  DO NOT MODIFY.* Original file: E:\\Project\\android-art-res-master\\Chapter_2\\src\\com\\ryg\\chapter_2\\aidl\\IBookManager.aidl*/package com.ryg.chapter_2.aidl;public interface IBookManager extends android.os.IInterface {     /** Local-side IPC implementation stub class. */     public static abstract class Stub extends android.os.Binder implements               com.ryg.chapter_2.aidl.IBookManager {          private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";          /** Construct the stub at attach it to the interface. */          public Stub() {               this.attachInterface(this, DESCRIPTOR);          }          /**          * Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager          * interface, generating a proxy if needed.          */          public static com.ryg.chapter_2.aidl.IBookManager asInterface(                    android.os.IBinder obj) {               if ((obj == null)) {                    return null;               }               android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);               if (((iin != null) && (iin instanceof com.ryg.chapter_2.aidl.IBookManager))) {                    return ((com.ryg.chapter_2.aidl.IBookManager) iin);               }               return new com.ryg.chapter_2.aidl.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.ryg.chapter_2.aidl.Book> _result = this                              .getBookList();                    reply.writeNoException();                    reply.writeTypedList(_result);                    return true;               }               case TRANSACTION_addBook: {                    data.enforceInterface(DESCRIPTOR);                    com.ryg.chapter_2.aidl.Book _arg0;                    if ((0 != data.readInt())) {                         _arg0 = com.ryg.chapter_2.aidl.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.ryg.chapter_2.aidl.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.ryg.chapter_2.aidl.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.ryg.chapter_2.aidl.Book> _result;                    try {                         _data.writeInterfaceToken(DESCRIPTOR);                         mRemote.transact(Stub.TRANSACTION_getBookList, _data,                                   _reply, 0);                         _reply.readException();                         _result = _reply                                   .createTypedArrayList(com.ryg.chapter_2.aidl.Book.CREATOR);                    } finally {                         _reply.recycle();                         _data.recycle();                    }                    return _result;               }               @Override               public void addBook(com.ryg.chapter_2.aidl.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.ryg.chapter_2.aidl.Book> getBookList()               throws android.os.RemoteException;     public void addBook(com.ryg.chapter_2.aidl.Book book)               throws android.os.RemoteException;}

每个方法的含义:
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名表示。

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

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

onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求的时候,远程请求会通过系统底层封装后交给此方法处理。该方法的原型是protected boolean onTransact( int code, Parcel data, Parcel reply,int flags)。
服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需要的参数(如果目标方法有参数的话)然后执行目标方法。
当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact的执行过程就是这样。
如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟不希望随便一个进程都能远程调用我们的服务。

Proxy#getBookList
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:
首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel象_reply和返回值对象List;
然后把该方法的参数信息写入_data中(如果有参数的话);
接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;
然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;
最后返回_reply中的数据。

Proxy#addBook
这个方法执行在客户端,执行过程和getBookList是一样的,addBook没有返回值,所以不需要从_reply中取出返回值。

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

从上面的分析过程来看,完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。
自己实现:
(1)声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只有一个asBinder方法。

package com.ryg.chapter_2.manualbinder;import java.util.List;import android.os.IBinder;import android.os.IInterface;import android.os.RemoteException;public interface IBookManager extends IInterface {    static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder.IBookManager";    static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);    static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);    public List<Book> getBookList() throws RemoteException;    public void addBook(Book book) throws RemoteException;}

可以看到,在接口中声明了一个Binder描述符和另外两个id,这两个id分别表示getBookList(),和addBook()方法。

(2)实现Stub类和Stub类中的Proxy代理类

package com.ryg.chapter_2.manualbinder;import java.util.List;import android.os.Binder;import android.os.IBinder;import android.os.Parcel;import android.os.RemoteException;public class BookManagerImpl extends Binder implements IBookManager {    /** Construct the stub at attach it to the interface. */    public BookManagerImpl() {        this.attachInterface(this, DESCRIPTOR);    }    /**     * Cast an IBinder object into an IBookManager interface, generating a proxy     * if needed.     */    public static IBookManager asInterface(IBinder obj) {        if ((obj == null)) {            return null;        }        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);        if (((iin != null) && (iin instanceof IBookManager))) {            return ((IBookManager) iin);        }        return new BookManagerImpl.Proxy(obj);    }    @Override    public IBinder asBinder() {        return this;    }    @Override    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)            throws RemoteException {        switch (code) {        case INTERFACE_TRANSACTION: {            reply.writeString(DESCRIPTOR);            return true;        }        case TRANSACTION_getBookList: {            data.enforceInterface(DESCRIPTOR);            List<Book> result = this.getBookList();            reply.writeNoException();            reply.writeTypedList(result);            return true;        }        case TRANSACTION_addBook: {            data.enforceInterface(DESCRIPTOR);            Book arg0;            if ((0 != data.readInt())) {                arg0 = Book.CREATOR.createFromParcel(data);            } else {                arg0 = null;            }            this.addBook(arg0);            reply.writeNoException();            return true;        }        }        return super.onTransact(code, data, reply, flags);    }    @Override    public List<Book> getBookList() throws RemoteException {        // TODO 待实现        return null;    }    @Override    public void addBook(Book book) throws RemoteException {        // TODO 待实现    }    private static class Proxy implements IBookManager {        private IBinder mRemote;        Proxy(IBinder remote) {            mRemote = remote;        }        @Override        public IBinder asBinder() {            return mRemote;        }        public java.lang.String getInterfaceDescriptor() {            return DESCRIPTOR;        }        @Override        public List<Book> getBookList() throws RemoteException {            Parcel data = Parcel.obtain();            Parcel reply = Parcel.obtain();            List<Book> result;            try {                data.writeInterfaceToken(DESCRIPTOR);                mRemote.transact(TRANSACTION_getBookList, data, reply, 0);                reply.readException();                result = reply.createTypedArrayList(Book.CREATOR);            } finally {                reply.recycle();                data.recycle();            }            return result;        }        @Override        public void addBook(Book book) throws RemoteException {            Parcel data = Parcel.obtain();            Parcel reply = Parcel.obtain();            try {                data.writeInterfaceToken(DESCRIPTOR);                if ((book != null)) {                    data.writeInt(1);                    book.writeToParcel(data, 0);                } else {                    data.writeInt(0);                }                mRemote.transact(TRANSACTION_addBook, data, reply, 0);                reply.readException();            } finally {                reply.recycle();                data.recycle();            }        }    }}

手动去写的意义在于更加理解Binder的工作原理,同时提供了一种不通过AIDL文件来实现Binder的新方式。如果手写的Binder,那么在服务端只需要创建一个BookManagerImpl的对象,并在Service中返回即可。

(4)Binder中两个很重要的方法
linkToDeath,unlinkToDeath
Binder运行在服务端进程中,如果服务端的进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败,更为关键的是,由于客户端不知道Binder连接已经断裂,那么客户端的功能就会受到影响。

为了解决这个问题,Binder中提供了两个配对的方法linkToDeath,unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

方法:
首先声明一个DeathRecipient对象,DeathRecipient是一个接口,内部只有一个方法,binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移除之前绑定的binder代理并重新绑定远程服务。

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {        @Override        public void binderDied() {            Log. d(TAG, "binder died. tname:" + Thread.currentThread().getName());            if ( mRemoteBookManager == null)                return;            mRemoteBookManager.asBinder(). unlinkToDeath(mDeathRecipient, 0);            mRemoteBookManager = null;            // TODO:这里重新绑定远程Service        }    };

其次在客户端绑定远程服务成功之后,给Binder设置死亡代理:

mRemoteBookManager .asBinder().linkToDeath(mDeathRecipient, 0);

其中linkToDeath的第二个参数,是个标记位,我们直接设置为0即可。

通过上面的步骤就给Binder设置了死亡代理,当Binder死亡的时候就可以收到通知了,另外通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

0 0