IPC(跨进程)基础知识

来源:互联网 发布:azis 知乎 编辑:程序博客网 时间:2024/05/24 15:41

Android IPC简介
IPC是Inter-Process Communication的缩写,含义为进程间或者跨进程通信,是指两个进程之间进行数据交换的过程。
进程和线程
线程是cpu调度的最小单元,同时线程是一种有限的系统资源。
进程一般指一个执行单元,在PC和移动设备上值一个程序或者一个应用。一个进程可以包含多个线程。
因而线程和进程是包含与被包含的关系。最简单情况一个进程中可以只有一个线程,即主线程。


Android中多进程模式
在Android中,通过给四大组件在AndroidMenifest指定android:process属性,可以开启多进程模式。
比如:

        <activity            android:name=".MainActivity"            android:launchMode="standard" >            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>        <activity            android:name=".SecondActivity"            android:process=":remote" />        <activity            android:name=".ThirdActivity"            android:process="com.ryg.chapter_2.remote" />

当前应用包名为com.ryg.chapter_2
当入口MainActvity启动时,它运行在默认进程中,进程名是com.ryg.chapter_2;
当SecondActivity启动时,系统会为它创建一个进程,进程名是com.ryg.chapter_2:remote;
当ThirdActivity启动时,系统也会为它创建一个进程,进程名是com.ryg.chapter_2.remote;
这样就开启了多进程了,通过shell来查看一个包中当前所在的进程信息:
adb shell ps —->查看正在运行的进程
adb shell “ps | grep com.ryg.chapter_2”—–>grep 后面的名称用来过滤指定进程
这里写图片描述
进程名以”:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
进程中不以“:”开头的进程属于当前应用的全局进程,其他应用的组件通过ShareUID方式可以和它跑在同一个进程中。
多进程模式的运行机制
Android系统为每个应用分配了一个独立的虚拟机,或者说是为每个进程都分配了一个独立的虚拟机,
不同的的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。
比如创建一个类

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

那么在这上面三个进程中都会存在一个A类,并且这个三个类是互不干扰的。在一个进程中修改sUserId的值,只会影响当前进程,
对其他进程不会造成任何影响。比如在MainActivity中是的sUserId=2,但是在SecondActivity中sUserId任然是1。它们互不影响。
所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。
多进程会造成如下几个方面的问题:
1.静态成员和单例模式完全失效;
比如类A在MainActivity所在进程有一份副本,在SecondActivity所在进程也有一份副本,完全不同的两个A类,它们之间没有关系
所以静态变量也不可能唯一,单例也谈不上,两个进程之间的不同对象。
2.线程同步机制完全失效
因为是在两个进程中,锁的对象完全不一样.
3.SharedPreferences的可靠性下降
sSharedPreferences不支持两个进程同时去执行写操作,否则会造成一定几率数据丢失。
SharedPreferences的底层是通过读写XML文件来实现,并发写显然会出问题。
4.Application会多次创建
系统在创建新的进程的同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。那么自然就会创建新的Application;

IPC基础概念介绍

Serializable接口
有两点需要注意的:
1.静态成员变量属于类不属于对象,所以不会参与序列化和反序列化的过程
2.用transient关键字标记的成员变量不会参与JAVA虚拟机默认的序列化,但是可以通过重写系统默认的序列化方法参与序列化;
更详细可以看这里 对象的序列化和反序列化—使用Serializable接口

Parcelable接口
也是一个对象序列化和反序列化的接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
下面是用法:

public class User implements Parcelable {    private int userId;    private String userName;    private boolean isMale;    private Book book;// Book类也实现了Parcelable    // 内容描述    @Override    public int describeContents() {        return 0;    }    // 序列化功能    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(userId);        dest.writeString(userName);        dest.writeInt(isMale ? 0 : 1);        dest.writeParcelable(book, 0);    }    // 反序列化功能    public static final Parcelable.Creator<User> CREATOR = new Creator<User>() {        @Override        public User[] newArray(int size) {            return new User[size];        }        @Override        public User createFromParcel(Parcel source) {            return new User(source);        }    };    private User(Parcel in) {        userId = in.readInt();        userName = in.readString();        isMale = in.readInt() == 0;        // book是另一个可序列化对象,所以它的序列化过程需要传递当前线程的上下文类加载器        book = in.readParcelable(Thread.currentThread().getContextClassLoader());        }}

序列化过程需实现的功能有序列化反序列化内容描述
wireToParcel完成序列化功能,通过Parcel的一系列方法write方法完成
CREATOR完成反序列化功能,内部表明了如何创建序列化的对象和数组,通过Parcel的一系列read方法完成反序列化。
(注:Parcel内部包装了可序列化的数据,可以在Binder中自由传输)
decribeContents方法完成内容描述功能 。几乎所有情况都返回的是0,仅当当前对象存在文件描述时返回1.

Parcelable和Serializalbe的比较:
Serializable是java中的接口,使用简单,开销很大,需要大量IO操作。
Parcelable是Android中的接口,使用麻烦,开销小点,效率很高,主要用在内存序列化上。
做Android开发首选Paracelable.
如果是将对象序列化到存储设备中或者将对象序列化后通过网络传输,两种都是可以的,
但Parcelable稍显复杂,这个时候就选择使用Serializable.

Binder
直观来说Binder是Android中的一个类,它实现了IBinder;
从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dec/binder;
从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActvityManager.WindowManager,等等)和相应ManagerService的桥梁;
从Android应用层来说,Binder是客户端和服务端进行通信的媒介;

Binder的工作机制

下面使用AIDL(Android进程间通信接口定义语言)生成的Binder接口类来分析:
AIDL代码文件如下,这里省略了Book类代码了:

package com.ryg.chapter_2.aidl;import com.ryg.chapter_2.aidl.Book;//Book类在aidl中的声明interface IBookManager{ List<Book> getBookList(); void addBook(in Book book);}

下面就是系统帮我们生成的java类,已经注释了:

package com.ryg.chapter_2.aidl;//系统生成的这个Binder接口类,所有在Binder中传输的接口都需要继承IInterface接口public interface IBookManager extends android.os.IInterface {    // 这个类就是一个Binder    public static abstract class Stub extends android.os.Binder implements            com.ryg.chapter_2.aidl.IBookManager {        // Binder的唯一标识,一般用当前Binder接口类名表示        private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";        public Stub() {            this.attachInterface(this, DESCRIPTOR);        }        // 用于将服务端的Binder对象转换成客户端端所需的AIDL接口类型的对象,这种转换是区分进程的        // 如果客户端和服务端位于同一进程,那么此方法就返回会服务端的Stub对象本身        // 反之不是一个进程,此方法返回系统封装后的Stub.Proxy对象        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);        }        // 此方法用于返回当前的Binder对象        @Override        public android.os.IBinder asBinder() {            return this;        }        // 此方法运行在服务端的线程池中,        // 当客户端发起跨进程请求时,远程请求通过系统底层后交由此方法处理        // code:服务端通过code可以确定客户端所请求得目标方法是什么,        // data:如果目标方法有参数的话,会从data中取出目标方法所需的参数        // reply:如果目标方法有返回值的话,就向reply中写入返回值        //flags:0表示双向有返回值,1,表示没有返回值        // 如果此方法返回false,那么客户算请求失败,可以利用这个特性来做权限验证。不希望任意进程都能调用我们服务        @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);        }        // Stub类中的Proxy代理类        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);                    //发起RPC(远程过程调用)请求,客户端的调用的当前线程挂起                    //然后服务端onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行                    //并从RPC过程的返回结果                    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();                }            }        }        // 这个id表示方法getBookList        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);        // 这个id标识方法addBook        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    }    }

有两点需要注意
1.当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能再UI线程中发起此次远程请求。
2.由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管耗时与否都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
(onTransact运行在服务端的Binder线程池中,在这个方法内部调用了Binder方法getBookList和addBook方法)
总结:Stub和Stub.Proxy是上面接口实现的核心。
Stub类就是一个Binder的子类,如果客户端和服务端在同一个进程中,那就直接通过服务端这个Binder对象实例来直接调用服务端的Binder方法(getBookList或者addBook)。
Stub.Proxy类中封装了一个Binder引用,在这里Proxy相当于一个代理,当客户端和服务端不在同一个进程中的时候,会通过内部的Binder对象去发起RPC(远程过程调用),在调用到服务端的onTransact方法。接着在调用Binder方法。

            private android.os.IBinder mRemote;            Proxy(android.os.IBinder remote) {                mRemote = remote;            }

当然这个过程不通过AIDL也可以手动实现。
当Binder死亡的时候也可以给Binder设置死亡代理
当服务端进程由于某种原因异常终止,这个时候连接断裂。成为Binder死亡。
Binder中配对方法linkToDeath()和unlinkToDeath()
给Binder设置死亡代理回调

 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {         //Binder死亡的时候会回调这个方法        @Override        public void binderDied() {            Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());            if (mRemoteBookManager == null)                return;            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);            mRemoteBookManager = null;            //这里重新绑定远程Service        }    };

给Binder设置死亡代理

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

其实ServiceConnection中也可以判断,当服务端进程异常终止时,onServiceDisconnected会被回调。

private ServiceConnection mConnection = new ServiceConnection() {        public void onServiceConnected(ComponentName className, IBinder service) {            IBookManager bookManager = IBookManager.Stub.asInterface(service);                   }        public void onServiceDisconnected(ComponentName className) {            mRemoteBookManager = null;                   }    };

以上就是IPC的基础知识。

0 0
原创粉丝点击