Android IPC机制(一)

Android IPC机制简介

1.IPC是Inter-Process-Communication是缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。首先来说一下什么是进程,什么是线程。按照操作系统中描述的概念,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC或者移动设备上指一个程序或者一个应用,一个进程可以包含多个线程,因此进程和线程是包含和被包含的关系。最简单的情况,一个进程中可以只有一个线程,即主线程,在android中叫做UI线程,在UI线程中才能操作界面元素,很多时候在一个进程中要执行许多耗时操作,如果这些操作放到主线程中就会导致界面无法响应,严重影响用户体验,这种情况在PC系统和移动系统中都有,在android中叫做ANR,即应用无响应,解决这个问题就要用到线程,即把耗时操作放到子线程中去。
2.IPC机制不是android所特有的,任何一个操作系统都有其对应的IPC机制。安卓虽然是基于Linux操作系统,但是它有他自己的进程通信机制,在Android中最具特色的进程通信方式就是Binder了,通过Binder可以轻松的实现进程间通信。除了用Binder,Android 还支持Socket也可以实现任意两个终端之间的通信,当然在同一个设备上的两个进程通过Socket通信自然也可以。
3.说道IPC,就必须提到多进程,多进程的情况分为两种。第一种情况是一个应用因为自身需要采用多进程模式来实现,原因有多种,比如某些模块由于特殊原因需要运行在单独的进程中,又或者为了加大一个应用可使用的内存所有采用多进程来获取多份内存空间。安卓对单个所使用的最大内存做了限制,早点版本是16M,不同设备大小不同。另一种情况就是当前应用需要向其他应用获取数据,由于是两个应用,所以必须是跨进程获取所需数据,,甚至我们通过系统提供的ContentProvider去查询数据的时候,其实也是一种进程间通信,只是通信的细节被系统内部屏蔽了。

Android中的多进程模式

1.开启多进程模式
正常情况下,在Android中多进程是指一个应用中存在多个进程的情况,先不讨论两个应用之间的多进程情况。首先,在Android中使用多进程只有一种方法,就是个四大组件(activity,service,receiver,contentprovider)在清单文件中设置android:process属性,除此外没有其他办法

<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" />
                <category android:name="android.intent.category._LAUNCHER" />
            </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" />

上面的两个activity都指定了process属性,并且他们的值不相同,说明我们给应用增加了两个进程,假设当前应用的包名为“com.abc.def”,当启动第二个activity的时候,系统会给它创建一个单独的进程,进程名为“com.abc.def:remote”,当第三个activity启动时,系统也会给它创建一个新的进程,进程名为“com.ryg.chapter_2.remote”,主activity的进程名默认是包名。

上面process的命名方式有两种,二者有什么不同呢?
首先:“:”的含义是指要在当前进程名前面附加上当前的包名,这是一种简写的方法。第二种则是一种完整的命名方式,不会附加包名信息。
其次:进程名以“:”开头的进程属于当前应用的私有进程,其他应用和组件不可以和他跑在同一进程里,而进程名布衣“:”开头的进程属于全局进程,其他应用可以用个ShareUID方法和他跑在同一个进程中。

android系统会给每一个应用都分配一个唯一的UID,具有相同的UID的应用才可以共享数据,注意,两个应用通过UID跑在同一个进程中是有要求的,需要两个应用有相同的Uid,并且签名相同才可以。UID相同他们可以相互互相的私有数据,如data目录,组件信息,如果相同的Uid,并且签名相同他们还可以分享内存数据,或者说他们像同一个应用的两个部分。

2.多进程模式的运行机制
我们知道Android为每一个应用分配一个独立的虚拟机,或者说为每一个进程都分配了一个单独的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同虚拟机上访问一个类的对象会有多个副本。同一类在不同进程之间的数据是不糊相互影响了,如果一个进程改变了一个类的对象的值,这个改变只会发生在当前进程,其他进程中类的对象的值并没有收到影响。

所有运行在不同进程的四大组件,都不能共享数据,这就是多进程带来的影响,一般情况下多进程会带来一下几点影响:

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效
  3. SharedPreferences的可靠性下降
  4. Application会被多次创建

    第一个问题上面已经解释了,第二个问题类似,既然都不在一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步了,因为不同进程锁的不是一个对象。第三个问题是因为SharedPreferences不支持两个进程同时执行写操作,否则会有一点几率的丢失,因为SharedPreferences底层是通过读写XML来实现的,并发写会出现问题,有时候读写可能都有问题。第四个问题是显而易见的,当一个组件跑到一个新的进程中的时候,由于系统要创建一个新的进程同时分配独立的虚拟机,所以这个过程其实就是重新启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一遍,那么必然会创建新的application,同理运行在不同进程的组件属于两个不同的虚拟机和application。

不同进程的组件会拥有独立的虚拟机,application和内存空间,这个实际开发带来的许多麻烦,我们可以这么理解同一个应用的多进程:它相当于两个不同的应用采用了ShareUID的方式。

IPC基础概览介绍

1.Serializable接口
对一个对象序列化只要实现Serializable接口即可
serialVersionUID是用来辅助序列化和反序列化的过程的,原则上序列化后的数据的serialVersionUID只有和当前类的serialVersionUID相同才能正常的反序列化。详细的工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当饭序列化的时候系统会去检测文件中饭serialVersionUID,看他是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,就可以成功序列化,否则说明当前类和序列化的类相比发生了某些转换,比如变量的数量,类型可能发生改变,则无法正常反序列化。
一般来说我们应该手动的设置serialVersionUID的值,比如1L,也可以让他根据当前类的结构去自动生成hash值,如果不手动的设置serialVersionUID 的值,让类的结构发生改变的时候,系统会重新计算当前类的hash值,比如增加或者删除了某个成员变量,这是会反序列化失败,程序crash。所有手动设置可以避免这样的情况发生。

public class User implements Serializable

2.Parcelable接口
Parcelable也是一个接口,只要实现了这个接口,一个类的对象就可以实现序列化并可以通过intent和binder传递。

public class User implements Parcelable {

    private int id;
    private String name;
    private String sex;
    private int age;

    public User(int id, String name, String sex, int age) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    //从序列化后的对象中创建原始对象
    protected User(Parcel in) {
        id = in.readInt();
        name = in.readString();
        sex = in.readString();
        age = in.readInt();
    }
    //反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        //从序列化后的对象中创建原始对象
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }
        //创建指定长度的原始对象数组
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
    //返回当前对象的内容描述
    @Override
    public int describeContents() {
        return 0;
    }
    //序列化
    //将当前对象写入序列化结构中
    //flag 0或1,1代表当前对象需作为返回值返回,一般都是用0
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeString(sex);
        dest.writeInt(age);
    }
}

两者如何选择?

Serializable是java中的序列化接口,使用起来简单但是开销很大,序列化和反序列化都要大量的I/O操作,而Parcelable 是Android 的序列化方式,因此更加适合用在android平台上,确定就是使用起来稍微麻烦点,但是它效率很高,这是安卓推荐的序列化方式,所以我们要首选Parcelable 。Parcelable 主要用于内存序列化上,将对象序列化存储到设备中或者用于网络传输是,建议使用Serializable

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

通过AIDL来分析Binder的工作机制(第47页)
这里写图片描述

手动实现一个Binder

  1. 声明一个我AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只有一个asBinder方法。
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

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

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

    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];
        }
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}
public interface IBookManager  extends IInterface {
    //Binder唯一标识符,一般用Binder的类名来表示
    static final String DESCRIPTOR = "com.ryg.chapter_2.manualbinder.IBookManager";
    //表示下面两个方法的id,写法固定,仿照系统生成的规则写的
    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方法。这段代码是系统原本是系统自动生成的,我们仿照系统生成的规则去手动书写这部分代码,如果有三个方法,我们就再声明一个id,然后再按照固定形式来声明新的方法。

2.实现Stub类和Stub类中的Proxy代理类,这段代码可以自己写,但是写出来和系统生成的一样,因此我们参考系统生成的代码,做一些结构上的调整

//这个实现其实就是一个Binder类
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 com.ryg.chapter_2.aidl.IBookManager
     * interface, generating a proxy if needed.
     * 将Binder对象装换成客户端所需的AIDL接口类型的数据
     */
    public static IBookManager asInterface(IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IBookManager))) {
            return ((IBookManager) iin);
        }
        return new BookManagerImpl.Proxy(obj);
    }

    /**
     *
     * @param code 服务器通过code判断请求的方法是什么
     * @param data 接着再data中获取目标方法所需的参数,如果目标方法有参数的话,然后执行目标方法
     * @param reply 当目标方法执行完毕后,就向replay中写入返回值
     * @param flags  注意如果这个方法返回false,则客户端请求失败
     * @return
     * @throws RemoteException
     */
    @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 待实现
    }

    @Override
    public IBinder asBinder() {
        return this;
    }
    //代理类
    /**
     * 当客户端和服务端位于同一个进程,方法调用就不会走跨进程的transact
     * 过程,而如果在不同线程,就会走transact过程,这个逻辑由Proxy来完成
     */
    private static class Proxy implements IBookManager {
        private IBinder mRemote;

        Proxy(IBinder remote) {
            mRemote = remote;
        }
        //返回当前binder对象
        @Override
        public IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        /**
         * 实现过程是这样的,首先创建该方法需要的Parcel 对象 data 和reply和返回值对象list
         * 然后把该方法的参数信息写入data中(如果有的话),然后在调用transact 方法发起RPC
         * (远程过程调用)请求,同时当前线程继续挂起,然后服务端的onTransact()方法会被调用,
         * 直到RPC过程返回后,当前线程继续执行,并右reply取出RPC返回的结果,最后返回reply数据
         * 注意:客户端发送远程请求后,由于当前线程会被挂起直到服务器进程返回数据,所有如果一个
         * 远程方法是耗时的,则不能再UI线程中发起远程请求,再者由于服务器的Binder方法运行在Binder
         * 的线程池中,所有Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行
         * 在一个线程中了
         */
        @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两个很重要的方法linkToDeath,unlinkToDeath
我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端Binder连接断裂,称之为Binder死亡,会导致我们远程调用失败,更为关键的是,如果我们不知道Binder已经连接断裂,那么客户端的功能就会受到影响,为了解决这个问题,Binder提供了两个配对的方法,linkToDeath,unlinkToDeath,通过linkToDeath可以给Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起请求恢复连接。

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

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

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

IBookManager bookManager = IBookManager.Stub.asInterface(service);
bookManager .asBinder().linkToDeath(mDeathRecipient, 0);

 private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
                List<Book> list = bookManager.getBookList();
                Log.i(TAG, "query book list, list type:"
                        + list.getClass().getCanonicalName());
                Log.i(TAG, "query book list:" + list.toString());
                Book newBook = new Book(3, "Android进阶");
                bookManager.addBook(newBook);
                Log.i(TAG, "add book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                Log.i(TAG, "query book list:" + newList.toString());
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            mRemoteBookManager = null;
            Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
        }
    };
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

http://blog.csdn.net/boyupeng/article/details/47011383

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值