IPC机制

1、IPC简介

IPC是Inter-Process Communication的缩写,进程间通信或者跨进程通信,是指两进程之间进行数据交换的过程。在Android中,UI是主线程,其可以操作界面元素,但耗时操作放在UI线程处理会导致ANR错误。

2、多进程模式
2.1、开启多进程

通过四大组件制定android:process属性,可以开启多进程模式,例如:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <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=":test"/>
    <activity android:name=".ThirdActivity"
        android:process="com.fomin.ipc.test"/>
</application>

上面分别为SecondActivity和ThirdActivity增加了process属性,可以看出,他们的命名是不一样的,首先,“:”的含义是指要在当前的进程名前面附加上当前的包名,简写的写法;其次,“:”开头的进程属于当前应用的私有名称,其它应用的组件不可以和它跑在同一个进程中,而进程名不以":"开头的进程属于全局进程,可以通过ShareUID和它跑在同一个进程中(要求签名相同既可以)。

2.2、运行机制

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

一般来说,使用多进程是产生下面的问题:

  • 静态成员和单列模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降
  • Application会多次创建
3、Binder

Binder是Android中的一个类,实现了IBinder接口,从IPC角度来说,它是Android的一种跨进程通信方式。主要在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信的。

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.fomin.ipc.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.fomin.ipc.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.fomin.ipc.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.fomin.ipc.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.fomin.ipc.IBookManager))) {
                return ((com.fomin.ipc.IBookManager) iin);
            }
            return new com.fomin.ipc.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.fomin.ipc.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.fomin.ipc.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.fomin.ipc.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.fomin.ipc.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.fomin.ipc.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.fomin.ipc.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.fomin.ipc.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.fomin.ipc.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.fomin.ipc.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.fomin.ipc.Book book) throws android.os.RemoteException;
}

上面的类是通过AIDL自动生成的,介绍下相关的方法含义。

  • DESCRIPTOR:Binder的唯一标识,一般用在当前Binder的类名表示。
  • asInterface(android.os.IBinder obj):用于将服务端的Binder对象转换成客户端的AIDL接口类型对象,转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的系统封装后的Stub.proxy对象。
  • asBinder:返回当前的Binder对象
  • onTransact:方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
  • Proxy#getBookList:自定义的API
  • Proxy#addBook:自定义的API

Binder的工作机制

4、Android中的IPC方式
4.1、使用文件共享

共享文件是一种不错的进程间通信方式,两个进程通过读/写一个文件来交换数据。在Android中,SharedPreferences是轻量级的存储方案,由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences的文件缓存,因此在多线程下,系统对读/写变得不可靠,面对高并发的读/写数据,有很大的几率会丢失数据,不建议进程间通信使用SharedPreferences。

进程一写数据:
fun saveDataToFile() {
    Thread(Runnable {
        var book: Book = Book(1, "Android")
        val dir = File(path)
        if (!dir.exists()) {
            dir.mkdirs()
        }
        val cachedFile = File(cachePath)
        var stream: ObjectOutputStream? = null
        try {
            stream = ObjectOutputStream(FileOutputStream(cachedFile))
            stream.writeObject(book)
        } catch (e: IOException) {
            e.printStackTrace();
        } finally {
            stream?.close();
        }
    }).start()
}
进程二读取数据:
fun readFromFile() {
    Thread(Runnable {
        var book: Book? = null
        val cachedFile = File(cachePath)
        if (cachedFile.exists()) {
            var stream: ObjectInputStream? = null
            try {
                stream = ObjectInputStream(FileInputStream(cachedFile))
                book = stream.readObject() as Book
            } catch (e: IOException) {
                e.printStackTrace();
            } finally {
                stream?.close();
            }
        }
    }).start()
}
4.2、使用Messager

Messager中进行的数据传递必须将数据传入Message中,而Messager和Message都实现了Parcelable接口。在Message中有what、arg1、arg2、Bundle和replyTo,而另外一个object字段只能使用系统提供的Parcelable对象才能传输,自定义的Parcelable对象是无法通过传输的。下面通过例子来看看它是怎样使用的。

class MessageService : Service() {

    private val TAG = "MessageService"

    private val mMessenger = Messenger(MessageHandler())

    private inner class MessageHandler : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                1001 -> {
                    Log.d(TAG, msg.data.getString("key"))
                    val client = msg.replyTo
                    val reply = Message.obtain(null, 1002)
                    val bundle = Bundle()
                    bundle.putString("key", "消息收到")
                    reply.data = bundle
                    try {
                        client.send(reply)
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
                else -> super.handleMessage(msg)
            }
        }
    }

override fun onBind(intent: Intent): IBinder? {
        return mMessenger.binder
    }
}

注册service

<service android:name=".MessageService"
    android:process=":process1"/>

客户端使用

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val intent = Intent(this, MessageService::class.java)
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
private val TAG = "MainActivity"
private var mService: Messenger? = null
private val mMessenger = Messenger(MessengerHandler())

private val mConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        mService = Messenger(service)
        val msg = Message.obtain(null, 1001)
        val data = Bundle()
        data.putString("msg", "客户端发送消息")
        msg.data = data
        msg.replyTo = mMessenger
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onServiceDisconnected(name: ComponentName) {

    }
}

private inner class MessengerHandler : Handler() {
    override fun handleMessage(msg: Message) {
        when (msg.what) {
            1002 -> Log.i(TAG, msg.data.getString("key"))
            else -> super.handleMessage(msg)
        }
    }
}
4.3、使用AIDL

AIDL首先需要创建一个Service监听客户端的连接请求;再次需要创建客户端绑定Service。下面来看看AIDL接口创建(IBookManager.aidl和Book.aidl)

//IBookManager.aidl
import com.fomin.ipc.Book;
// Declare any non-default types here with import statements

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

// IBook.aidl
packag// IBook.aidl
package com.fomin.ipc;

// Declare any non-default types here with import statements

parcelable Book;

支持以下数据类型:

  • 基本数据类型(int、long、char、boolean、double等)
  • String和CharSequence
  • List:只支持ArraryList
  • Map:只支持HashMap
  • Parcelable:所有实现Parcelable的对象
  • AIDL:所有AIDL本身也可以使用

创建Service:

class BookManagerService : Service() {

    private val TAG = "BookManagerService"
    private val mBookList = CopyOnWriteArrayList<Book>()

    private val mBinder = object : IBookManager.Stub() {
        @Throws(RemoteException::class)
        override fun getBookList(): List<Book>? {
            return mBookList
        }

        @Throws(RemoteException::class)
        override fun addBook(book: Book) {
            mBookList.add(book)
        }
    }
    override fun onBind(intent: Intent?): IBinder {
        return mBinder
    }
}

注册service

<service android:name=".BookManagerService"
    android:process=":process2"/>

创建客户端

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val intent = Intent(this, BookManagerService::class.java)
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}

override fun onDestroy() {
    unbindService(mConnection)
    super.onDestroy()
}

private val mConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        val bookManager = IBookManager.Stub.asInterface(service)
        try {
           val bookList = bookManager.bookList
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onServiceDisconnected(name: ComponentName) {

    }
}

在这有个问题,UI线程执行耗时的操作会导致ANR,所以客户端在getBookList的时候需要在非UI线程执行

4.4、使用ContentProvider

ContentProvider底层也是实现Binder,主要以表格形式来组织数据,可以包含多个表,支持文件数据(图片、视频等)。需要注意的是query、update、insert、delete四大方法是存在多线程并发访问的,因此内部需要做好线程同步。

//数据库操作工具
class DbHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL("")//创建表
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {

    }

    companion object {
        private val DB_NAME = "book.db"
        private val DB_VERSION = 1
    }
}
//数据操作
class BookProvider : ContentProvider() {

    private var mDb: SQLiteDatabase? = null

    override fun onCreate(): Boolean {
        mDb = DbHelper(getContext()).writableDatabase
        return true
    }

    override fun insert(uri: Uri?, values: ContentValues?): Uri {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun getType(uri: Uri?): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

注册ContentProvider,android:authorities必须是唯一的,外界想使用BookProvider必须声明com.fomin.PROVIDER这权限

<provider
    android:authorities="com.fomin.ipc"
    android:name=".BookProvider"
    android:permission="com.fomin.PROVIDER"
    android:process=":provider"/>

客户端使用:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val bookUri = Uri.parse("content://com.fomin.ipc/book")
    val contentValues = ContentValues()
    contentValues.put("id", 1)
    contentValues.put("name", "Android")
    contentResolver.insert(bookUri, contentValues)
}
4.5、使用socket

使用socket通信,首先需要声明权限:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

其次不能再主线程中访问网络,网络操作可能是耗时的,影响程序的效率,而且在4.0系统会报错android.os.NetworkOnMainThreadException.

5、选用合适的IPC方式
名称 优点 缺点 适用场景
Bundle 简单 只能传输Bundle支持的数据 四大组件进程间的通信
文件共享 简单 不适合高并发场景,并无法做到进程间的即时通信 无并发访问情形
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用复杂,需要处理好线程间的同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好的处理高并发的情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据 低并发的一对多即时通信,无RPC需求,或者不需要返回结果的RPC需求
ContentProvider 数据源访问功能强大,支持一对多并发数据共享 主要提供数据源的CRUD操作 一对多进程间的数据共享
Socket 功能强大,通过网络传输字节流,支持一对多并发实时通信 实现繁琐,不支持直接的RPC 网络数据交换
posted @ 2018-09-17 22:56  fomin  阅读(391)  评论(0编辑  收藏  举报