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

一、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总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。