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
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 | 网络数据交换 |
网友评论