美文网首页
Android读书笔记(2)—— IPC机制

Android读书笔记(2)—— IPC机制

作者: AndroidMaster | 来源:发表于2018-01-14 20:41 被阅读15次

    一、IPC与多线程

    1、IPC简介

    线程:① CPU最小的调度单元 ② 一种有限的系统资源
    进程:一个执行单元。一般指一个程序或一个应用。一个进程可以包含多个线程。
    IPC:进程间通信。

    多线程的情况
    1)因为某些原因自身需要采用多线程模式来实现。比如:某些模块需要运行在单独进程;为了加大一个应用可以使用的内存。
    2)需要从其他应用获取数据。

    2、Android中的多进程模式

    2.1、开启多进程模式

    在Android中一个应用开启多进程唯一办法:给四大组件在AndroidMenifest.xml中指定android:process属性。

    <service
        android:name=".MyService"
        android:process=":remote" />
    <service
        android:name=".SecondService"
        android:process="com.example.xiang.myapplication.remote" />
    

    默认进程名是包名。“:remote”是一种省略写法,完整名为“com.example.xiang.myapplication:remote”进程名,以“:”开头,属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。

    2.2、多进程模式的运行机制

    Android系统为每一个进程分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间。将导致存在如下问题:
    1)静态成员和单例完全失效
    2)线程同步机制完全失效
    3)SharedPreferences的可靠性下降
    4)Application会多次创建
    不同的进程拥有独立的虚拟机、Application和内存空间,导致通过内存来共享数据,都会共享失败。

    Android的IPC方式
    1)Intent
    2)文件共享方式
    3)Binder(AIDL和Messenger)
    4)ContentProvider
    5)Socket

    二、IPC的基础概念

    为什么要序列化?
    1)永久性保存对象的字节序列到本地文件中
    2)通过序列化在网络中传递对象
    3)通过序列化在进程间传递对象

    1、Serializable接口

    Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。

    private static final long serialVersionUID = 8154678445665565611L;
    

    serialVersionUID是用来辅助序列化和序列化的过程,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。一般我们应该手动指定serialVersionUID的值,比如1L(或者根据类结构生成hash值)。若不指定,反序列化时当前类有所改变(比如增加或者删除了成员变量),那么系统会重新计算当前类的hash值并赋给serialVersionUID,导致serialVersionUID不一致,于是反序列化失败,程序就会crash。

    静态成员变量属于类不属于对象,不会参加序列化
    用transient标记的成员变量不会参与序列化

    2、Parcelable接口

    public class User implements Parcelable {
        private int userId;
        private String userName;
        private Book book;
    
        protected User(Parcel in) {
            userId = in.readInt();
            userName = in.readString();
            //book是一个可序列化对象,需要传递当前线程的上下文类加载器
            book = in.readParcelable(Thread.currentThread().getContextClassLoader());
        }
    
        /**
         * 实现反序列化
         */
        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];
            }
        };
    
        /**
         * 几乎所有情况都返回0
         * @return
         */
        @Override
        public int describeContents() {
            return 0;
        }
    
        /**
         * 实现序列化
         *
         * @param parcel
         * @param i
         */
        @Override
        public void writeToParcel(Parcel parcel, int i) {
            parcel.writeInt(userId);
            parcel.writeString(userName);
            parcel.writeParcelable(book, i);
        }
    }
    

    3、Binder

    3.1、AIDL接口的创建

    Binder就是Android中实现了IBinder接口的一个类,是跨进程通信的媒介。在Android开发中,Binder主要用在Service中,包括Messenger(底层其实是AIDL)和AIDL。

    //Book.java
    package com.example.xiang.myapplication;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Book implements Parcelable {
        private int bookId;
        private String bookName;
    
        public Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        /**
         * 几乎所有情况都返回0
         * @return
         */
        @Override
        public int describeContents() {
            return 0;
        }
    
        /**
         * 实现序列化
         * @param parcel
         * @param i
         */
        @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];
            }
        };
    
        public Book(Parcel in) {
            bookId = in.readInt();
            bookName = in.readString();
        }
    }
    
    //Book.aidl
    package com.example.xiang.myapplication;
    
    parcelable Book;
    
    // IBookManager.aidl
    package com.example.xiang.myapplication;
    
    import com.example.xiang.myapplication.Book;//必须导入,否则报错
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
    }
    

    Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是定义的一个接口,虽然Book类和IBookManager在同一个包中,但是还是要显示导入Book类。目录结构如下:

    AIDL目录结构

    (新建Book.aidl时候,直接填Book为名字时候会报错,只有先创建完之后再RENAME才不会报错)

    3.2、Binder类分析

    系统为IBookManager.aidl自动生成的Binder类

    package com.example.xiang.myapplication;
    
    public interface IBookManager extends android.os.IInterface {
       
        public static abstract class Stub extends Binder implements IBookManager {
            //Binder的唯一标识
            private static final String DESCRIPTOR = "com.example.xiang.myapplication.IBookManager";
           
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            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 IBookManager.Stub.Proxy(obj);
            }
    
            @Override
            public android.os.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);
            }
    
            private static class Proxy implements IBookManager {
                private IBinder mRemote;
    
                Proxy(IBinder remote) {
                    mRemote = remote;
                }
    
                @Override
                public IBinder asBinder() {
                    return mRemote;
                }
    
                public 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);
                        //_data写入参数
                        //发起远程调用,当前线程挂起
                        mRemote.transact(Stub.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(Stub.TRANSACTION_addBook, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
    
            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;
    }
    
    
    • asInterface //将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,如果C/S位于同一进程,此方法返回就是服务端的Stub对象本身,否则返回的就是系统封装后的Stub.proxy对象
    • asBinder //返回当前的Binder对象
    • onTransact//运行在服务端
    • Proxy#getBookList//运行在客户端,内部实现过程如下:首先创建该方法所需要的输入型对象Parcel对象_data,输出型Parcel对象_reply和返回值对象List。然后把该方法的参数信息写入_data( 如果有参数);接着调用transact方法发起RPC( 远程过程调用),同时当前线程挂起(因此不能再UI线程中发起远程请求);然后服务端的onTransact方法会被调用(服务端的Binder方法运行在线程池,所以需要采用同步方式实现),直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
    Binder工作机制

    AIDL的本质:系统提供的一个快速实现Binder的工具而已。

    3.3、远程服务端Service的实现
    public class BookManagerService extends Service {
        private static final String TAG = "BookManagerService";
        //CopyOnWriteArrayList支持并发读/写
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
        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, "Android进阶之光"));
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }
    
    //注册Service
    <service
        android:name="com.example.service.MessengerService"
        android:process=":remote" />
    
    

    AIDL方法(getBookList和addBook)是运行在Binder线程池中的,所以需要处理线程同步,这里采用CopyOnWriteArrayList来进行自动的线程同步。

    3.4、客户端的实现
    public class BookManagerActivity extends Activity {
        private static final String TAG = "BookManagerActivity";
        
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager bookManager = IBookManager.Stub.asInterface(service);
                try {
                    List<Book> list = bookManager.getBookList();
                    Log.d(TAG, "查询图书列表:" + list.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent, connection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(connection);
            super.onDestroy();
        }
    }
    

    服务端的方法可能需要很久才能执行完毕,上面这样写的目的是为了更好的了解AIDL的实现步骤。
    对象是不能跨进程直接传输的,对象跨进程传输的本质是反序列化过程,这也是为什么AIDL中定义的对象必须实现Parcelable接口。

    4、Binder的两个重要方法

    linkToDeath(DeathRecipient recipient, int flags) //设置Binder的死亡代理。当Binder死亡时,系统会回调DeathRecipient的binderDied()方法,所以需要在此方法中移除之前绑定的binder代理(调用unlinkToDeath)并重新绑定远程服务
    unlinkToDeath(DeathRecipient recipient, int flags) //移除死亡代理
    isBinderAlive() //判断Binder是否死亡

    DeathRecipient是IBinder的一个内部接口,里面只有一个方法binderDied()

    5、补充说明

    AIDL文件支持的数据类型
    1)基本数据类型(int、long、char、boolean等)
    2)String和CharSequence
    3)List:只支持ArrayList,里面的每个元素必须被AIDL所支持
    4)Map:只支持HashMap,里面的每个元素必须被AIDL所支持,包括key和value
    5)Parcelable:所有实现了Parcelable接口的对象
    6)AIDL:所有AIDL接口本身也可以在AIDL文件中使用

    • 自定义的Parcelable对象(如上例中的Book类),必须新建一个和它同名的AIDL文件(Book.aidl),并添加相应的内容。
    • 自定义Parcelable对象和AIDL对象必须要显式import进来。
    • 除了基本数据类型的其他类型参数,都需要标上方向:in、out或者inout。
    • AIDL接口中只支持方法,不支持声明静态常量。
    • 建议把所有和AIDL相关的类和文件全部放在同一个包中,好处是,若客户端在另一应用(模块),复制整个包即可。
    • AIDL的包结构在服务端和客户端必须保持一致,否则运行出错。

    DeathRecipient是IBinder的一个内部接口,里面只有一个方法binderDied()

    三、Android中的IPC方式

    1、使用Intent

    启动另一个进程的Activity、Service和Receiver的时候,在Bundle(实现了Parcelable接口)中附加信息,并通过Intent进行传递

    2、使用文件共享

    序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象,适合对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写问题。

    SharedPreferences底层实现采用XML文件来存储键值对。系统对它的读/写有一定的缓存策略,即在内存中会有一份 SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写变得不可靠,面对高并发读/写时SharedPreferences 有很大几率丢失数据,因此不建议在IPC中使用SharedPreferences。

    3、使用Messenger(信使)

    一种轻量级的IPC方案,底层实现是AIDL。服务端以串行的方式处理客户端发来的Message对象。
    举个例子

    //MessengerService.java
    //服务端代码
    public class MessengerService extends Service {
        private static final String TAG = "MessengerService";
    
        private static class MessengerHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case Constants.MSG_FROM_CLIENT:
                        //收到客户端的消息
                        Log.d(TAG, "收到客户端发来的信息:" + msg.getData().getString("msg"));
                        Messenger client = msg.replyTo;
                        Message replyMessage = Message.obtain(null, Constants.MSG_FROM_SERVER);
                        Bundle bundle = new Bundle();
                        bundle.putString("reply","你的消息我已经收到,稍后回复你。");
                        try {
                            client.send(replyMessage);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        break;
                }
            }
        }
    
        private final Messenger mMessenger = new Messenger(new MessengerHandler());
    
        @Override
        public IBinder onBind(Intent intent) {
            return mMessenger.getBinder();
        }
    }
    
    
    //注册Service
    <service
        android:name="com.example.service.MessengerService"
        android:process=":remote" />
    
    //MainActivity.java
    //客户端实现
    public class MainActivity extends Activity {
        private static final String TAG = "MainActivity";
        private Messenger mService;
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                mService = new Messenger(iBinder);
                Message msg = Message.obtain(null, Constants.MSG_FROM_CLIENT);
                Bundle data = new Bundle();
                data.putString("msg", "hello, this is client");
                msg.setData(data);
                //注意这一句
                msg.replyTo = mGetReplyMessenger;
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
            }
        };
        private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    
        private class MessengerHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case Constants.MSG_FROM_SERVER:
                        Log.d(TAG, "收到服务端的回复" + msg.getData().getString("reply"));
                        break;
                }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            final Intent intent = new Intent(this, MessengerService.class);
            findViewById(R.id.btn_bind).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    bindService(intent, connection, Context.BIND_AUTO_CREATE);
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            unbindService(connection);
            super.onDestroy();
        }
    }
    

    Messenger中传递的数据必须将数据放入Message,Message和Messenger都实现了Parcelable接口(放入Message中的对象也要实现Parcelable接口才能传递)。工作原理如图所示:
    [站外图片上传中...(image-7382db-1516020836113)]

    Messenger常用方法

    Messenger(Handler target)
    Messenger(IBinder target)
    send(Message message)
    getBinder() IBinder

    Message相关属性

    replyTo //返回一个可以答复的Messenger

    4、使用AIDL

    见上面的例子

    5、使用ContentProvider

    6、使用Socket

    四、Binder连接池

    五、IPC总结

    相关文章

      网友评论

          本文标题:Android读书笔记(2)—— IPC机制

          本文链接:https://www.haomeiwen.com/subject/raqloxtx.html