IPC机制

作者: fomin | 来源:发表于2018-09-16 23:16 被阅读6次
    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 网络数据交换

    相关文章

      网友评论

          本文标题:IPC机制

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